Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ash storms to lavaland #2445

Merged
merged 5 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Content.Server/Body/Components/LungComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ public sealed partial class LungComponent : Component
/// </summary>
[DataField]
public ProtoId<AlertPrototype> Alert = "LowOxygen";

/// <summary>
/// DeltaV: Multiplier on saturation passively lost.
/// Higher values require more air, lower require less.
/// </summary>
[DataField]
public float SaturationLoss = 1f;
}
10 changes: 9 additions & 1 deletion Content.Server/Body/Systems/RespiratorSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,15 @@ public override void Update(float frameTime)
if (_mobState.IsDead(uid))
continue;

UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator);
// Begin DeltaV Code: Addition:
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((uid, body));
var multiplier = -1f;
foreach (var (_, lung, _) in organs)
{
multiplier *= lung.SaturationLoss;
}
// End DeltaV Code
UpdateSaturation(uid, multiplier * (float) respirator.UpdateInterval.TotalSeconds, respirator); // DeltaV: use multiplier instead of negating

if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
{
Expand Down
82 changes: 82 additions & 0 deletions Content.Server/DeltaV/Weather/WeatherEffectsSystem.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
58 changes: 58 additions & 0 deletions Content.Server/DeltaV/Weather/WeatherSchedulerComponent.cs
Original file line number Diff line number Diff line change
@@ -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;
}
75 changes: 75 additions & 0 deletions Content.Server/DeltaV/Weather/WeatherSchedulerSystem.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions Content.Shared/Weather/SharedWeatherSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private void OnWeatherUnpaused(EntityUid uid, WeatherComponent component, ref En
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)
Expand Down
12 changes: 12 additions & 0 deletions Content.Shared/Weather/WeatherComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ public sealed partial class WeatherComponent : Component
[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);
}
Expand Down
15 changes: 15 additions & 0 deletions Content.Shared/Weather/WeatherPrototype.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Content.Shared.Damage;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
Expand All @@ -20,4 +22,17 @@ public sealed partial class WeatherPrototype : IPrototype
/// </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;
}
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/deltav/weather/ashstorm.ftl
Original file line number Diff line number Diff line change
@@ -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]
9 changes: 9 additions & 0 deletions Resources/Prototypes/DeltaV/Body/Organs/ashwalker.yml
Original file line number Diff line number Diff line change
@@ -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
50 changes: 50 additions & 0 deletions Resources/Prototypes/DeltaV/Body/Prototypes/ashwalker.yml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions Resources/Prototypes/DeltaV/Entities/Mobs/Species/ashwalker.yml
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading