Weather effects (#12528)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
@@ -21,6 +21,7 @@ public sealed class ParallaxOverlay : Overlay
|
||||
|
||||
public ParallaxOverlay()
|
||||
{
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_parallax = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ParallaxSystem>();
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ public sealed class ParallaxSystem : SharedParallaxSystem
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
private const string Fallback = "Default";
|
||||
public const int ParallaxZIndex = 0;
|
||||
|
||||
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("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)
|
||||
{
|
||||
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
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorGrass
|
||||
@@ -1121,6 +1122,7 @@
|
||||
itemDrop: FloorTileItemGrass
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorGrassJungle
|
||||
@@ -1136,6 +1138,7 @@
|
||||
itemDrop: FloorTileItemGrassJungle
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorGrassDark
|
||||
@@ -1152,6 +1155,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorGrassLight
|
||||
@@ -1168,6 +1172,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorDirt
|
||||
@@ -1184,6 +1189,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
# Asteroid
|
||||
- type: tile
|
||||
@@ -1199,6 +1205,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorAsteroidTile
|
||||
@@ -1213,6 +1220,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorAsteroidCoarseSand0
|
||||
@@ -1229,6 +1237,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tileAlias
|
||||
id: FloorAsteroidCoarseSand1
|
||||
@@ -1251,6 +1260,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorAsteroidIronsand1
|
||||
@@ -1265,6 +1275,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorAsteroidIronsand2
|
||||
@@ -1279,6 +1290,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorAsteroidIronsand3
|
||||
@@ -1293,6 +1305,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
- type: tile
|
||||
id: FloorAsteroidIronsand4
|
||||
@@ -1307,6 +1320,7 @@
|
||||
friction: 0.30
|
||||
thermalConductivity: 0.04
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
|
||||
# Caves
|
||||
- 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 |