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