Weather effects (#12528)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
@@ -21,6 +21,7 @@ public sealed class ParallaxOverlay : Overlay
|
|||||||
|
|
||||||
public ParallaxOverlay()
|
public ParallaxOverlay()
|
||||||
{
|
{
|
||||||
|
ZIndex = ParallaxSystem.ParallaxZIndex;
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
_parallax = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ParallaxSystem>();
|
_parallax = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ParallaxSystem>();
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public sealed class ParallaxSystem : SharedParallaxSystem
|
|||||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||||
|
|
||||||
private const string Fallback = "Default";
|
private const string Fallback = "Default";
|
||||||
|
public const int ParallaxZIndex = 0;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
|
|||||||
207
Content.Client/Weather/WeatherOverlay.cs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Client.Parallax;
|
||||||
|
using Content.Shared.Weather;
|
||||||
|
using OpenToolkit.Graphics.ES11;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
|
using Robust.Client.Utility;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Weather;
|
||||||
|
|
||||||
|
public sealed class WeatherOverlay : Overlay
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IClyde _clyde = default!;
|
||||||
|
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||||
|
[Dependency] private readonly IResourceCache _cache = default!;
|
||||||
|
private readonly SharedTransformSystem _transform;
|
||||||
|
private readonly SpriteSystem _sprite;
|
||||||
|
private readonly WeatherSystem _weather;
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||||
|
|
||||||
|
private IRenderTexture? _blep;
|
||||||
|
|
||||||
|
public WeatherOverlay(SharedTransformSystem transform, SpriteSystem sprite, WeatherSystem weather)
|
||||||
|
{
|
||||||
|
ZIndex = ParallaxSystem.ParallaxZIndex + 1;
|
||||||
|
_transform = transform;
|
||||||
|
_weather = weather;
|
||||||
|
_sprite = sprite;
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
if (args.MapId == MapId.Nullspace)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_entManager.TryGetComponent<WeatherComponent>(_mapManager.GetMapEntityId(args.MapId), out var weather) ||
|
||||||
|
weather.Weather == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.BeforeDraw(in args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||||
|
|
||||||
|
if (!_entManager.TryGetComponent<WeatherComponent>(mapUid, out var weather) ||
|
||||||
|
weather.Weather == null ||
|
||||||
|
!_protoManager.TryIndex<WeatherPrototype>(weather.Weather, out var weatherProto))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var alpha = _weather.GetPercent(weather, mapUid, weatherProto);
|
||||||
|
DrawWorld(args, weatherProto, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawWorld(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha)
|
||||||
|
{
|
||||||
|
var worldHandle = args.WorldHandle;
|
||||||
|
var mapId = args.MapId;
|
||||||
|
var worldAABB = args.WorldAABB;
|
||||||
|
var worldBounds = args.WorldBounds;
|
||||||
|
var invMatrix = args.Viewport.GetWorldToLocalMatrix();
|
||||||
|
var position = args.Viewport.Eye?.Position.Position ?? Vector2.Zero;
|
||||||
|
|
||||||
|
if (_blep?.Texture.Size != args.Viewport.Size)
|
||||||
|
{
|
||||||
|
_blep?.Dispose();
|
||||||
|
_blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "weather-stencil");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut out the irrelevant bits via stencil
|
||||||
|
// This is why we don't just use parallax; we might want specific tiles to get drawn over
|
||||||
|
// particularly for planet maps or stations.
|
||||||
|
worldHandle.RenderInRenderTarget(_blep, () =>
|
||||||
|
{
|
||||||
|
var bodyQuery = _entManager.GetEntityQuery<PhysicsComponent>();
|
||||||
|
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||||
|
|
||||||
|
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
|
||||||
|
{
|
||||||
|
var matrix = _transform.GetWorldMatrix(grid.Owner, xformQuery);
|
||||||
|
Matrix3.Multiply(in matrix, in invMatrix, out var matty);
|
||||||
|
worldHandle.SetTransform(matty);
|
||||||
|
|
||||||
|
foreach (var tile in grid.GetTilesIntersecting(worldAABB))
|
||||||
|
{
|
||||||
|
// Ignored tiles for stencil
|
||||||
|
if (_weather.CanWeatherAffect(grid, tile, bodyQuery))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gridTile = new Box2(tile.GridIndices * grid.TileSize,
|
||||||
|
(tile.GridIndices + Vector2i.One) * grid.TileSize);
|
||||||
|
|
||||||
|
worldHandle.DrawRect(gridTile, Color.White);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}, Color.Transparent);
|
||||||
|
|
||||||
|
worldHandle.SetTransform(Matrix3.Identity);
|
||||||
|
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilMask").Instance());
|
||||||
|
worldHandle.DrawTextureRect(_blep.Texture, worldBounds);
|
||||||
|
Texture? sprite = null;
|
||||||
|
var curTime = _timing.RealTime;
|
||||||
|
|
||||||
|
switch (weatherProto.Sprite)
|
||||||
|
{
|
||||||
|
case SpriteSpecifier.Rsi rsi:
|
||||||
|
var rsiActual = _cache.GetResource<RSIResource>(rsi.RsiPath).RSI;
|
||||||
|
rsiActual.TryGetState(rsi.RsiState, out var state);
|
||||||
|
var frames = state!.GetFrames(RSI.State.Direction.South);
|
||||||
|
var delays = state.GetDelays();
|
||||||
|
var totalDelay = delays.Sum();
|
||||||
|
var time = curTime.TotalSeconds % totalDelay;
|
||||||
|
var delaySum = 0f;
|
||||||
|
|
||||||
|
for (var i = 0; i < delays.Length; i++)
|
||||||
|
{
|
||||||
|
var delay = delays[i];
|
||||||
|
delaySum += delay;
|
||||||
|
|
||||||
|
if (time > delaySum)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sprite = frames[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite ??= _sprite.Frame0(weatherProto.Sprite);
|
||||||
|
break;
|
||||||
|
case SpriteSpecifier.Texture texture:
|
||||||
|
sprite = texture.GetTexture(_cache);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the rain
|
||||||
|
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||||
|
|
||||||
|
// TODO: This is very similar to parallax but we need stencil support but we can probably combine these somehow
|
||||||
|
// and not make it spaghetti, while getting the advantages of not-duped code?
|
||||||
|
|
||||||
|
|
||||||
|
// Okay I have spent like 5 hours on this at this point and afaict you have one of the following comprises:
|
||||||
|
// - No scrolling so the weather is always centered on the player
|
||||||
|
// - Crappy looking rotation but strafing looks okay and scrolls
|
||||||
|
// - Crappy looking strafing but rotation looks okay.
|
||||||
|
// - No rotation
|
||||||
|
// - Storing state across frames to do scrolling and just having it always do topdown.
|
||||||
|
|
||||||
|
// I have chosen no rotation.
|
||||||
|
|
||||||
|
const float scale = 1f;
|
||||||
|
const float slowness = 0f;
|
||||||
|
var scrolling = Vector2.Zero;
|
||||||
|
|
||||||
|
// Size of the texture in world units.
|
||||||
|
var size = (sprite.Size / (float) EyeManager.PixelsPerMeter) * scale;
|
||||||
|
var scrolled = scrolling * (float) curTime.TotalSeconds;
|
||||||
|
|
||||||
|
// Origin - start with the parallax shift itself.
|
||||||
|
var originBL = position * slowness + scrolled;
|
||||||
|
|
||||||
|
// Centre the image.
|
||||||
|
originBL -= size / 2;
|
||||||
|
|
||||||
|
// Remove offset so we can floor.
|
||||||
|
var flooredBL = args.WorldAABB.BottomLeft - originBL;
|
||||||
|
|
||||||
|
// Floor to background size.
|
||||||
|
flooredBL = (flooredBL / size).Floored() * size;
|
||||||
|
|
||||||
|
// Re-offset.
|
||||||
|
flooredBL += originBL;
|
||||||
|
|
||||||
|
for (var x = flooredBL.X; x < args.WorldAABB.Right; x += size.X)
|
||||||
|
{
|
||||||
|
for (var y = flooredBL.Y; y < args.WorldAABB.Top; y += size.Y)
|
||||||
|
{
|
||||||
|
var box = Box2.FromDimensions((x, y), size);
|
||||||
|
worldHandle.DrawTextureRect(sprite, box, (weatherProto.Color ?? Color.White).WithAlpha(alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
worldHandle.SetTransform(Matrix3.Identity);
|
||||||
|
worldHandle.UseShader(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
219
Content.Client/Weather/WeatherSystem.cs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
using Content.Shared.Weather;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Physics.Systems;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
|
||||||
|
namespace Content.Client.Weather;
|
||||||
|
|
||||||
|
public sealed class WeatherSystem : SharedWeatherSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly AudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||||
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
|
||||||
|
// Consistency isn't really important, just want to avoid sharp changes and there's no way to lerp on engine nicely atm.
|
||||||
|
private float _lastAlpha;
|
||||||
|
private float _lastOcclusion;
|
||||||
|
|
||||||
|
private const float OcclusionLerpRate = 4f;
|
||||||
|
private const float AlphaLerpRate = 4f;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_overlayManager.AddOverlay(new WeatherOverlay(_transform, EntityManager.System<SpriteSystem>(), this));
|
||||||
|
SubscribeLocalEvent<WeatherComponent, ComponentHandleState>(OnWeatherHandleState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_overlayManager.RemoveOverlay<WeatherOverlay>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Run(EntityUid uid, WeatherComponent component, WeatherPrototype weather, WeatherState state, float frameTime)
|
||||||
|
{
|
||||||
|
base.Run(uid, component, weather, state, frameTime);
|
||||||
|
|
||||||
|
var ent = _playerManager.LocalPlayer?.ControlledEntity;
|
||||||
|
|
||||||
|
if (ent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var mapUid = Transform(uid).MapUid;
|
||||||
|
var entXform = Transform(ent.Value);
|
||||||
|
|
||||||
|
// Maybe have the viewports manage this?
|
||||||
|
if (mapUid == null || entXform.MapUid != mapUid)
|
||||||
|
{
|
||||||
|
_lastOcclusion = 0f;
|
||||||
|
_lastAlpha = 0f;
|
||||||
|
component.Stream?.Stop();
|
||||||
|
component.Stream = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Timing.IsFirstTimePredicted || weather.Sound == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Stream ??= _audio.PlayGlobal(weather.Sound, Filter.Local(), true);
|
||||||
|
var volumeMod = MathF.Pow(10, weather.Sound.Params.Volume / 10f);
|
||||||
|
|
||||||
|
var stream = (AudioSystem.PlayingStream) component.Stream!;
|
||||||
|
var alpha = GetPercent(component, mapUid.Value, weather);
|
||||||
|
alpha = MathF.Pow(alpha, 2f) * volumeMod;
|
||||||
|
// TODO: Lerp this occlusion.
|
||||||
|
var occlusion = 0f;
|
||||||
|
// TODO: Fade-out needs to be slower
|
||||||
|
// TODO: HELPER PLZ
|
||||||
|
|
||||||
|
// Work out tiles nearby to determine volume.
|
||||||
|
if (TryComp<MapGridComponent>(entXform.GridUid, out var grid))
|
||||||
|
{
|
||||||
|
// Floodfill to the nearest tile and use that for audio.
|
||||||
|
var seed = grid.GetTileRef(entXform.Coordinates);
|
||||||
|
var frontier = new Queue<TileRef>();
|
||||||
|
frontier.Enqueue(seed);
|
||||||
|
// If we don't have a nearest node don't play any sound.
|
||||||
|
EntityCoordinates? nearestNode = null;
|
||||||
|
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||||
|
var visited = new HashSet<Vector2i>();
|
||||||
|
|
||||||
|
while (frontier.TryDequeue(out var node))
|
||||||
|
{
|
||||||
|
if (!visited.Add(node.GridIndices))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!CanWeatherAffect(grid, node, bodyQuery))
|
||||||
|
{
|
||||||
|
// Add neighbors
|
||||||
|
// TODO: Ideally we pick some deterministically random direction and use that
|
||||||
|
// We can't just do that naively here because it will flicker between nearby tiles.
|
||||||
|
for (var x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (var y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
if (Math.Abs(x) == 1 && Math.Abs(y) == 1 ||
|
||||||
|
x == 0 && y == 0 ||
|
||||||
|
(new Vector2(x, y) + node.GridIndices - seed.GridIndices).Length > 3)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
frontier.Enqueue(grid.GetTileRef(new Vector2i(x, y) + node.GridIndices));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nearestNode = new EntityCoordinates(entXform.GridUid.Value,
|
||||||
|
(Vector2) node.GridIndices + (grid.TileSize / 2f));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearestNode == null)
|
||||||
|
alpha = 0f;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var entPos = _transform.GetWorldPosition(entXform);
|
||||||
|
var sourceRelative = nearestNode.Value.ToMap(EntityManager).Position - entPos;
|
||||||
|
|
||||||
|
if (sourceRelative.LengthSquared > 1f)
|
||||||
|
{
|
||||||
|
occlusion = _physics.IntersectRayPenetration(entXform.MapID,
|
||||||
|
new CollisionRay(entPos, sourceRelative.Normalized, _audio.OcclusionCollisionMask),
|
||||||
|
sourceRelative.Length, stream.TrackingEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MathHelper.CloseTo(_lastOcclusion, occlusion, 0.01f))
|
||||||
|
_lastOcclusion = occlusion;
|
||||||
|
else
|
||||||
|
_lastOcclusion += (occlusion - _lastOcclusion) * OcclusionLerpRate * frameTime;
|
||||||
|
|
||||||
|
if (MathHelper.CloseTo(_lastAlpha, alpha, 0.01f))
|
||||||
|
_lastAlpha = alpha;
|
||||||
|
else
|
||||||
|
_lastAlpha += (alpha - _lastAlpha) * AlphaLerpRate * frameTime;
|
||||||
|
|
||||||
|
// Full volume if not on grid
|
||||||
|
Sawmill.Debug($"Setting alpha to {alpha:0.000}");
|
||||||
|
stream.Source.SetVolumeDirect(_lastAlpha);
|
||||||
|
stream.Source.SetOcclusion(_lastOcclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetPercent(WeatherComponent component, EntityUid mapUid, WeatherPrototype weatherProto)
|
||||||
|
{
|
||||||
|
var pauseTime = _metadata.GetPauseTime(mapUid);
|
||||||
|
var elapsed = Timing.CurTime - (component.StartTime + pauseTime);
|
||||||
|
var duration = component.Duration;
|
||||||
|
var remaining = duration - elapsed;
|
||||||
|
float alpha;
|
||||||
|
|
||||||
|
if (elapsed < weatherProto.StartupTime)
|
||||||
|
{
|
||||||
|
alpha = (float) (elapsed / weatherProto.StartupTime);
|
||||||
|
}
|
||||||
|
else if (remaining < weatherProto.ShutdownTime)
|
||||||
|
{
|
||||||
|
alpha = (float) (remaining / weatherProto.ShutdownTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alpha = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool SetState(EntityUid uid, WeatherComponent component, WeatherState state, WeatherPrototype prototype)
|
||||||
|
{
|
||||||
|
if (!base.SetState(uid, component, state, prototype))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!Timing.IsFirstTimePredicted)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// TODO: Fades
|
||||||
|
component.Stream?.Stop();
|
||||||
|
component.Stream = null;
|
||||||
|
component.Stream = _audio.PlayGlobal(prototype.Sound, Filter.Local(), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void EndWeather(WeatherComponent component)
|
||||||
|
{
|
||||||
|
_lastOcclusion = 0f;
|
||||||
|
_lastAlpha = 0f;
|
||||||
|
base.EndWeather(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWeatherHandleState(EntityUid uid, WeatherComponent component, ref ComponentHandleState args)
|
||||||
|
{
|
||||||
|
if (args.Current is not WeatherComponentState state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (component.Weather != state.Weather || !component.EndTime.Equals(state.EndTime) || !component.StartTime.Equals(state.StartTime))
|
||||||
|
{
|
||||||
|
EndWeather(component);
|
||||||
|
|
||||||
|
if (state.Weather != null)
|
||||||
|
StartWeather(component, ProtoMan.Index<WeatherPrototype>(state.Weather));
|
||||||
|
}
|
||||||
|
|
||||||
|
component.EndTime = state.EndTime;
|
||||||
|
component.StartTime = state.StartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
Content.Server/Weather/WeatherSystem.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Administration;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Weather;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
|
||||||
|
namespace Content.Server.Weather;
|
||||||
|
|
||||||
|
public sealed class WeatherSystem : SharedWeatherSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConsoleHost _console = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<WeatherComponent, ComponentGetState>(OnWeatherGetState);
|
||||||
|
_console.RegisterCommand("weather",
|
||||||
|
Loc.GetString("cmd-weather-desc"),
|
||||||
|
Loc.GetString("cmd-weather-help"),
|
||||||
|
WeatherTwo,
|
||||||
|
WeatherCompletion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWeatherGetState(EntityUid uid, WeatherComponent component, ref ComponentGetState args)
|
||||||
|
{
|
||||||
|
args.State = new WeatherComponentState()
|
||||||
|
{
|
||||||
|
Weather = component.Weather,
|
||||||
|
EndTime = component.EndTime,
|
||||||
|
StartTime = component.StartTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.Fun)]
|
||||||
|
private void WeatherTwo(IConsoleShell shell, string argstr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length != 2)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(args[0], out var mapInt))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapId = new MapId(mapInt);
|
||||||
|
|
||||||
|
if (!MapManager.MapExists(mapId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args[1].Equals("null"))
|
||||||
|
{
|
||||||
|
SetWeather(mapId, null);
|
||||||
|
}
|
||||||
|
else if (ProtoMan.TryIndex<WeatherPrototype>(args[1], out var weatherProto))
|
||||||
|
{
|
||||||
|
SetWeather(mapId, weatherProto);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shell.WriteError($"Unable to parse weather prototype");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletionResult WeatherCompletion(IConsoleShell shell, string[] args)
|
||||||
|
{
|
||||||
|
var options = new List<CompletionOption>();
|
||||||
|
|
||||||
|
if (args.Length == 1)
|
||||||
|
{
|
||||||
|
options.AddRange(EntityQuery<MapComponent>(true).Select(o => new CompletionOption(o.WorldMap.ToString())));
|
||||||
|
return CompletionResult.FromHintOptions(options, "Map Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = CompletionHelper.PrototypeIDs<WeatherPrototype>(true, ProtoMan);
|
||||||
|
return CompletionResult.FromHintOptions(a, Loc.GetString("cmd-weather-hint"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,11 @@ namespace Content.Shared.Maps
|
|||||||
[DataField("isSpace")] public bool IsSpace { get; private set; }
|
[DataField("isSpace")] public bool IsSpace { get; private set; }
|
||||||
[DataField("sturdy")] public bool Sturdy { get; private set; } = true;
|
[DataField("sturdy")] public bool Sturdy { get; private set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can weather affect this tile.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("weather")] public bool Weather = false;
|
||||||
|
|
||||||
public void AssignTileId(ushort id)
|
public void AssignTileId(ushort id)
|
||||||
{
|
{
|
||||||
TileId = id;
|
TileId = id;
|
||||||
|
|||||||
167
Content.Shared/Weather/SharedWeatherSystem.cs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Weather;
|
||||||
|
|
||||||
|
public abstract class SharedWeatherSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||||
|
[Dependency] protected readonly IMapManager MapManager = default!;
|
||||||
|
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||||
|
|
||||||
|
protected ISawmill Sawmill = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
Sawmill = Logger.GetSawmill("weather");
|
||||||
|
SubscribeLocalEvent<WeatherComponent, EntityUnpausedEvent>(OnWeatherUnpaused);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWeatherUnpaused(EntityUid uid, WeatherComponent component, ref EntityUnpausedEvent args)
|
||||||
|
{
|
||||||
|
component.EndTime += args.PausedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanWeatherAffect(MapGridComponent grid, TileRef tileRef, EntityQuery<PhysicsComponent> bodyQuery)
|
||||||
|
{
|
||||||
|
if (tileRef.Tile.IsEmpty)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var tileDef = (ContentTileDefinition) _tileDefManager[tileRef.Tile.TypeId];
|
||||||
|
|
||||||
|
if (!tileDef.Weather)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var anchoredEnts = grid.GetAnchoredEntitiesEnumerator(tileRef.GridIndices);
|
||||||
|
|
||||||
|
while (anchoredEnts.MoveNext(out var ent))
|
||||||
|
{
|
||||||
|
if (bodyQuery.TryGetComponent(ent, out var body) && body.CanCollide)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(float frameTime)
|
||||||
|
{
|
||||||
|
base.Update(frameTime);
|
||||||
|
|
||||||
|
if (!Timing.IsFirstTimePredicted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var curTime = Timing.CurTime;
|
||||||
|
|
||||||
|
foreach (var (comp, metadata) in EntityQuery<WeatherComponent, MetaDataComponent>())
|
||||||
|
{
|
||||||
|
if (comp.Weather == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var uid = comp.Owner;
|
||||||
|
var endTime = comp.EndTime;
|
||||||
|
|
||||||
|
// Ended
|
||||||
|
if (endTime < curTime)
|
||||||
|
{
|
||||||
|
EndWeather(comp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin messed up or the likes.
|
||||||
|
if (!ProtoMan.TryIndex<WeatherPrototype>(comp.Weather, out var weatherProto))
|
||||||
|
{
|
||||||
|
Sawmill.Error($"Unable to find weather prototype for {comp.Weather}, ending!");
|
||||||
|
EndWeather(comp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var remainingTime = endTime - curTime;
|
||||||
|
|
||||||
|
// Shutting down
|
||||||
|
if (remainingTime < weatherProto.ShutdownTime)
|
||||||
|
{
|
||||||
|
SetState(uid, comp, WeatherState.Ending, weatherProto);
|
||||||
|
}
|
||||||
|
// Starting up
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var startTime = comp.StartTime;
|
||||||
|
var elapsed = Timing.CurTime - startTime;
|
||||||
|
|
||||||
|
if (elapsed < weatherProto.StartupTime)
|
||||||
|
{
|
||||||
|
SetState(uid, comp, WeatherState.Starting, weatherProto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run whatever code we need.
|
||||||
|
Run(uid, comp, weatherProto, comp.State, frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetWeather(MapId mapId, WeatherPrototype? weather)
|
||||||
|
{
|
||||||
|
var weatherComp = EnsureComp<WeatherComponent>(MapManager.GetMapEntityId(mapId));
|
||||||
|
EndWeather(weatherComp);
|
||||||
|
|
||||||
|
if (weather != null)
|
||||||
|
StartWeather(weatherComp, weather);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run every tick when the weather is running.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Run(EntityUid uid, WeatherComponent component, WeatherPrototype weather, WeatherState state, float frameTime) {}
|
||||||
|
|
||||||
|
protected void StartWeather(WeatherComponent component, WeatherPrototype weather)
|
||||||
|
{
|
||||||
|
component.Weather = weather.ID;
|
||||||
|
// TODO: ENGINE PR
|
||||||
|
var duration = _random.NextDouble(weather.DurationMinimum.TotalSeconds, weather.DurationMaximum.TotalSeconds);
|
||||||
|
component.EndTime = Timing.CurTime + TimeSpan.FromSeconds(duration);
|
||||||
|
component.StartTime = Timing.CurTime;
|
||||||
|
DebugTools.Assert(component.State == WeatherState.Invalid);
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void EndWeather(WeatherComponent component)
|
||||||
|
{
|
||||||
|
component.Stream?.Stop();
|
||||||
|
component.Stream = null;
|
||||||
|
component.Weather = null;
|
||||||
|
component.StartTime = TimeSpan.Zero;
|
||||||
|
component.EndTime = TimeSpan.Zero;
|
||||||
|
component.State = WeatherState.Invalid;
|
||||||
|
Dirty(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool SetState(EntityUid uid, WeatherComponent component, WeatherState state, WeatherPrototype prototype)
|
||||||
|
{
|
||||||
|
if (component.State.Equals(state))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
component.State = state;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
protected sealed class WeatherComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public string? Weather;
|
||||||
|
public TimeSpan StartTime;
|
||||||
|
public TimeSpan EndTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Content.Shared/Weather/WeatherComponent.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
|
namespace Content.Shared.Weather;
|
||||||
|
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
public sealed class WeatherComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Currently running weather.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("weather")]
|
||||||
|
public string? Weather;
|
||||||
|
|
||||||
|
// now
|
||||||
|
public IPlayingAudioStream? Stream;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the weather started.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables, DataField("startTime")]
|
||||||
|
public TimeSpan StartTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the applied weather will end.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("endTime")]
|
||||||
|
public TimeSpan EndTime = TimeSpan.Zero;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public TimeSpan Duration => EndTime - StartTime;
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public WeatherState State = WeatherState.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WeatherState : byte
|
||||||
|
{
|
||||||
|
Invalid = 0,
|
||||||
|
Starting,
|
||||||
|
Running,
|
||||||
|
Ending,
|
||||||
|
}
|
||||||
43
Content.Shared/Weather/WeatherPrototype.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Weather;
|
||||||
|
|
||||||
|
[Prototype("weather")]
|
||||||
|
public sealed class WeatherPrototype : IPrototype
|
||||||
|
{
|
||||||
|
[IdDataFieldAttribute] public string ID { get; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum duration for the weather.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan DurationMinimum = TimeSpan.FromSeconds(120);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum duration for the weather.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public TimeSpan DurationMaximum = TimeSpan.FromSeconds(300);
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("startupTime")]
|
||||||
|
public TimeSpan StartupTime = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("endTime")]
|
||||||
|
public TimeSpan ShutdownTime = TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("sprite", required: true)]
|
||||||
|
public SpriteSpecifier Sprite = default!;
|
||||||
|
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("color")]
|
||||||
|
public Color? Color;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound to play on the affected areas.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables(VVAccess.ReadWrite), DataField("sound")]
|
||||||
|
public SoundSpecifier? Sound;
|
||||||
|
}
|
||||||
23
Resources/Audio/Effects/Weather/licenses.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
- files:
|
||||||
|
- rain.ogg
|
||||||
|
- snowstorm.ogg
|
||||||
|
- snowstorm_weak.ogg
|
||||||
|
- wind_2_1.ogg
|
||||||
|
- wind_2_2.ogg
|
||||||
|
- wind_3_1.ogg
|
||||||
|
- wind_4_1.ogg
|
||||||
|
- wind_4_2.ogg
|
||||||
|
- wind_5_1.ogg
|
||||||
|
license: "CC-BY-SA-3.0"
|
||||||
|
copyright: "Taken from https://github.com/Citadel-Station-13/Citadel-Station-13-RP/tree/28e11dee540e61b6c42fa293c1e1da087c1c2b0a and looped"
|
||||||
|
source: "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/tree/28e11dee540e61b6c42fa293c1e1da087c1c2b0a"
|
||||||
|
|
||||||
|
- files: ["rain2.wav"]
|
||||||
|
license: "Royalty free"
|
||||||
|
copyright: "Varazuvi - Natural Environments"
|
||||||
|
source: "Taken from Soniss.com - GDC"
|
||||||
|
|
||||||
|
- files: ["rain_heavy.ogg"]
|
||||||
|
license: "Royalty free"
|
||||||
|
copyright: "RATH - Rain Hard Loop 08"
|
||||||
|
source: "Taken from Soniss.com - GDC"
|
||||||
BIN
Resources/Audio/Effects/Weather/rain.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/rain2.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/rain_heavy.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/snowstorm.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/snowstorm_weak.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/wind_2_1.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/wind_2_2.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/wind_3_1.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/wind_4_1.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/wind_4_2.ogg
Normal file
BIN
Resources/Audio/Effects/Weather/wind_5_1.ogg
Normal file
3
Resources/Locale/en-US/weather/weather.ftl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
cmd-weather-desc = Sets the weather for the current map.
|
||||||
|
cmd-weather-help = weather <mapId> <prototype / null>
|
||||||
|
cmd-weather-hint = Weather prototype
|
||||||
@@ -1106,6 +1106,7 @@
|
|||||||
itemDrop: FloorTileItemSnow
|
itemDrop: FloorTileItemSnow
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorGrass
|
id: FloorGrass
|
||||||
@@ -1121,6 +1122,7 @@
|
|||||||
itemDrop: FloorTileItemGrass
|
itemDrop: FloorTileItemGrass
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorGrassJungle
|
id: FloorGrassJungle
|
||||||
@@ -1136,6 +1138,7 @@
|
|||||||
itemDrop: FloorTileItemGrassJungle
|
itemDrop: FloorTileItemGrassJungle
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorGrassDark
|
id: FloorGrassDark
|
||||||
@@ -1152,6 +1155,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorGrassLight
|
id: FloorGrassLight
|
||||||
@@ -1168,6 +1172,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorDirt
|
id: FloorDirt
|
||||||
@@ -1184,6 +1189,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
# Asteroid
|
# Asteroid
|
||||||
- type: tile
|
- type: tile
|
||||||
@@ -1199,6 +1205,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorAsteroidTile
|
id: FloorAsteroidTile
|
||||||
@@ -1213,6 +1220,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorAsteroidCoarseSand0
|
id: FloorAsteroidCoarseSand0
|
||||||
@@ -1229,6 +1237,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tileAlias
|
- type: tileAlias
|
||||||
id: FloorAsteroidCoarseSand1
|
id: FloorAsteroidCoarseSand1
|
||||||
@@ -1251,6 +1260,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorAsteroidIronsand1
|
id: FloorAsteroidIronsand1
|
||||||
@@ -1265,6 +1275,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorAsteroidIronsand2
|
id: FloorAsteroidIronsand2
|
||||||
@@ -1279,6 +1290,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorAsteroidIronsand3
|
id: FloorAsteroidIronsand3
|
||||||
@@ -1293,6 +1305,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
- type: tile
|
- type: tile
|
||||||
id: FloorAsteroidIronsand4
|
id: FloorAsteroidIronsand4
|
||||||
@@ -1307,6 +1320,7 @@
|
|||||||
friction: 0.30
|
friction: 0.30
|
||||||
thermalConductivity: 0.04
|
thermalConductivity: 0.04
|
||||||
heatCapacity: 10000
|
heatCapacity: 10000
|
||||||
|
weather: true
|
||||||
|
|
||||||
# Caves
|
# Caves
|
||||||
- type: tile
|
- type: tile
|
||||||
|
|||||||
138
Resources/Prototypes/weather.yml
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
- type: weather
|
||||||
|
id: Ashfall
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: ashfall
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: AshfallLight
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: ashfall_light
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: AshfallHeavy
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: ashfall_heavy
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: Fallout
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: fallout
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: Hail
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: hail
|
||||||
|
sound:
|
||||||
|
path:
|
||||||
|
/Audio/Effects/Weather/rain.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: Rain
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: rain
|
||||||
|
sound:
|
||||||
|
collection: Rain
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: soundCollection
|
||||||
|
id: Rain
|
||||||
|
files:
|
||||||
|
- /Audio/Effects/Weather/rain.ogg
|
||||||
|
- /Audio/Effects/Weather/rain2.ogg
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: Sandstorm
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: sandstorm
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: SandstormHeavy
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: sandstorm_heavy
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: SnowfallLight
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: snowfall_light
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: SnowfallMedium
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: snowfall_med
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: SnowfallHeavy
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: snowfall_heavy
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/snowstorm.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
id: Storm
|
||||||
|
sprite:
|
||||||
|
sprite: /Textures/Effects/weather.rsi
|
||||||
|
state: storm
|
||||||
|
sound:
|
||||||
|
path: /Audio/Effects/Weather/rain_heavy.ogg
|
||||||
|
params:
|
||||||
|
loop: true
|
||||||
|
volume: -6
|
||||||
BIN
Resources/Textures/Effects/weather.rsi/ashfall.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Resources/Textures/Effects/weather.rsi/ashfall_heavy.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
Resources/Textures/Effects/weather.rsi/ashfall_light.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Resources/Textures/Effects/weather.rsi/fallout.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Resources/Textures/Effects/weather.rsi/hail.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
319
Resources/Textures/Effects/weather.rsi/meta.json
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"copyright": "https://github.com/Citadel-Station-13/Citadel-Station-13-RP/tree/5781addfa1193c2811408f64d15176139395d670",
|
||||||
|
"license": "CC-BY-SA-3.0",
|
||||||
|
"size": {
|
||||||
|
"x": 32,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"states": [
|
||||||
|
{
|
||||||
|
"name": "snowfall_light",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "snowfall_med",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2,
|
||||||
|
0.2
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "snowfall_heavy",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rain",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02,
|
||||||
|
0.02
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "storm",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hail",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001,
|
||||||
|
0.030000001
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ashfall_heavy",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ashfall_light",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fallout",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ashfall",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sandstorm_heavy",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sandstorm",
|
||||||
|
"delays": [
|
||||||
|
[
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1,
|
||||||
|
0.1
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
Resources/Textures/Effects/weather.rsi/rain.png
Normal file
|
After Width: | Height: | Size: 981 B |
BIN
Resources/Textures/Effects/weather.rsi/sandstorm.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Resources/Textures/Effects/weather.rsi/sandstorm_heavy.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
Resources/Textures/Effects/weather.rsi/snowfall_heavy.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
Resources/Textures/Effects/weather.rsi/snowfall_light.png
Normal file
|
After Width: | Height: | Size: 653 B |
BIN
Resources/Textures/Effects/weather.rsi/snowfall_med.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Resources/Textures/Effects/weather.rsi/storm.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |