Weather entities (#41427)
* weather status effect entities * proto port + fixes * Update weather.yml * fix visuals * Update SharedWeatherSystem.cs * Update SharedWeatherSystem.cs * Update WeatherSystem.cs * Thanks Slarti, thats a much better * Update WeatherSystem.cs * Update StencilOverlay.Weather.cs * Update Content.Client/Overlays/StencilOverlay.Weather.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Update Content.Shared/StatusEffectNew/Components/StatusEffectComponent.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Update Content.Client/Overlays/StencilOverlay.Weather.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * Revise weather command help descriptions Updated weather command help texts for clarity. * Merge branch 'master' into ed-14-11-2025-weather-entities * Tayrtahn review apply * fixes and cleanup --------- Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
parent
e2a2f0c969
commit
4186c8caa2
|
|
@ -1,9 +1,9 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
|
|
@ -14,8 +14,7 @@ public sealed partial class StencilOverlay
|
|||
private void DrawWeather(
|
||||
in OverlayDrawArgs args,
|
||||
CachedResources res,
|
||||
WeatherPrototype weatherProto,
|
||||
float alpha,
|
||||
HashSet<Entity<WeatherStatusEffectComponent, StatusEffectComponent>> weathers,
|
||||
Matrix3x2 invMatrix)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
|
@ -27,47 +26,58 @@ public sealed partial class StencilOverlay
|
|||
// 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(res.Blep!, () =>
|
||||
{
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
_grids.Clear();
|
||||
|
||||
// idk if this is safe to cache in a field and clear sloth help
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref _grids);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
worldHandle.RenderInRenderTarget(res.Blep!,
|
||||
() =>
|
||||
{
|
||||
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
|
||||
var matty = Matrix3x2.Multiply(matrix, invMatrix);
|
||||
worldHandle.SetTransform(matty);
|
||||
_entManager.TryGetComponent(grid.Owner, out RoofComponent? roofComp);
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
_grids.Clear();
|
||||
|
||||
foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB))
|
||||
// idk if this is safe to cache in a field and clear sloth help
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref _grids);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect(grid.Owner, grid, tile, roofComp))
|
||||
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
|
||||
var matty = Matrix3x2.Multiply(matrix, invMatrix);
|
||||
worldHandle.SetTransform(matty);
|
||||
_entManager.TryGetComponent(grid.Owner, out RoofComponent? roofComp);
|
||||
|
||||
foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB))
|
||||
{
|
||||
continue;
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect((grid.Owner, grid, roofComp), tile))
|
||||
continue;
|
||||
|
||||
var gridTile = new Box2(tile.GridIndices * grid.Comp.TileSize,
|
||||
(tile.GridIndices + Vector2i.One) * grid.Comp.TileSize);
|
||||
|
||||
worldHandle.DrawRect(gridTile, Color.White);
|
||||
}
|
||||
|
||||
var gridTile = new Box2(tile.GridIndices * grid.Comp.TileSize,
|
||||
(tile.GridIndices + Vector2i.One) * grid.Comp.TileSize);
|
||||
|
||||
worldHandle.DrawRect(gridTile, Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
}, Color.Transparent);
|
||||
},
|
||||
Color.Transparent);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(_protoManager.Index(StencilMask).Instance());
|
||||
worldHandle.DrawTextureRect(res.Blep!.Texture, worldBounds);
|
||||
var curTime = _timing.RealTime;
|
||||
var sprite = _sprite.GetFrame(weatherProto.Sprite, curTime);
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index(StencilDraw).Instance());
|
||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha));
|
||||
|
||||
foreach (var (uid, weather, status) in weathers)
|
||||
{
|
||||
var alpha = _weather.GetWeatherPercent((uid, status));
|
||||
var sprite = _sprite.GetFrame(weather.Sprite, curTime);
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index(StencilDraw).Instance());
|
||||
_parallax.DrawParallax(worldHandle,
|
||||
worldAABB,
|
||||
sprite,
|
||||
curTime,
|
||||
position,
|
||||
weather.Scrolling ?? Vector2.Zero,
|
||||
modulate: (weather.Color ?? Color.White).WithAlpha(alpha));
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(null);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using Content.Client.Graphics;
|
|||
using Content.Client.Parallax;
|
||||
using Content.Client.Weather;
|
||||
using Content.Shared.Salvage;
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
|
|
@ -32,6 +34,8 @@ public sealed partial class StencilOverlay : Overlay
|
|||
private readonly SharedMapSystem _map;
|
||||
private readonly SpriteSystem _sprite;
|
||||
private readonly WeatherSystem _weather;
|
||||
private readonly StatusEffectsSystem _statusEffects;
|
||||
private HashSet<Entity<WeatherStatusEffectComponent, StatusEffectComponent>>? _weatherSet = new();
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
|
||||
|
||||
|
|
@ -39,7 +43,7 @@ public sealed partial class StencilOverlay : Overlay
|
|||
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SharedMapSystem map, SpriteSystem sprite, WeatherSystem weather)
|
||||
public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SharedMapSystem map, SpriteSystem sprite, WeatherSystem weather, StatusEffectsSystem statusEffects)
|
||||
{
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex + 1;
|
||||
_parallax = parallax;
|
||||
|
|
@ -47,6 +51,7 @@ public sealed partial class StencilOverlay : Overlay
|
|||
_map = map;
|
||||
_sprite = sprite;
|
||||
_weather = weather;
|
||||
_statusEffects = statusEffects;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _protoManager.Index(CircleShader).InstanceUnique();
|
||||
}
|
||||
|
|
@ -64,22 +69,11 @@ public sealed partial class StencilOverlay : Overlay
|
|||
res.Blep = _clyde.CreateRenderTarget(args.Viewport.Size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "weather-stencil");
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<WeatherComponent>(mapUid, out var comp))
|
||||
{
|
||||
foreach (var (proto, weather) in comp.Weather)
|
||||
{
|
||||
if (!_protoManager.Resolve<WeatherPrototype>(proto, out var weatherProto))
|
||||
continue;
|
||||
|
||||
var alpha = _weather.GetPercent(weather, mapUid);
|
||||
DrawWeather(args, res, weatherProto, alpha, invMatrix);
|
||||
}
|
||||
}
|
||||
if (_statusEffects.TryEffectsWithComp(mapUid, out _weatherSet))
|
||||
DrawWeather(args, res, _weatherSet, invMatrix);
|
||||
|
||||
if (_entManager.TryGetComponent<RestrictedRangeComponent>(mapUid, out var restrictedRangeComponent))
|
||||
{
|
||||
DrawRestrictedRange(args, res, restrictedRangeComponent, invMatrix);
|
||||
}
|
||||
|
||||
args.WorldHandle.UseShader(null);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Client.Parallax;
|
||||
using Content.Client.Weather;
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
|
|
@ -13,11 +14,12 @@ public sealed class StencilOverlaySystem : EntitySystem
|
|||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
[Dependency] private readonly WeatherSystem _weather = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _status = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _map, _sprite, _weather));
|
||||
_overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _map, _sprite, _weather, _status));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
|
||||
|
||||
namespace Content.Client.Weather;
|
||||
|
||||
|
|
@ -20,163 +20,121 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
|||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private EntityQuery<AudioComponent> _audioQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<RoofComponent> _roofQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<WeatherComponent, ComponentHandleState>(OnWeatherHandleState);
|
||||
|
||||
SubscribeLocalEvent<WeatherStatusEffectComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
|
||||
_audioQuery = GetEntityQuery<AudioComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_roofQuery = GetEntityQuery<RoofComponent>();
|
||||
}
|
||||
|
||||
protected override void Run(EntityUid uid, WeatherData weather, WeatherPrototype weatherProto, float frameTime)
|
||||
private void OnComponentShutdown(Entity<WeatherStatusEffectComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
base.Run(uid, weather, weatherProto, frameTime);
|
||||
|
||||
var ent = _playerManager.LocalEntity;
|
||||
|
||||
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)
|
||||
{
|
||||
weather.Stream = _audio.Stop(weather.Stream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Timing.IsFirstTimePredicted || weatherProto.Sound == null)
|
||||
return;
|
||||
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
|
||||
|
||||
if (!TryComp(weather.Stream, out AudioComponent? comp))
|
||||
return;
|
||||
|
||||
var occlusion = 0f;
|
||||
|
||||
// Work out tiles nearby to determine volume.
|
||||
if (TryComp<MapGridComponent>(entXform.GridUid, out var grid))
|
||||
{
|
||||
TryComp(entXform.GridUid, out RoofComponent? roofComp);
|
||||
var gridId = entXform.GridUid.Value;
|
||||
// FloodFill to the nearest tile and use that for audio.
|
||||
var seed = _mapSystem.GetTileRef(gridId, grid, 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 visited = new HashSet<Vector2i>();
|
||||
|
||||
while (frontier.TryDequeue(out var node))
|
||||
{
|
||||
if (!visited.Add(node.GridIndices))
|
||||
continue;
|
||||
|
||||
if (!CanWeatherAffect(entXform.GridUid.Value, grid, node, roofComp))
|
||||
{
|
||||
// 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(_mapSystem.GetTileRef(gridId, grid, new Vector2i(x, y) + node.GridIndices));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
nearestNode = new EntityCoordinates(entXform.GridUid.Value,
|
||||
node.GridIndices + grid.TileSizeHalfVector);
|
||||
break;
|
||||
}
|
||||
|
||||
// Get occlusion to the targeted node if it exists, otherwise set a default occlusion.
|
||||
if (nearestNode != null)
|
||||
{
|
||||
var entPos = _transform.GetMapCoordinates(entXform);
|
||||
var nodePosition = _transform.ToMapCoordinates(nearestNode.Value).Position;
|
||||
var delta = nodePosition - entPos.Position;
|
||||
var distance = delta.Length();
|
||||
occlusion = _audio.GetOcclusion(entPos, delta, distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
occlusion = 3f;
|
||||
}
|
||||
}
|
||||
|
||||
var alpha = GetPercent(weather, uid);
|
||||
alpha *= SharedAudioSystem.VolumeToGain(weatherProto.Sound.Params.Volume);
|
||||
_audio.SetGain(weather.Stream, alpha, comp);
|
||||
comp.Occlusion = occlusion;
|
||||
ent.Comp.Stream = _audio.Stop(ent.Comp.Stream);
|
||||
}
|
||||
|
||||
protected override bool SetState(EntityUid uid, WeatherState state, WeatherComponent comp, WeatherData weather, WeatherPrototype weatherProto)
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!base.SetState(uid, state, comp, weather, weatherProto))
|
||||
return false;
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
return true;
|
||||
|
||||
// Begin DeltaV Additions: Prevent hearing weather in the lobby
|
||||
if (_playerManager.LocalEntity is not {} ent)
|
||||
return false;
|
||||
|
||||
var map = Transform(uid).MapUid;
|
||||
var entMap = Transform(ent).MapUid;
|
||||
|
||||
if (map == null || entMap != map)
|
||||
{
|
||||
weather.Stream = _audio.Stop(weather.Stream);
|
||||
return false;
|
||||
}
|
||||
// End DeltaV Additions
|
||||
|
||||
// TODO: Fades (properly)
|
||||
weather.Stream = _audio.Stop(weather.Stream);
|
||||
weather.Stream = _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnWeatherHandleState(EntityUid uid, WeatherComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not WeatherComponentState state)
|
||||
return;
|
||||
|
||||
foreach (var (proto, weather) in component.Weather)
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
var playerXform = Transform(player.Value);
|
||||
|
||||
var query = EntityQueryEnumerator<WeatherStatusEffectComponent, StatusEffectComponent>();
|
||||
while (query.MoveNext(out var uid, out var weather, out var status))
|
||||
{
|
||||
// End existing one
|
||||
if (!state.Weather.TryGetValue(proto, out var stateData))
|
||||
if (weather.Sound == null || status.AppliedTo != playerXform.MapUid)
|
||||
{
|
||||
EndWeather(uid, component, proto);
|
||||
continue;
|
||||
weather.Stream = _audio.Stop(weather.Stream);
|
||||
return;
|
||||
}
|
||||
|
||||
// Data update?
|
||||
weather.StartTime = stateData.StartTime;
|
||||
weather.EndTime = stateData.EndTime;
|
||||
weather.State = stateData.State;
|
||||
}
|
||||
weather.Stream ??= _audio.PlayGlobal(weather.Sound, Filter.Local(), true)?.Entity;
|
||||
|
||||
foreach (var (proto, weather) in state.Weather)
|
||||
{
|
||||
if (component.Weather.ContainsKey(proto))
|
||||
continue;
|
||||
if (!_audioQuery.TryComp(weather.Stream, out var audio))
|
||||
return;
|
||||
|
||||
// New weather
|
||||
StartWeather(uid, component, ProtoMan.Index<WeatherPrototype>(proto), weather.EndTime);
|
||||
var occlusion = 0f;
|
||||
|
||||
// Work out tiles nearby to determine volume.
|
||||
if (_gridQuery.TryComp(playerXform.GridUid, out var grid))
|
||||
{
|
||||
_roofQuery.TryComp(playerXform.GridUid, out var roofComp);
|
||||
var gridId = playerXform.GridUid.Value;
|
||||
// FloodFill to the nearest tile and use that for audio.
|
||||
var seed = _mapSystem.GetTileRef(gridId, grid, playerXform.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 visited = new HashSet<Vector2i>();
|
||||
|
||||
while (frontier.TryDequeue(out var node))
|
||||
{
|
||||
if (!visited.Add(node.GridIndices))
|
||||
continue;
|
||||
|
||||
if (!CanWeatherAffect((playerXform.GridUid.Value, grid, roofComp), node))
|
||||
{
|
||||
// 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(_mapSystem.GetTileRef(gridId, grid, new Vector2i(x, y) + node.GridIndices));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
nearestNode = new EntityCoordinates(playerXform.GridUid.Value,
|
||||
node.GridIndices + grid.TileSizeHalfVector);
|
||||
break;
|
||||
}
|
||||
|
||||
// Get occlusion to the targeted node if it exists, otherwise set a default occlusion.
|
||||
if (nearestNode != null)
|
||||
{
|
||||
var entPos = _transform.GetMapCoordinates(playerXform);
|
||||
var nodePosition = _transform.ToMapCoordinates(nearestNode.Value).Position;
|
||||
var delta = nodePosition - entPos.Position;
|
||||
var distance = delta.Length();
|
||||
occlusion = _audio.GetOcclusion(entPos, delta, distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
occlusion = 3f;
|
||||
}
|
||||
}
|
||||
|
||||
var alpha = GetWeatherPercent((uid, status));
|
||||
alpha *= SharedAudioSystem.VolumeToGain(weather.Sound.Params.Volume);
|
||||
_audio.SetGain(weather.Stream, alpha, audio);
|
||||
audio.Occlusion = occlusion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Weather.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Add specific weather to map.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class WeatherAddCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly WeatherSystem _weather = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
|
||||
public override string Command => "weatheradd";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-no-arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
//MapId parse
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
return;
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-wrong-map", ("id", mapId.ToString())));
|
||||
return;
|
||||
}
|
||||
|
||||
//Weather proto parse
|
||||
EntProtoId weatherProto = args[1];
|
||||
if (!_proto.TryIndex(weatherProto, out _))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-unknown-proto"));
|
||||
return;
|
||||
}
|
||||
|
||||
//Time parsing
|
||||
TimeSpan? duration = null;
|
||||
if (args.Length == 3)
|
||||
{
|
||||
if (int.TryParse(args[2], out var durationInt))
|
||||
duration = TimeSpan.FromSeconds(durationInt);
|
||||
else
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-wrong-time"));
|
||||
}
|
||||
|
||||
_weather.TryAddWeather(mapId, weatherProto, out _, duration);
|
||||
}
|
||||
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), Loc.GetString("cmd-weather-hint-map-id"));
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
var opts = new List<CompletionOption>();
|
||||
foreach (var proto in _proto.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (!proto.HasComponent<WeatherStatusEffectComponent>(_compFactory))
|
||||
continue;
|
||||
|
||||
opts.Add(new CompletionOption(proto.ID, proto.Name));
|
||||
}
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-weather-hint-prototype"));
|
||||
}
|
||||
|
||||
if (args.Length == 3)
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-weather-hint-time"));
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Weather.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Remove specific weather from map.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class WeatherRemoveCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly WeatherSystem _weather = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
|
||||
public override string Command => "weatherremove";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-no-arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
//MapId parse
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
return;
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-wrong-map", ("id", mapId.ToString())));
|
||||
return;
|
||||
}
|
||||
|
||||
//Weather proto parse
|
||||
EntProtoId weatherProto = args[1];
|
||||
if (!_proto.TryIndex(weatherProto, out _))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-unknown-proto"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_weather.HasWeather(mapId, weatherProto))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-no-weather"));
|
||||
return;
|
||||
}
|
||||
|
||||
_weather.TryRemoveWeather(mapId, weatherProto);
|
||||
}
|
||||
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), Loc.GetString("cmd-weather-hint-map-id"));
|
||||
|
||||
if (args.Length == 2) //TODO: dont show ALL weathers here, only weathers applied to selected map
|
||||
{
|
||||
var opts = new List<CompletionOption>();
|
||||
foreach (var proto in _proto.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (!proto.HasComponent<WeatherStatusEffectComponent>(_compFactory))
|
||||
continue;
|
||||
|
||||
opts.Add(new CompletionOption(proto.ID, proto.Name));
|
||||
}
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-weather-hint-prototype"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Prototypes;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Weather.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Removes all weather except the specified one. If the specified weather does not exist on the map, it adds it.
|
||||
/// </summary>
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public sealed class WeatherSetCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly WeatherSystem _weather = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
|
||||
public override string Command => "weatherset";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-no-arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
//MapId parse
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
return;
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-wrong-map", ("id", mapId.ToString())));
|
||||
return;
|
||||
}
|
||||
|
||||
//Weather proto parse
|
||||
EntProtoId? weatherProto = args[1];
|
||||
if (args[1] == "null")
|
||||
weatherProto = null;
|
||||
else if (!_proto.TryIndex(weatherProto, out _))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-unknown-proto"));
|
||||
return;
|
||||
}
|
||||
|
||||
//Time parsing
|
||||
TimeSpan? duration = null;
|
||||
if (args.Length == 3)
|
||||
{
|
||||
if (int.TryParse(args[2], out var durationInt))
|
||||
duration = TimeSpan.FromSeconds(durationInt);
|
||||
else
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-wrong-time"));
|
||||
}
|
||||
|
||||
_weather.TrySetWeather(mapId, weatherProto, out _, duration);
|
||||
}
|
||||
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), Loc.GetString("cmd-weather-hint-map-id"));
|
||||
|
||||
if (args.Length == 2)
|
||||
{
|
||||
var opts = new List<CompletionOption>();
|
||||
foreach (var proto in _proto.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (!proto.HasComponent<WeatherStatusEffectComponent>(_compFactory))
|
||||
continue;
|
||||
|
||||
opts.Add(new CompletionOption(proto.ID, proto.Name));
|
||||
}
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-weather-hint-prototype"));
|
||||
}
|
||||
|
||||
if (args.Length == 3)
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-weather-hint-time"));
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +1,30 @@
|
|||
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 System.Linq;
|
||||
using Robust.Server.GameStates;
|
||||
|
||||
namespace Content.Server.Weather;
|
||||
|
||||
public sealed class WeatherSystem : SharedWeatherSystem
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
//I dont really like to PVS override weather entities, but map status effect containers dont PVS-ing out of the box
|
||||
[Dependency] private readonly PvsOverrideSystem _pvs = 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);
|
||||
|
||||
SubscribeLocalEvent<WeatherStatusEffectComponent, ComponentInit>(OnCompInit);
|
||||
SubscribeLocalEvent<WeatherStatusEffectComponent, ComponentShutdown>(OnCompShutdown);
|
||||
}
|
||||
|
||||
private void OnWeatherGetState(EntityUid uid, WeatherComponent component, ref ComponentGetState args)
|
||||
private void OnCompInit(Entity<WeatherStatusEffectComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
args.State = new WeatherComponentState(component.Weather);
|
||||
// The map entitiy itself is networked by PVS if the player is on that map but not anything inside a container,
|
||||
// So we need to add an overridce to make sure the client sees it.
|
||||
_pvs.AddGlobalOverride(ent);
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void WeatherTwo(IConsoleShell shell, string argStr, string[] args)
|
||||
private void OnCompShutdown(Entity<WeatherStatusEffectComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-no-arguments"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
return;
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
|
||||
if (!_mapSystem.MapExists(mapId))
|
||||
return;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
return;
|
||||
|
||||
var weatherComp = EnsureComp<WeatherComponent>(mapUid.Value);
|
||||
|
||||
//Weather Proto parsing
|
||||
WeatherPrototype? weather = null;
|
||||
if (!args[1].Equals("null"))
|
||||
{
|
||||
if (!ProtoMan.TryIndex(args[1], out weather))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-unknown-proto"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Time parsing
|
||||
TimeSpan? endTime = null;
|
||||
if (args.Length == 3)
|
||||
{
|
||||
var curTime = Timing.CurTime;
|
||||
if (int.TryParse(args[2], out var durationInt))
|
||||
{
|
||||
endTime = curTime + TimeSpan.FromSeconds(durationInt);
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-weather-error-wrong-time"));
|
||||
}
|
||||
}
|
||||
|
||||
SetWeather(mapId, weather, endTime);
|
||||
}
|
||||
|
||||
private CompletionResult WeatherCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), "Map Id");
|
||||
|
||||
var a = CompletionHelper.PrototypeIDs<WeatherPrototype>(true, ProtoMan);
|
||||
var b = a.Concat(new[] { new CompletionOption("null", Loc.GetString("cmd-weather-null")) });
|
||||
return CompletionResult.FromHintOptions(b, Loc.GetString("cmd-weather-hint"));
|
||||
_pvs.RemoveGlobalOverride(ent);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ public sealed partial class SalvageWeatherMod : IPrototype, IBiomeSpecificMod
|
|||
public List<ProtoId<SalvageBiomeModPrototype>>? Biomes { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Weather prototype to use on the planet.
|
||||
/// Weather status effect prototype to use on the planet.
|
||||
/// </summary>
|
||||
[DataField("weather", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<WeatherPrototype>))]
|
||||
public string WeatherPrototype = string.Empty;
|
||||
[DataField("weather", required: true)]
|
||||
public EntProtoId WeatherPrototype = string.Empty;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,4 +50,10 @@ public sealed partial class StatusEffectComponent : Component
|
|||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
/// <summary>
|
||||
/// QoL function, returns total duration of this status effect.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public TimeSpan Duration => EndEffectTime == null ? TimeSpan.MaxValue : EndEffectTime.Value - StartEffectTime;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ public sealed partial class StatusEffectsSystem : EntitySystem
|
|||
|
||||
var endTime = delay == null ? _timing.CurTime + duration : _timing.CurTime + delay + duration;
|
||||
SetStatusEffectEndTime((effect.Value, effectComp), endTime);
|
||||
var startTime = delay == null ? TimeSpan.Zero : _timing.CurTime + delay.Value;
|
||||
var startTime = delay == null ? _timing.CurTime : _timing.CurTime + delay.Value;
|
||||
SetStatusEffectStartTime(effect.Value, startTime);
|
||||
|
||||
TryApplyStatusEffect((statusEffect.Value, effectComp));
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ namespace Content.Shared.Trigger.Components.Effects;
|
|||
public sealed partial class WeatherOnTriggerComponent : BaseXOnTriggerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Weather type. Null to clear the weather.
|
||||
/// Weather status effect proto. Null to clear the weather.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public ProtoId<WeatherPrototype>? Weather;
|
||||
public EntProtoId? Weather;
|
||||
|
||||
/// <summary>
|
||||
/// How long the weather should last. Null for forever.
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ public sealed class WeatherTriggerSystem : XOnTriggerSystem<WeatherOnTriggerComp
|
|||
|
||||
if (ent.Comp.Weather == null) //Clear weather if nothing is set
|
||||
{
|
||||
_weather.SetWeather(xform.MapID, null, null);
|
||||
_weather.TrySetWeather(xform.MapID, null, out _);
|
||||
return;
|
||||
}
|
||||
|
||||
var endTime = ent.Comp.Duration == null ? null : ent.Comp.Duration + _timing.CurTime;
|
||||
|
||||
if (_prototypeManager.Resolve(ent.Comp.Weather, out var weatherPrototype))
|
||||
_weather.SetWeather(xform.MapID, weatherPrototype, endTime);
|
||||
_weather.TrySetWeather(xform.MapID, weatherPrototype, out _, endTime);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.StatusEffectNew;
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Weather;
|
||||
|
|
@ -14,236 +16,193 @@ public abstract class SharedWeatherSystem : EntitySystem
|
|||
{
|
||||
[Dependency] protected readonly IGameTiming Timing = default!;
|
||||
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
|
||||
[Dependency] protected readonly SharedAudioSystem Audio = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedRoofSystem _roof = default!;
|
||||
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
|
||||
|
||||
private EntityQuery<BlockWeatherComponent> _blockQuery;
|
||||
private EntityQuery<WeatherStatusEffectComponent> _weatherQuery;
|
||||
|
||||
public static readonly TimeSpan StartupTime = TimeSpan.FromSeconds(15);
|
||||
public static readonly TimeSpan ShutdownTime = TimeSpan.FromSeconds(15);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_blockQuery = GetEntityQuery<BlockWeatherComponent>();
|
||||
SubscribeLocalEvent<WeatherComponent, EntityUnpausedEvent>(OnWeatherUnpaused);
|
||||
_weatherQuery = GetEntityQuery<WeatherStatusEffectComponent>();
|
||||
}
|
||||
|
||||
private void OnWeatherUnpaused(EntityUid uid, WeatherComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
foreach (var weather in component.Weather.Values)
|
||||
{
|
||||
weather.StartTime += args.PausedTime;
|
||||
|
||||
if (weather.EndTime != null)
|
||||
weather.EndTime = weather.EndTime.Value + args.PausedTime;
|
||||
}
|
||||
component.NextUpdate += args.PausedTime; // DeltaV
|
||||
}
|
||||
|
||||
public bool CanWeatherAffect(EntityUid uid, MapGridComponent grid, TileRef tileRef, RoofComponent? roofComp = null)
|
||||
public bool CanWeatherAffect(Entity<MapGridComponent?, RoofComponent?> ent, TileRef tileRef)
|
||||
{
|
||||
if (tileRef.Tile.IsEmpty)
|
||||
return true;
|
||||
|
||||
if (Resolve(uid, ref roofComp, false) && _roof.IsRooved((uid, grid, roofComp), tileRef.GridIndices))
|
||||
if (!Resolve(ent, ref ent.Comp1))
|
||||
return false;
|
||||
|
||||
var tileDef = (ContentTileDefinition) _tileDefManager[tileRef.Tile.TypeId];
|
||||
if (Resolve(ent, ref ent.Comp2, false) && _roof.IsRooved((ent, ent.Comp1, ent.Comp2), tileRef.GridIndices))
|
||||
return false;
|
||||
|
||||
var tileDef = (ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
if (!tileDef.Weather)
|
||||
return false;
|
||||
|
||||
var anchoredEntities = _mapSystem.GetAnchoredEntitiesEnumerator(uid, grid, tileRef.GridIndices);
|
||||
var anchoredEntities = _mapSystem.GetAnchoredEntitiesEnumerator(ent, ent.Comp1, tileRef.GridIndices);
|
||||
|
||||
while (anchoredEntities.MoveNext(out var ent))
|
||||
while (anchoredEntities.MoveNext(out var anchored))
|
||||
{
|
||||
if (_blockQuery.HasComponent(ent.Value))
|
||||
if (_blockQuery.HasComponent(anchored.Value))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public float GetPercent(WeatherData component, EntityUid mapUid)
|
||||
/// <summary>
|
||||
/// Calculates the current “strength” of the specified weather based on the duration of the status effect.
|
||||
/// Between 0 and 1.
|
||||
/// </summary>
|
||||
public float GetWeatherPercent(Entity<StatusEffectComponent> ent)
|
||||
{
|
||||
var pauseTime = _metadata.GetPauseTime(mapUid);
|
||||
var elapsed = Timing.CurTime - (component.StartTime + pauseTime);
|
||||
var duration = component.Duration;
|
||||
var elapsed = Timing.CurTime - ent.Comp.StartEffectTime;
|
||||
var duration = ent.Comp.Duration;
|
||||
var remaining = duration - elapsed;
|
||||
float alpha;
|
||||
|
||||
if (remaining < WeatherComponent.ShutdownTime)
|
||||
{
|
||||
alpha = (float) (remaining / WeatherComponent.ShutdownTime);
|
||||
}
|
||||
else if (elapsed < WeatherComponent.StartupTime)
|
||||
{
|
||||
alpha = (float) (elapsed / WeatherComponent.StartupTime);
|
||||
}
|
||||
if (remaining < ShutdownTime)
|
||||
return (float)(remaining / ShutdownTime);
|
||||
else if (elapsed < StartupTime)
|
||||
return (float)(elapsed / StartupTime);
|
||||
else
|
||||
{
|
||||
alpha = 1f;
|
||||
}
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!Timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var curTime = Timing.CurTime;
|
||||
|
||||
var query = EntityQueryEnumerator<WeatherComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.Weather.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (var (proto, weather) in comp.Weather)
|
||||
{
|
||||
var endTime = weather.EndTime;
|
||||
|
||||
// Ended
|
||||
if (endTime != null && endTime < curTime)
|
||||
{
|
||||
EndWeather(uid, comp, proto);
|
||||
continue;
|
||||
}
|
||||
|
||||
var remainingTime = endTime - curTime;
|
||||
|
||||
// Admin messed up or the likes.
|
||||
if (!ProtoMan.TryIndex<WeatherPrototype>(proto, out var weatherProto))
|
||||
{
|
||||
Log.Error($"Unable to find weather prototype for {comp.Weather}, ending!");
|
||||
EndWeather(uid, comp, proto);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Shutting down
|
||||
if (endTime != null && remainingTime < WeatherComponent.ShutdownTime)
|
||||
{
|
||||
SetState(uid, WeatherState.Ending, comp, weather, weatherProto);
|
||||
}
|
||||
// Starting up
|
||||
else
|
||||
{
|
||||
var startTime = weather.StartTime;
|
||||
var elapsed = Timing.CurTime - startTime;
|
||||
|
||||
if (elapsed < WeatherComponent.StartupTime)
|
||||
{
|
||||
SetState(uid, WeatherState.Starting, comp, weather, weatherProto);
|
||||
}
|
||||
else // DeltaV: Set state to Running when it finishes the starting time
|
||||
{
|
||||
SetState(uid, WeatherState.Running, comp, weather, weatherProto);
|
||||
}
|
||||
}
|
||||
|
||||
// Run whatever code we need.
|
||||
Run(uid, weather, weatherProto, frameTime);
|
||||
}
|
||||
}
|
||||
return 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuts down all existing weather and starts the new one if applicable.
|
||||
/// Attempts to add a new weather status effect to the specified map.
|
||||
/// Does not remove or replace any other existing weather effects on the map.
|
||||
/// If the specified weather effect already exists, its duration will be overridden.
|
||||
/// </summary>
|
||||
public void SetWeather(MapId mapId, WeatherPrototype? proto, TimeSpan? endTime)
|
||||
/// <param name="mapId">The <see cref="MapId"/> of the target map to apply the weather effect to.</param>
|
||||
/// <param name="weatherProto">The prototype ID (<see cref="EntProtoId"/>) of the weather status effect to add.</param>
|
||||
/// <param name="weatherEnt">When this method returns, contains the <see cref="EntityUid"/> of the weather entity if the operation succeeded; otherwise, <c>null</c>.</param>
|
||||
/// <param name="duration">Optional. The duration for which the weather should exist on the map. If <c>null</c>, the weather will persist indefinitely.</param>
|
||||
/// <returns><c>true</c> if the weather was successfully added or updated; otherwise, <c>false</c>.</returns>
|
||||
public bool TryAddWeather(MapId mapId, EntProtoId weatherProto, [NotNullWhen(true)] out EntityUid? weatherEnt, TimeSpan? duration = null)
|
||||
{
|
||||
weatherEnt = null;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
return;
|
||||
|
||||
var weatherComp = EnsureComp<WeatherComponent>(mapUid.Value);
|
||||
|
||||
foreach (var (eProto, weather) in weatherComp.Weather)
|
||||
{
|
||||
// if we turn off the weather, we don't want endTime = null
|
||||
if (proto == null)
|
||||
endTime ??= Timing.CurTime + WeatherComponent.ShutdownTime;
|
||||
|
||||
// Reset cooldown if it's an existing one.
|
||||
if (proto is not null && eProto == proto.ID)
|
||||
{
|
||||
weather.EndTime = endTime;
|
||||
if (weather.State == WeatherState.Ending)
|
||||
weather.State = WeatherState.Running;
|
||||
|
||||
Dirty(mapUid.Value, weatherComp);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Speedrun
|
||||
var end = Timing.CurTime + WeatherComponent.ShutdownTime;
|
||||
|
||||
if (weather.EndTime == null || weather.EndTime > end)
|
||||
{
|
||||
weather.EndTime = end;
|
||||
Dirty(mapUid.Value, weatherComp);
|
||||
}
|
||||
}
|
||||
|
||||
if (proto != null)
|
||||
StartWeather(mapUid.Value, weatherComp, proto, endTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run every tick when the weather is running.
|
||||
/// </summary>
|
||||
protected virtual void Run(EntityUid uid, WeatherData weather, WeatherPrototype weatherProto, float frameTime) { }
|
||||
|
||||
protected void StartWeather(EntityUid uid, WeatherComponent component, WeatherPrototype weather, TimeSpan? endTime)
|
||||
{
|
||||
if (component.Weather.ContainsKey(weather.ID))
|
||||
return;
|
||||
|
||||
var data = new WeatherData()
|
||||
{
|
||||
StartTime = Timing.CurTime,
|
||||
EndTime = endTime,
|
||||
};
|
||||
|
||||
component.Weather.Add(weather.ID, data);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
protected virtual void EndWeather(EntityUid uid, WeatherComponent component, string proto)
|
||||
{
|
||||
if (!component.Weather.TryGetValue(proto, out var data))
|
||||
return;
|
||||
|
||||
_audio.Stop(data.Stream);
|
||||
data.Stream = null;
|
||||
component.Weather.Remove(proto);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
protected virtual bool SetState(EntityUid uid, WeatherState state, WeatherComponent component, WeatherData weather, WeatherPrototype weatherProto)
|
||||
{
|
||||
if (weather.State.Equals(state))
|
||||
return false;
|
||||
|
||||
weather.State = state;
|
||||
Dirty(uid, component);
|
||||
return true;
|
||||
return TryAddWeather(mapUid.Value, weatherProto, out weatherEnt, duration);
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class WeatherComponentState : ComponentState
|
||||
/// <summary>
|
||||
/// Adds a new weather to a map. Does not remove other existing weathers. If this type of weather already exists, it simply overrides its duration.
|
||||
/// </summary>
|
||||
/// <param name="mapUid">Target map entity</param>
|
||||
/// <param name="weatherProto">EntProtoId of weather status effect</param>
|
||||
/// <param name="weatherEnt">When this method returns, contains the <see cref="EntityUid"/> of the weather entity if the operation succeeded; otherwise, <c>null</c>.</param>
|
||||
/// <param name="duration">How long this weather should exist on the map? If null - infinite duration</param>
|
||||
public bool TryAddWeather(EntityUid mapUid, EntProtoId weatherProto, [NotNullWhen(true)] out EntityUid? weatherEnt, TimeSpan? duration = null)
|
||||
{
|
||||
public Dictionary<ProtoId<WeatherPrototype>, WeatherData> Weather;
|
||||
return _statusEffects.TrySetStatusEffectDuration(mapUid, weatherProto, out weatherEnt, duration);
|
||||
}
|
||||
|
||||
public WeatherComponentState(Dictionary<ProtoId<WeatherPrototype>, WeatherData> weather)
|
||||
/// <summary>
|
||||
/// Checks if a specific weather exists on the given map.
|
||||
/// </summary>
|
||||
/// <param name="mapId">Target mapId</param>
|
||||
/// <param name="weatherProto">EntProtoId of weather status effect</param>
|
||||
/// <returns>True if the weather exists, otherwise false</returns>
|
||||
public bool HasWeather(MapId mapId, EntProtoId weatherProto)
|
||||
{
|
||||
if (!_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
return false;
|
||||
|
||||
return _statusEffects.TryGetStatusEffect(mapUid.Value, weatherProto, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slowly remove weather from a map. It should be gone after <see cref="ShutdownTime"/> seconds.
|
||||
/// </summary>
|
||||
/// <param name="mapId">Target mapId</param>
|
||||
/// <param name="weatherProto">EntProtoId of weather status effect</param>
|
||||
public bool TryRemoveWeather(MapId mapId, EntProtoId weatherProto)
|
||||
{
|
||||
if (!_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
return false;
|
||||
|
||||
return TryRemoveWeather(mapUid.Value, weatherProto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slowly remove weather from map. It should be gone after <see cref="ShutdownTime"/> seconds.
|
||||
/// </summary>
|
||||
/// <param name="mapUid">Target entity map</param>
|
||||
/// <param name="weatherProto">EntProtoId of weather status effect</param>
|
||||
public bool TryRemoveWeather(EntityUid mapUid, EntProtoId weatherProto)
|
||||
{
|
||||
if (!_statusEffects.TryGetStatusEffect(mapUid, weatherProto, out var weatherEnt))
|
||||
return false;
|
||||
|
||||
if (!_weatherQuery.HasComp(weatherEnt))
|
||||
return false;
|
||||
|
||||
return _statusEffects.TrySetStatusEffectDuration(mapUid, weatherProto, ShutdownTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all weather conditions except the specified one. If the specified weather does not exist on the map, it adds it.
|
||||
/// Returns true if the specified weather is present or was added, false otherwise.
|
||||
/// </summary>
|
||||
/// <param name="mapId">Target mapId</param>
|
||||
/// <param name="weatherProto">EntProtoId of weather status effect</param>
|
||||
/// <param name="weatherEnt">When this method returns, contains the <see cref="EntityUid"/> of the weather entity if the operation succeeded; otherwise, <c>null</c>.</param>
|
||||
/// <param name="duration">How long this weather should exist on map? If null - infinite duration</param>
|
||||
/// <returns><c>true</c> if the specified weather is present or was added; otherwise, <c>false</c>.</returns>
|
||||
public bool TrySetWeather(MapId mapId, EntProtoId? weatherProto, out EntityUid? weatherEnt, TimeSpan? duration = null)
|
||||
{
|
||||
weatherEnt = null;
|
||||
if (!_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
return false;
|
||||
|
||||
// Remove all other weather effects except the specified one
|
||||
if (_statusEffects.TryEffectsWithComp<WeatherStatusEffectComponent>(mapUid, out var effects))
|
||||
{
|
||||
Weather = weather;
|
||||
foreach (var effect in effects)
|
||||
{
|
||||
var effectProto = Prototype(effect);
|
||||
if (effectProto is null)
|
||||
continue;
|
||||
|
||||
if (effectProto != weatherProto)
|
||||
{
|
||||
TryRemoveWeather(mapUid.Value, effectProto);
|
||||
}
|
||||
else
|
||||
{
|
||||
weatherEnt = effect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If weatherProto is null, we just removed all weather and return true
|
||||
if (weatherProto is null)
|
||||
return true;
|
||||
|
||||
// If the specified weather already exists, just update its duration
|
||||
if (weatherEnt != null)
|
||||
{
|
||||
TryAddWeather(mapUid.Value, weatherProto.Value, out weatherEnt, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, add the specified weather
|
||||
return TryAddWeather(mapUid.Value, weatherProto.Value, out weatherEnt, duration);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Shared.Weather;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class WeatherComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently running weathers
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Dictionary<ProtoId<WeatherPrototype>, WeatherData> Weather = new();
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV: How long to wait between updating weather effects.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan UpdateDelay = TimeSpan.FromSeconds(1);
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV: When to next update weather effects (damage).
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan NextUpdate = TimeSpan.Zero;
|
||||
|
||||
public static readonly TimeSpan StartupTime = TimeSpan.FromSeconds(15);
|
||||
public static readonly TimeSpan ShutdownTime = TimeSpan.FromSeconds(15);
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public sealed partial class WeatherData
|
||||
{
|
||||
// Client audio stream.
|
||||
[NonSerialized]
|
||||
public EntityUid? Stream;
|
||||
|
||||
/// <summary>
|
||||
/// When the weather started if relevant.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] //TODO: Remove Custom serializer
|
||||
public TimeSpan StartTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// When the applied weather will end.
|
||||
/// </summary>
|
||||
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer))] //TODO: Remove Custom serializer
|
||||
public TimeSpan? EndTime;
|
||||
|
||||
[ViewVariables]
|
||||
public TimeSpan Duration => EndTime == null ? TimeSpan.MaxValue : EndTime.Value - StartTime;
|
||||
|
||||
[DataField]
|
||||
public WeatherState State = WeatherState.Invalid;
|
||||
}
|
||||
|
||||
public enum WeatherState : byte
|
||||
{
|
||||
Invalid = 0,
|
||||
Starting,
|
||||
Running,
|
||||
Ending,
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
using Content.Shared.Damage;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Weather;
|
||||
|
||||
[Prototype]
|
||||
public sealed partial class WeatherPrototype : IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; private set; } = default!;
|
||||
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV: Damage you can take from being in this weather.
|
||||
/// Only applies when weather has fully set in.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DamageSpecifier? Damage;
|
||||
|
||||
/// <summary>
|
||||
/// DeltaV: Don't damage entities that match this blacklist.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? DamageBlacklist;
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.StatusEffectNew.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Weather;
|
||||
|
||||
/// <summary>
|
||||
/// Used only in conjure with <see cref="StatusEffectComponent"/> for status effects applied to map entities.
|
||||
/// Contains basic information about all types of weather effects.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedWeatherSystem))]
|
||||
public sealed partial class WeatherStatusEffectComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// A texture that will tile and render as a weather effect across the entire map.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public SpriteSpecifier Sprite = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Tint that will be applied to the weather texture.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Color? Color;
|
||||
|
||||
/// <summary>
|
||||
/// Weather scrolling speed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Vector2? Scrolling;
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play on the affected areas.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? Sound;
|
||||
|
||||
/// <summary>
|
||||
/// Client audio stream.
|
||||
/// Not used on the server.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? Stream;
|
||||
}
|
||||
|
|
@ -1,8 +1,17 @@
|
|||
cmd-weather-desc = Sets the weather for the current map.
|
||||
cmd-weather-help = weather <mapId> <prototype / null>
|
||||
cmd-weather-hint = Weather prototype
|
||||
cmd-weather-null = Clears the weather
|
||||
cmd-weatherremove-desc = Remove specific weather from map.
|
||||
cmd-weatherset-desc = Removes all weather except the specified one. If the specified weather does not exist on the map, it adds it.
|
||||
cmd-weatheradd-desc = Add specific weather to map.
|
||||
|
||||
cmd-weatherremove-help = weatherremove <mapId> <prototype>
|
||||
cmd-weatherset-help = weatherset <mapId> <prototype / null>
|
||||
cmd-weatheradd-help = weatheradd <mapId> <prototype / null>
|
||||
|
||||
cmd-weather-error-no-arguments = Not enough arguments!
|
||||
cmd-weather-error-unknown-proto = Unknown Weather prototype!
|
||||
cmd-weather-error-wrong-time = Time is in the wrong format!
|
||||
cmd-weather-error-wrong-time = Time is in the wrong format!
|
||||
cmd-weather-error-wrong-map = Map with MapId {$id} doesn't exist!
|
||||
cmd-weather-error-no-weather = This weather does not exist on the selected map!
|
||||
|
||||
cmd-weather-hint-map-id = Map Id
|
||||
cmd-weather-hint-prototype = Weather entity prototype
|
||||
cmd-weather-hint-time = Duration in seconds (leave empty for infinite duration)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
- type: entity
|
||||
parent: StatusEffectBase
|
||||
id: WeatherBase
|
||||
abstract: true
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
- type: StatusEffect
|
||||
whitelist:
|
||||
components:
|
||||
- Map
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherRain
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: rain
|
||||
sound:
|
||||
collection: Rain
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherAshfall
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: ashfall
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherAshfallLight
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: ashfall_light
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherAshfallHeavy
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: ashfall_heavy
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherFallout
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: fallout
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherHail
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: hail
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/rain.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherSandstorm
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: sandstorm
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherSandstormHeavy
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: sandstorm_heavy
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherSnowfallLight
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: snowfall_light
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherSnowfallMedium
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: snowfall_med
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm_weak.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherSnowfallHeavy
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: snowfall_heavy
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/snowstorm.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
||||
- type: entity
|
||||
parent: WeatherBase
|
||||
id: WeatherStorm
|
||||
components:
|
||||
- type: WeatherStatusEffect
|
||||
sprite:
|
||||
sprite: /Textures/Effects/weather.rsi
|
||||
state: storm
|
||||
sound:
|
||||
path: /Audio/Effects/Weather/rain_heavy.ogg
|
||||
params:
|
||||
loop: true
|
||||
volume: -6
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
- type: soundCollection
|
||||
id: Rain
|
||||
files:
|
||||
- /Audio/Effects/Weather/rain.ogg
|
||||
- /Audio/Effects/Weather/rain2.ogg
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
- 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
|
||||
damage: # DeltaV
|
||||
types:
|
||||
Heat: 4
|
||||
damageBlacklist: # DeltaV
|
||||
components:
|
||||
- AshStormImmune
|
||||
|
||||
- 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
|
||||
Loading…
Reference in New Issue