From 800fd928b883d14ef7b45295ada2475e92f6df22 Mon Sep 17 00:00:00 2001
From: deltanedas <39013340+deltanedas@users.noreply.github.com>
Date: Wed, 18 Dec 2024 01:27:23 +0000
Subject: [PATCH] ash storms just the facts
---
.../DeltaV/Weather/WeatherEffectsSystem.cs | 82 +++++++++++++++++++
.../Weather/WeatherSchedulerComponent.cs | 58 +++++++++++++
.../DeltaV/Weather/WeatherSchedulerSystem.cs | 75 +++++++++++++++++
.../Components/AshStormImmuneComponent.cs | 9 ++
Content.Shared/Weather/SharedWeatherSystem.cs | 1 +
Content.Shared/Weather/WeatherComponent.cs | 12 +++
Content.Shared/Weather/WeatherPrototype.cs | 15 ++++
.../Locale/en-US/deltav/weather/ashstorm.ftl | 3 +
.../Prototypes/Body/Prototypes/ashwalker.yml | 50 +++++++++++
.../DeltaV/Body/Organs/ashwalker.yml | 9 ++
.../Entities/Mobs/Species/ashwalker.yml | 11 +++
Resources/Prototypes/weather.yml | 6 ++
12 files changed, 331 insertions(+)
create mode 100644 Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs
create mode 100644 Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs
create mode 100644 Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs
create mode 100644 Content.Shared/DeltaV/Weather/Components/AshStormImmuneComponent.cs
create mode 100644 Resources/Locale/en-US/deltav/weather/ashstorm.ftl
create mode 100644 Resources/Prototypes/Body/Prototypes/ashwalker.yml
create mode 100644 Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml
create mode 100644 Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml
diff --git a/Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs b/Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs
new file mode 100644
index 0000000000..11a29e05cc
--- /dev/null
+++ b/Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs
@@ -0,0 +1,82 @@
+using Content.Shared.Damage;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Weather;
+using Content.Shared.Whitelist;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.DeltaV.Weather;
+
+///
+/// Handles weather damage for exposed entities.
+///
+public sealed partial class WeatherEffectsSystem : EntitySystem
+{
+ [Dependency] private readonly DamageableSystem _damageable = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly SharedMapSystem _map = default!;
+ [Dependency] private readonly SharedWeatherSystem _weather = default!;
+
+ private EntityQuery _gridQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _gridQuery = GetEntityQuery();
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var now = _timing.CurTime;
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var map, out var weather))
+ {
+ if (now < weather.NextUpdate)
+ continue;
+
+ weather.NextUpdate = now + weather.UpdateDelay;
+
+ foreach (var (id, data) in weather.Weather)
+ {
+ // start and end do no damage
+ if (data.State != WeatherState.Running)
+ continue;
+
+ UpdateDamage(map, id);
+ }
+ }
+ }
+
+ private void UpdateDamage(EntityUid map, ProtoId id)
+ {
+ var weather = _proto.Index(id);
+ if (weather.Damage is not {} damage)
+ return;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var mob, out var xform))
+ {
+ // don't give dead bodies 10000 burn, that's not fun for anyone
+ if (xform.MapUid != map || mob.CurrentState == MobState.Dead)
+ continue;
+
+ // if not in space, check for being indoors
+ if (xform.GridUid is {} gridUid && _gridQuery.TryComp(gridUid, out var grid))
+ {
+ var tile = _map.GetTileRef((gridUid, grid), xform.Coordinates);
+ if (!_weather.CanWeatherAffect(gridUid, grid, tile))
+ continue;
+ }
+
+ if (_whitelist.IsBlacklistFailOrNull(weather.DamageBlacklist, uid))
+ _damageable.TryChangeDamage(uid, damage, interruptsDoAfters: false);
+ }
+ }
+}
diff --git a/Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs b/Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs
new file mode 100644
index 0000000000..ac69c95705
--- /dev/null
+++ b/Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs
@@ -0,0 +1,58 @@
+using Content.Shared.Destructible.Thresholds;
+using Content.Shared.Weather;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.DeltaV.Weather;
+
+///
+/// Makes weather randomly happen every so often.
+///
+[RegisterComponent, Access(typeof(WeatherSchedulerSystem))]
+[AutoGenerateComponentPause]
+public sealed partial class WeatherSchedulerComponent : Component
+{
+ ///
+ /// Weather stages to schedule.
+ ///
+ [DataField(required: true)]
+ public List Stages = new();
+
+ ///
+ /// The index of to use next, wraps back to the start.
+ ///
+ [DataField]
+ public int Stage;
+
+ ///
+ /// When to go to the next step of the schedule.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
+ public TimeSpan NextUpdate;
+}
+
+///
+/// A stage in a weather schedule.
+///
+[Serializable, DataDefinition]
+public partial struct WeatherStage
+{
+ ///
+ /// A range of how long the stage can last for, in seconds.
+ ///
+ [DataField(required: true)]
+ public MinMax Duration = new(0, 0);
+
+ ///
+ /// The weather prototype to add, or null for clear weather.
+ ///
+ [DataField]
+ public ProtoId? Weather;
+
+ ///
+ /// Alert message to send in chat for players on the map when it starts.
+ ///
+ [DataField]
+ public LocId? Message;
+}
diff --git a/Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs b/Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs
new file mode 100644
index 0000000000..a19a2f3787
--- /dev/null
+++ b/Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs
@@ -0,0 +1,75 @@
+using Content.Server.Chat.Managers;
+using Content.Shared.Chat;
+using Content.Shared.Weather;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.DeltaV.Weather;
+
+public sealed class WeatherSchedulerSystem : EntitySystem
+{
+ [Dependency] private readonly IChatManager _chat = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedWeatherSystem _weather = default!;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var now = _timing.CurTime;
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var map, out var comp))
+ {
+ if (now < comp.NextUpdate)
+ continue;
+
+ if (comp.Stage >= comp.Stages.Count)
+ comp.Stage = 0;
+
+ var stage = comp.Stages[comp.Stage++];
+ var duration = stage.Duration.Next(_random);
+ comp.NextUpdate = now + TimeSpan.FromSeconds(duration);
+
+ var mapId = Comp(map).MapId;
+ if (stage.Weather is {} weather)
+ {
+ var ending = comp.NextUpdate;
+ // crossfade weather so as one ends the next starts
+ if (HasWeather(comp, comp.Stage - 1))
+ ending += WeatherComponent.ShutdownTime;
+ if (HasWeather(comp, comp.Stage + 1))
+ ending += WeatherComponent.StartupTime;
+ _weather.SetWeather(mapId, _proto.Index(weather), ending);
+ }
+
+ if (stage.Message is {} message)
+ {
+ var msg = Loc.GetString(message);
+ _chat.ChatMessageToManyFiltered(
+ Filter.BroadcastMap(mapId),
+ ChatChannel.Radio,
+ msg,
+ msg,
+ map,
+ false,
+ true,
+ null);
+ }
+ }
+ }
+
+ private bool HasWeather(WeatherSchedulerComponent comp, int stage)
+ {
+ if (stage < 0)
+ stage = comp.Stages.Count + stage;
+ else if (stage >= comp.Stages.Count)
+ stage %= comp.Stages.Count;
+
+ return comp.Stages[stage].Weather != null;
+ }
+}
diff --git a/Content.Shared/DeltaV/Weather/Components/AshStormImmuneComponent.cs b/Content.Shared/DeltaV/Weather/Components/AshStormImmuneComponent.cs
new file mode 100644
index 0000000000..ec2c272695
--- /dev/null
+++ b/Content.Shared/DeltaV/Weather/Components/AshStormImmuneComponent.cs
@@ -0,0 +1,9 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.DeltaV.Weather.Components;
+
+///
+/// Makes an entity not take damage from ash storms.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class AshStormImmuneComponent : Component;
diff --git a/Content.Shared/Weather/SharedWeatherSystem.cs b/Content.Shared/Weather/SharedWeatherSystem.cs
index b537884950..abe191a41d 100644
--- a/Content.Shared/Weather/SharedWeatherSystem.cs
+++ b/Content.Shared/Weather/SharedWeatherSystem.cs
@@ -38,6 +38,7 @@ public abstract class SharedWeatherSystem : EntitySystem
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)
diff --git a/Content.Shared/Weather/WeatherComponent.cs b/Content.Shared/Weather/WeatherComponent.cs
index eaf901fb42..e16fe978fa 100644
--- a/Content.Shared/Weather/WeatherComponent.cs
+++ b/Content.Shared/Weather/WeatherComponent.cs
@@ -14,6 +14,18 @@ public sealed partial class WeatherComponent : Component
[DataField]
public Dictionary, WeatherData> Weather = new();
+ ///
+ /// DeltaV: How long to wait between updating weather effects.
+ ///
+ [DataField]
+ public TimeSpan UpdateDelay = TimeSpan.FromSeconds(1);
+
+ ///
+ /// DeltaV: When to next update weather effects (damage).
+ ///
+ [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);
}
diff --git a/Content.Shared/Weather/WeatherPrototype.cs b/Content.Shared/Weather/WeatherPrototype.cs
index 246e929dce..60233f7b49 100644
--- a/Content.Shared/Weather/WeatherPrototype.cs
+++ b/Content.Shared/Weather/WeatherPrototype.cs
@@ -1,3 +1,5 @@
+using Content.Shared.Damage;
+using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -20,4 +22,17 @@ public sealed partial class WeatherPrototype : IPrototype
///
[ViewVariables(VVAccess.ReadWrite), DataField("sound")]
public SoundSpecifier? Sound;
+
+ ///
+ /// DeltaV: Damage you can take from being in this weather.
+ /// Only applies when weather has fully set in.
+ ///
+ [DataField]
+ public DamageSpecifier? Damage;
+
+ ///
+ /// DeltaV: Don't damage entities that match this blacklist.
+ ///
+ [DataField]
+ public EntityWhitelist? DamageBlacklist;
}
diff --git a/Resources/Locale/en-US/deltav/weather/ashstorm.ftl b/Resources/Locale/en-US/deltav/weather/ashstorm.ftl
new file mode 100644
index 0000000000..4977852250
--- /dev/null
+++ b/Resources/Locale/en-US/deltav/weather/ashstorm.ftl
@@ -0,0 +1,3 @@
+ash-storm-telegraph = [color=red][bold]An eerie moan rises on the wind. Sheets of burning ash blacken the horizon. Seek shelter.[/bold][/color]
+ash-storm-alert = [color=red][bolditalic]Smoldering clouds of scorching ash billow down around you! Get inside![/bolditalic][/color]
+ash-storm-clearing = [color=red]The shrieking wind whips away the last of the ash and falls to its usual murmur. It should be safe to go outside now.[/color]
diff --git a/Resources/Prototypes/Body/Prototypes/ashwalker.yml b/Resources/Prototypes/Body/Prototypes/ashwalker.yml
new file mode 100644
index 0000000000..59c55e89f4
--- /dev/null
+++ b/Resources/Prototypes/Body/Prototypes/ashwalker.yml
@@ -0,0 +1,50 @@
+# TODO: will need updating with shitmed
+- type: body
+ name: ashwalker
+ id: AshWalker
+ root: torso
+ slots:
+ head:
+ part: HeadReptilian
+ connections:
+ - torso
+ organs:
+ brain: OrganHumanBrain
+ eyes: OrganHumanEyes
+ torso:
+ part: TorsoReptilian
+ organs:
+ heart: OrganAnimalHeart
+ lungs: OrganAshWalkerLungs
+ stomach: OrganReptilianStomach
+ liver: OrganAnimalLiver
+ kidneys: OrganHumanKidneys
+ connections:
+ - right arm
+ - left arm
+ - right leg
+ - left leg
+ right arm:
+ part: RightArmReptilian
+ connections:
+ - right hand
+ left arm:
+ part: LeftArmReptilian
+ connections:
+ - left hand
+ right hand:
+ part: RightHandReptilian
+ left hand:
+ part: LeftHandReptilian
+ right leg:
+ part: RightLegReptilian
+ connections:
+ - right foot
+ left leg:
+ part: LeftLegReptilian
+ connections:
+ - left foot
+ right foot:
+ part: RightFootReptilian
+ left foot:
+ part: LeftFootReptilian
diff --git a/Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml b/Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml
new file mode 100644
index 0000000000..c0388ffd0e
--- /dev/null
+++ b/Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml
@@ -0,0 +1,9 @@
+- type: entity
+ parent: OrganAnimalLungs
+ id: OrganAshWalkerLungs
+ name: ashwalker lungs
+ description: These lungs are adapted from isolation in lavaland, capable of withstanding the low oxygen content and ash storms.
+ components:
+ - type: Lung
+ saturationLoss: 0.5
+ # TODO SHITMED: add AshStormImmune when transplanted
diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml
new file mode 100644
index 0000000000..4a397caaa3
--- /dev/null
+++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml
@@ -0,0 +1,11 @@
+- type: entity
+ save: false
+ parent: MobReptilian
+ id: MobAshWalker
+ name: Urist McAsh
+ suffix: ""
+ components:
+ - type: Body
+ prototype: AshWalker
+ - type: AshStormImmune
+ # TODO: shitmed stuff so you can steal ashwalker lungs
diff --git a/Resources/Prototypes/weather.yml b/Resources/Prototypes/weather.yml
index a71e59354a..896df8641a 100644
--- a/Resources/Prototypes/weather.yml
+++ b/Resources/Prototypes/weather.yml
@@ -8,6 +8,12 @@
params:
loop: true
volume: -6
+ damage: # DeltaV
+ types:
+ Heat: 4
+ damageBlacklist: # DeltaV
+ components:
+ - AshStormImmune
- type: weather
id: AshfallLight