Weather effects (#12528)

Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
This commit is contained in:
metalgearsloth
2023-01-17 20:26:52 +11:00
committed by GitHub
parent 650c858d8a
commit 81a1c6d7de
37 changed files with 1267 additions and 0 deletions

View File

@@ -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>();

View File

@@ -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()
{ {

View 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);
}
}

View 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;
}
}

View 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"));
}
}

View File

@@ -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;

View 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;
}
}

View 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,
}

View 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;
}

View 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"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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

View File

@@ -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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View 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
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB