ash storms just the facts

This commit is contained in:
deltanedas
2024-12-18 01:27:23 +00:00
committed by tommy
parent cef7597e8d
commit 800fd928b8
12 changed files with 331 additions and 0 deletions

View File

@@ -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;
/// <summary>
/// Handles weather damage for exposed entities.
/// </summary>
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<MapGridComponent> _gridQuery;
public override void Initialize()
{
base.Initialize();
_gridQuery = GetEntityQuery<MapGridComponent>();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var now = _timing.CurTime;
var query = EntityQueryEnumerator<WeatherComponent>();
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<WeatherPrototype> id)
{
var weather = _proto.Index(id);
if (weather.Damage is not {} damage)
return;
var query = EntityQueryEnumerator<MobStateComponent, TransformComponent>();
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);
}
}
}

View File

@@ -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;
/// <summary>
/// Makes weather randomly happen every so often.
/// </summary>
[RegisterComponent, Access(typeof(WeatherSchedulerSystem))]
[AutoGenerateComponentPause]
public sealed partial class WeatherSchedulerComponent : Component
{
/// <summary>
/// Weather stages to schedule.
/// </summary>
[DataField(required: true)]
public List<WeatherStage> Stages = new();
/// <summary>
/// The index of <see cref="Stages"/> to use next, wraps back to the start.
/// </summary>
[DataField]
public int Stage;
/// <summary>
/// When to go to the next step of the schedule.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan NextUpdate;
}
/// <summary>
/// A stage in a weather schedule.
/// </summary>
[Serializable, DataDefinition]
public partial struct WeatherStage
{
/// <summary>
/// A range of how long the stage can last for, in seconds.
/// </summary>
[DataField(required: true)]
public MinMax Duration = new(0, 0);
/// <summary>
/// The weather prototype to add, or null for clear weather.
/// </summary>
[DataField]
public ProtoId<WeatherPrototype>? Weather;
/// <summary>
/// Alert message to send in chat for players on the map when it starts.
/// </summary>
[DataField]
public LocId? Message;
}

View File

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

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared.DeltaV.Weather.Components;
/// <summary>
/// Makes an entity not take damage from ash storms.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class AshStormImmuneComponent : Component;

View File

@@ -38,6 +38,7 @@ public abstract class SharedWeatherSystem : EntitySystem
if (weather.EndTime != null) if (weather.EndTime != null)
weather.EndTime = weather.EndTime.Value + args.PausedTime; 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(EntityUid uid, MapGridComponent grid, TileRef tileRef, RoofComponent? roofComp = null)

View File

@@ -14,6 +14,18 @@ public sealed partial class WeatherComponent : Component
[DataField] [DataField]
public Dictionary<ProtoId<WeatherPrototype>, WeatherData> Weather = new(); 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 StartupTime = TimeSpan.FromSeconds(15);
public static readonly TimeSpan ShutdownTime = TimeSpan.FromSeconds(15); public static readonly TimeSpan ShutdownTime = TimeSpan.FromSeconds(15);
} }

View File

@@ -1,3 +1,5 @@
using Content.Shared.Damage;
using Content.Shared.Whitelist;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -20,4 +22,17 @@ public sealed partial class WeatherPrototype : IPrototype
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("sound")] [ViewVariables(VVAccess.ReadWrite), DataField("sound")]
public SoundSpecifier? 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;
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,12 @@
params: params:
loop: true loop: true
volume: -6 volume: -6
damage: # DeltaV
types:
Heat: 4
damageBlacklist: # DeltaV
components:
- AshStormImmune
- type: weather - type: weather
id: AshfallLight id: AshfallLight