resimmed offmed

This commit is contained in:
Janet Blackquill
2025-10-06 01:55:42 -04:00
parent 847154b320
commit 173f24590f
84 changed files with 1210 additions and 1004 deletions

View File

@@ -443,7 +443,7 @@ public sealed class MobThresholdSystem : EntitySystem
// Begin Offbrand
private void MobThresholdMapInit(Entity<MobThresholdsComponent> ent, ref MapInitEvent args)
{
var overlayUpdate = new Content.Shared._Offbrand.Wounds.bPotentiallyUpdateDamageOverlayEventb(ent);
var overlayUpdate = new Content.Shared._Offbrand.Wounds.PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlayUpdate);
}
// End Offbrand

View File

@@ -30,15 +30,17 @@ public sealed partial class StatusEffectsSystem
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.WoundGetDamageEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetWoundsWithSpaceEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetPainEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetStrainEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.HealWoundsEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetBleedLevelEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.PainSuppressionEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.BeforeDealBrainDamage>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.BeforeDepleteBrainOxygen>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.BeforeHealBrainDamage>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetOxygenationModifier>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.GetStoppedCirculationModifier>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedVascularToneEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedLungFunctionEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedMetabolicRateEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedCardiacOutputEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Wounds.ModifiedRespiratoryRateEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Weapons.RelayedGetMeleeDamageEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Weapons.RelayedGetMeleeAttackRateEvent>(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent<StatusEffectContainerComponent, Content.Shared._Offbrand.Weapons.RelayedGunRefreshModifiersEvent>(RefRelayStatusEffectEvent); // Offbrand

View File

@@ -1,14 +0,0 @@
using Content.Shared.FixedPoint;
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(AssistedCirculationStatusEffectSystem))]
public sealed partial class AssistedCirculationStatusEffectComponent : Component
{
/// <summary>
/// How much blood circulation to add
/// </summary>
[DataField(required: true)]
public FixedPoint2 Amount;
}

View File

@@ -1,20 +0,0 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.FixedPoint;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed partial class AssistedCirculationStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AssistedCirculationStatusEffectComponent, StatusEffectRelayedEvent<GetStoppedCirculationModifier>>(OnGetStoppedCirculationModifier);
}
private void OnGetStoppedCirculationModifier(Entity<AssistedCirculationStatusEffectComponent> ent, ref StatusEffectRelayedEvent<GetStoppedCirculationModifier> args)
{
args.Args = args.Args with { Modifier = FixedPoint2.Clamp(args.Args.Modifier + ent.Comp.Amount, 0, 1) };
}
}

View File

@@ -1,22 +0,0 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class BloodOxygenationModifierStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<BloodOxygenationModifierStatusEffectComponent, StatusEffectRelayedEvent<GetOxygenationModifier>>(OnGetOxygenationModifier);
}
private void OnGetOxygenationModifier(Entity<BloodOxygenationModifierStatusEffectComponent> ent, ref StatusEffectRelayedEvent<GetOxygenationModifier> args)
{
var theirs = args.Args.Modifier.Double();
var ours = ent.Comp.MinimumOxygenation.Double();
args.Args = args.Args with { Modifier = theirs + ours - (theirs * ours) };
}
}

View File

@@ -1,14 +0,0 @@
using Content.Shared.FixedPoint;
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(BloodOxygenationModifierStatusEffectSystem))]
public sealed partial class BloodOxygenationModifierStatusEffectComponent : Component
{
/// <summary>
/// The minimum lung oxygenation this status effect guarantees
/// </summary>
[DataField(required: true)]
public FixedPoint2 MinimumOxygenation;
}

View File

@@ -0,0 +1,12 @@
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(CardiacOutputModifierStatusEffectSystem))]
public sealed partial class CardiacOutputModifierStatusEffectComponent : Component
{
/// <summary>
/// The minimum cardiac output this status effect guarantees
/// </summary>
[DataField(required: true)]
public float Output;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class CardiacOutputModifierStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CardiacOutputModifierStatusEffectComponent, StatusEffectRelayedEvent<ModifiedCardiacOutputEvent>>(OnModifiedCardiacOutput);
}
private void OnModifiedCardiacOutput(Entity<CardiacOutputModifierStatusEffectComponent> ent, ref StatusEffectRelayedEvent<ModifiedCardiacOutputEvent> args)
{
args.Args = args.Args with { Output = MathF.Max(ent.Comp.Output, args.Args.Output) };
}
}

View File

@@ -0,0 +1,12 @@
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(LungFunctionModifierStatusEffectSystem))]
public sealed partial class LungFunctionModifierStatusEffectComponent : Component
{
/// <summary>
/// The minimum lung function this status effect guarantees
/// </summary>
[DataField(required: true)]
public float Function;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class LungFunctionModifierStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LungFunctionModifierStatusEffectComponent, StatusEffectRelayedEvent<ModifiedLungFunctionEvent>>(OnModifiedVascularTone);
}
private void OnModifiedVascularTone(Entity<LungFunctionModifierStatusEffectComponent> ent, ref StatusEffectRelayedEvent<ModifiedLungFunctionEvent> args)
{
args.Args = args.Args with { Function = MathF.Max(ent.Comp.Function, args.Args.Function) };
}
}

View File

@@ -0,0 +1,24 @@
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(MetabolicRateModifierStatusEffectSystem))]
public sealed partial class MetabolicRateModifierStatusEffectComponent : Component
{
/// <summary>
/// The modifier applied to the metabolic rate
/// </summary>
[DataField(required: true)]
public float Delta;
/// <summary>
/// The minimum metabolic rate that can happen as a result of this status effect
/// </summary>
[DataField]
public float Min = 1f;
/// <summary>
/// The maximum metabolic rate that can happen as a result of this status effect
/// </summary>
[DataField]
public float Max = float.PositiveInfinity;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class MetabolicRateModifierStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MetabolicRateModifierStatusEffectComponent, StatusEffectRelayedEvent<ModifiedMetabolicRateEvent>>(OnModifiedMetabolicRate);
}
private void OnModifiedMetabolicRate(Entity<MetabolicRateModifierStatusEffectComponent> ent, ref StatusEffectRelayedEvent<ModifiedMetabolicRateEvent> args)
{
args.Args = args.Args with { Rate = Math.Clamp(args.Args.Rate + ent.Comp.Delta, ent.Comp.Min, ent.Comp.Max) };
}
}

View File

@@ -20,7 +20,7 @@ public sealed class ModifyBrainDamageChanceStatusEffectSystem : EntitySystem
if (Comp<StatusEffectComponent>(ent).AppliedTo is not { } target)
return;
var oxygenation = _heart.BloodOxygenation((target, Comp<HeartrateComponent>(target)));
var oxygenation = _heart.Spo2((target, Comp<HeartrateComponent>(target)));
if (ent.Comp.OxygenationModifierThresholds.LowestMatch(oxygenation) is not { } modifier)
return;

View File

@@ -20,7 +20,7 @@ public sealed class ModifyBrainOxygenDepletionChanceStatusEffectSystem : EntityS
if (Comp<StatusEffectComponent>(ent).AppliedTo is not { } target)
return;
var oxygenation = _heart.BloodOxygenation((target, Comp<HeartrateComponent>(target)));
var oxygenation = _heart.Spo2((target, Comp<HeartrateComponent>(target)));
if (ent.Comp.OxygenationModifierThresholds.LowestMatch(oxygenation) is not { } modifier)
return;

View File

@@ -0,0 +1,12 @@
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(RespiratoryRateModifierStatusEffectSystem))]
public sealed partial class RespiratoryRateModifierStatusEffectComponent : Component
{
/// <summary>
/// The minimum respiratory rate this status effect guarantees
/// </summary>
[DataField(required: true)]
public float Rate;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class RespiratoryRateModifierStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RespiratoryRateModifierStatusEffectComponent, StatusEffectRelayedEvent<ModifiedRespiratoryRateEvent>>(OnModifiedRespiratoryRate);
}
private void OnModifiedRespiratoryRate(Entity<RespiratoryRateModifierStatusEffectComponent> ent, ref StatusEffectRelayedEvent<ModifiedRespiratoryRateEvent> args)
{
args.Args = args.Args with { Rate = MathF.Max(ent.Comp.Rate, args.Args.Rate) };
}
}

View File

@@ -1,11 +0,0 @@
using Content.Shared.FixedPoint;
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(StrainStatusEffectSystem))]
public sealed partial class StrainStatusEffectComponent : Component
{
[DataField(required: true)]
public FixedPoint2 Delta;
}

View File

@@ -1,19 +0,0 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class StrainStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StrainStatusEffectComponent, StatusEffectRelayedEvent<GetStrainEvent>>(OnGetStrain);
}
private void OnGetStrain(Entity<StrainStatusEffectComponent> ent, ref StatusEffectRelayedEvent<GetStrainEvent> args)
{
args.Args = args.Args with { Strain = args.Args.Strain + ent.Comp.Delta };
}
}

View File

@@ -0,0 +1,12 @@
namespace Content.Shared._Offbrand.StatusEffects;
[RegisterComponent]
[Access(typeof(VascularToneModifierStatusEffectSystem))]
public sealed partial class VascularToneModifierStatusEffectComponent : Component
{
/// <summary>
/// The minimum vascular tone this status effect guarantees
/// </summary>
[DataField(required: true)]
public float Tone;
}

View File

@@ -0,0 +1,19 @@
using Content.Shared._Offbrand.Wounds;
using Content.Shared.StatusEffectNew;
namespace Content.Shared._Offbrand.StatusEffects;
public sealed class VascularToneModifierStatusEffectSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<VascularToneModifierStatusEffectComponent, StatusEffectRelayedEvent<ModifiedVascularToneEvent>>(OnModifiedVascularTone);
}
private void OnModifiedVascularTone(Entity<VascularToneModifierStatusEffectComponent> ent, ref StatusEffectRelayedEvent<ModifiedVascularToneEvent> args)
{
args.Args = args.Args with { Tone = MathF.Max(ent.Comp.Tone, args.Args.Tone) };
}
}

View File

@@ -20,6 +20,7 @@ public sealed partial class BrainDamageSystem : EntitySystem
SubscribeLocalEvent<BrainDamageComponent, SuicideEvent>(OnSuicide);
SubscribeLocalEvent<BrainDamageComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<BrainDamageComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<BrainDamageComponent, BaseVascularToneEvent>(OnBaseVascularTone);
SubscribeLocalEvent<BrainDamageOxygenationComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<BrainDamageOxygenationComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
}
@@ -42,7 +43,7 @@ public sealed partial class BrainDamageSystem : EntitySystem
var notifDamage = new AfterBrainDamageChanged();
RaiseLocalEvent(ent, ref notifDamage);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -60,6 +61,11 @@ public sealed partial class BrainDamageSystem : EntitySystem
RaiseLocalEvent(ent, ref notifDamage);
}
private void OnBaseVascularTone(Entity<BrainDamageComponent> ent, ref BaseVascularToneEvent args)
{
args.Tone *= 1f - ent.Comp.Damage.Float() / ent.Comp.MaxDamage.Float();
}
private void OnApplyMetabolicMultiplier(Entity<BrainDamageOxygenationComponent> ent, ref ApplyMetabolicMultiplierEvent args)
{
ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
@@ -97,7 +103,7 @@ public sealed partial class BrainDamageSystem : EntitySystem
var notifDamage = new AfterBrainDamageChanged();
RaiseLocalEvent(ent, ref notifDamage);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -112,7 +118,7 @@ public sealed partial class BrainDamageSystem : EntitySystem
var notif = new AfterBrainDamageChanged();
RaiseLocalEvent(ent, ref notif);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
public void TryChangeBrainOxygenation(Entity<BrainDamageComponent?> ent, FixedPoint2 amount)
@@ -126,7 +132,7 @@ public sealed partial class BrainDamageSystem : EntitySystem
var notif = new AfterBrainOxygenChanged();
RaiseLocalEvent(ent, ref notif);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -185,7 +191,7 @@ public sealed partial class BrainDamageSystem : EntitySystem
var notif = new AfterBrainDamageChanged();
RaiseLocalEvent(ent, ref notif);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -215,13 +221,13 @@ public sealed partial class BrainDamageSystem : EntitySystem
var notif = new AfterBrainDamageChanged();
RaiseLocalEvent(ent, ref notif);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
private void DoUpdate(Entity<BrainDamageComponent, BrainDamageOxygenationComponent, HeartrateComponent> ent)
{
var oxygenation = _heart.BloodOxygenation((ent.Owner, ent.Comp3));
var oxygenation = _heart.Spo2((ent.Owner, ent.Comp3));
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
var rand = new System.Random(seed);

View File

@@ -69,7 +69,7 @@ public sealed partial class BrainDamageThresholdsSystem : EntitySystem
ent.Comp.CurrentDamageEffect = damageEffect;
Dirty(ent);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -92,7 +92,7 @@ public sealed partial class BrainDamageThresholdsSystem : EntitySystem
ent.Comp.CurrentOxygenEffect = oxygenEffect;
Dirty(ent);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -119,7 +119,7 @@ public sealed partial class BrainDamageThresholdsSystem : EntitySystem
{
args.State = ThresholdHelpers.Max(ent.Comp.CurrentState, args.State);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
}

View File

@@ -3,12 +3,12 @@ using Content.Shared._Offbrand.StatusEffects;
using Content.Shared.Body.Components;
using Content.Shared.Body.Events;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Medical;
using Content.Shared.Random.Helpers;
using Content.Shared.Rejuvenate;
using Content.Shared.StatusEffectNew;
using Robust.Shared.Maths;
using Robust.Shared.Random;
using Robust.Shared.Timing;
@@ -30,15 +30,8 @@ public sealed partial class HeartSystem : EntitySystem
SubscribeLocalEvent<HeartrateComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
SubscribeLocalEvent<HeartrateComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<BloodstreamComponent, GetStrainEvent>(OnBloodstreamGetStrain);
SubscribeLocalEvent<HeartStopOnHypovolemiaComponent, HeartBeatEvent>(OnHeartBeatHypovolemia);
SubscribeLocalEvent<HeartStopOnHighStrainComponent, HeartBeatEvent>(OnHeartBeatStrain);
SubscribeLocalEvent<HeartStopOnBrainHealthComponent, HeartBeatEvent>(OnHeartBeatBrain);
SubscribeLocalEvent<HeartStopOnHypovolemiaComponent, BeforeTargetDefibrillatedEvent>(OnHeartBeatHypovolemiaMessage);
SubscribeLocalEvent<HeartStopOnHighStrainComponent, BeforeTargetDefibrillatedEvent>(OnHeartBeatStrainMessage);
SubscribeLocalEvent<HeartStopOnBrainHealthComponent, BeforeTargetDefibrillatedEvent>(OnHeartBeatBrainMessage);
SubscribeLocalEvent<HeartDefibrillatableComponent, TargetDefibrillatedEvent>(OnTargetDefibrillated);
}
@@ -47,13 +40,9 @@ public sealed partial class HeartSystem : EntitySystem
{
ent.Comp.Damage = 0;
ent.Comp.Running = true;
ent.Comp.Strain = 0;
Dirty(ent);
var strainChangedEvt = new AfterStrainChangedEvent();
RaiseLocalEvent(ent, ref strainChangedEvt);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -81,25 +70,23 @@ public sealed partial class HeartSystem : EntitySystem
heartrate.LastUpdate = _timing.CurTime;
Dirty(uid, heartrate);
RecomputeVitals((uid, heartrate));
var strainChanged = new AfterStrainChangedEvent();
RaiseLocalEvent(uid, ref strainChanged);
var respiration = new ApplyRespiratoryRateModifiersEvent(ComputeRespiratoryRateModifier((uid, heartrate)), ComputeExhaleEfficiencyModifier((uid, heartrate)));
RaiseLocalEvent(uid, ref respiration);
if (!heartrate.Running)
continue;
var newStrain = RecomputeHeartStrain((uid, heartrate));
if (newStrain != heartrate.Strain)
{
heartrate.Strain = RecomputeHeartStrain((uid, heartrate));
Dirty(uid, heartrate);
var strainChangedEvt = new AfterStrainChangedEvent();
RaiseLocalEvent(uid, ref strainChangedEvt);
}
var evt = new HeartBeatEvent(false);
RaiseLocalEvent(uid, ref evt);
if (!evt.Stop)
{
var threshold = heartrate.StrainDamageThresholds.HighestMatch(HeartStrain((uid, heartrate)));
var threshold = heartrate.StrainDamageThresholds.HighestMatch(Strain((uid, heartrate)));
if (threshold is (var chance, var amount))
{
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(uid).Id });
@@ -118,13 +105,14 @@ public sealed partial class HeartSystem : EntitySystem
}
}
if (evt.Stop)
{
StopHeart((uid, heartrate));
continue;
}
var overlays = new bPotentiallyUpdateDamageOverlayEventb(uid);
var overlays = new PotentiallyUpdateDamageOverlayEvent(uid);
RaiseLocalEvent(uid, ref overlays, true);
}
}
@@ -132,10 +120,7 @@ public sealed partial class HeartSystem : EntitySystem
private void StopHeart(Entity<HeartrateComponent> ent)
{
ent.Comp.Running = false;
ent.Comp.Strain = 0;
var strainChangedEvt = new AfterStrainChangedEvent();
RaiseLocalEvent(ent, ref strainChangedEvt);
Dirty(ent);
var stoppedEvt = new HeartStoppedEvent();
RaiseLocalEvent(ent, ref stoppedEvt);
@@ -150,12 +135,8 @@ public sealed partial class HeartSystem : EntitySystem
ent.Comp.Damage = ent.Comp.MaxDamage;
ent.Comp.Running = false;
ent.Comp.Strain = 0;
Dirty(ent);
var strainChangedEvt = new AfterStrainChangedEvent();
RaiseLocalEvent(ent, ref strainChangedEvt);
var stoppedEvt = new HeartStoppedEvent();
RaiseLocalEvent(ent, ref stoppedEvt);
@@ -168,15 +149,6 @@ public sealed partial class HeartSystem : EntitySystem
Dirty(ent);
}
private void OnHeartBeatHypovolemia(Entity<HeartStopOnHypovolemiaComponent> ent, ref HeartBeatEvent args)
{
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
var rand = new System.Random(seed);
var volume = BloodVolume((ent.Owner, Comp<HeartrateComponent>(ent)));
args.Stop = args.Stop || rand.Prob(ent.Comp.Chance) && volume < ent.Comp.VolumeThreshold;
}
private void OnHeartBeatStrain(Entity<HeartStopOnHighStrainComponent> ent, ref HeartBeatEvent args)
{
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
@@ -185,55 +157,32 @@ public sealed partial class HeartSystem : EntitySystem
if (_statusEffects.HasEffectComp<PreventHeartStopFromStrainStatusEffectComponent>(ent))
return;
var strain = HeartStrain((ent.Owner, Comp<HeartrateComponent>(ent)));
var strain = Strain((ent.Owner, Comp<HeartrateComponent>(ent)));
args.Stop = args.Stop || rand.Prob(ent.Comp.Chance) && strain > ent.Comp.Threshold;
}
private void OnHeartBeatBrain(Entity<HeartStopOnBrainHealthComponent> ent, ref HeartBeatEvent args)
{
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
var rand = new System.Random(seed);
if (_statusEffects.HasEffectComp<PreventHeartStopFromStrainStatusEffectComponent>(ent))
return;
var damage = Comp<BrainDamageComponent>(ent).Damage;
args.Stop = args.Stop || rand.Prob(ent.Comp.Chance) && damage > ent.Comp.Threshold;
}
private void OnHeartBeatHypovolemiaMessage(Entity<HeartStopOnHypovolemiaComponent> ent, ref BeforeTargetDefibrillatedEvent args)
{
var volume = BloodVolume((ent.Owner, Comp<HeartrateComponent>(ent)));
if (volume >= ent.Comp.VolumeThreshold)
return;
args.Messages.Add(ent.Comp.Warning);
}
private void OnHeartBeatStrainMessage(Entity<HeartStopOnHighStrainComponent> ent, ref BeforeTargetDefibrillatedEvent args)
{
if (_statusEffects.HasEffectComp<PreventHeartStopFromStrainStatusEffectComponent>(ent))
return;
var strain = RecomputeHeartStrain((ent.Owner, Comp<HeartrateComponent>(ent)));
var heartrate = Comp<HeartrateComponent>(ent);
var volume = ComputeBloodVolume((ent.Owner, heartrate));
var tone = ComputeVascularTone((ent.Owner, heartrate));
var perfusion = MathF.Min(volume, tone);
var function = ComputeLungFunction((ent.Owner, heartrate));
var supply = function * perfusion;
var demand = ComputeMetabolicRate((ent.Owner, heartrate));
var compensation = ComputeCompensation((ent.Owner, heartrate), supply, demand);
var strain = heartrate.CompensationStrainCoefficient * compensation + heartrate.CompensationStrainConstant;
if (strain < ent.Comp.Threshold)
return;
args.Messages.Add(ent.Comp.Warning);
}
private void OnHeartBeatBrainMessage(Entity<HeartStopOnBrainHealthComponent> ent, ref BeforeTargetDefibrillatedEvent args)
{
if (_statusEffects.HasEffectComp<PreventHeartStopFromStrainStatusEffectComponent>(ent))
return;
var damage = Comp<BrainDamageComponent>(ent).Damage;
if (damage <= ent.Comp.Threshold)
return;
args.Messages.Add(ent.Comp.Warning);
}
public void ChangeHeartDamage(Entity<HeartrateComponent?> ent, FixedPoint2 amount)
{
if (!Resolve(ent, ref ent.Comp, false))
@@ -252,120 +201,117 @@ public sealed partial class HeartSystem : EntitySystem
}
}
public FixedPoint2 BloodVolume(Entity<HeartrateComponent> ent)
private void RecomputeVitals(Entity<HeartrateComponent> ent)
{
var volume = ComputeBloodVolume(ent);
var tone = ComputeVascularTone(ent);
var perfusion = MathF.Min(volume, MathF.Min(tone, CardiacOutput(ent)));
var function = ComputeLungFunction(ent);
var supply = function * perfusion;
var demand = ComputeMetabolicRate(ent);
var compensation = ComputeCompensation(ent, supply, demand);
perfusion *= compensation;
supply = function * perfusion;
ent.Comp.Perfusion = perfusion;
ent.Comp.Compensation = compensation;
ent.Comp.OxygenSupply = supply;
ent.Comp.OxygenDemand = demand;
Dirty(ent);
}
[Access(typeof(HeartSystem), typeof(HeartrateComponent))]
public float CardiacOutput(Entity<HeartrateComponent> ent)
{
var baseEv = new BaseCardiacOutputEvent(!ent.Comp.Running ? 0f : 1f - (ent.Comp.Damage.Float() / ent.Comp.MaxDamage.Float()));
RaiseLocalEvent(ent, ref baseEv);
var modifiedEv = new ModifiedCardiacOutputEvent(baseEv.Output);
RaiseLocalEvent(ent, ref modifiedEv);
return Math.Max(modifiedEv.Output, ent.Comp.MinimumCardiacOutput);
}
[Access(typeof(HeartSystem), typeof(HeartrateComponent))]
public float ComputeCompensation(Entity<HeartrateComponent> ent, float supply, float demand)
{
var invert = MathF.Log(demand / supply);
if (!float.IsFinite(invert))
throw new InvalidOperationException($"demand/supply {demand}/{supply} is not finite: {invert}");
var targetCompensation = ent.Comp.CompensationCoefficient * invert + ent.Comp.CompensationConstant;
var healthFactor = !ent.Comp.Running ? 0f : 1f - (ent.Comp.Damage.Float() / ent.Comp.MaxDamage.Float());
return Math.Max(targetCompensation * healthFactor, 1f);
}
[Access(typeof(HeartSystem), typeof(HeartrateComponent))]
public float ComputeBloodVolume(Entity<HeartrateComponent> ent)
{
var bloodstream = Comp<BloodstreamComponent>(ent);
if (!_solutionContainer.ResolveSolution(ent.Owner, bloodstream.BloodSolutionName,
ref bloodstream.BloodSolution, out var bloodSolution))
{
return 1;
return 1f;
}
return bloodSolution.Volume / bloodSolution.MaxVolume;
return Math.Max(bloodSolution.Volume.Float() / bloodSolution.MaxVolume.Float(), ent.Comp.MinimumBloodVolume);
}
public FixedPoint4 BloodFlow(Entity<HeartrateComponent> ent)
[Access(typeof(HeartSystem), typeof(HeartrateComponent))]
public float ComputeVascularTone(Entity<HeartrateComponent> ent)
{
if (!ent.Comp.Running)
{
var evt = new GetStoppedCirculationModifier(ent.Comp.StoppedBloodCirculationModifier);
RaiseLocalEvent(ent, ref evt);
return evt.Modifier;
}
var baseEv = new BaseVascularToneEvent(1f);
RaiseLocalEvent(ent, ref baseEv);
FixedPoint4 modifier = 1;
var modifiedEv = new ModifiedVascularToneEvent(baseEv.Tone);
RaiseLocalEvent(ent, ref modifiedEv);
FixedPoint4 strain = HeartStrain(ent);
var strainModifier = ent.Comp.CirculationStrainModifierCoefficient * strain + ent.Comp.CirculationStrainModifierConstant;
modifier *= strainModifier;
modifier *= FixedPoint2.Max( ent.Comp.MinimumDamageCirculationModifier, FixedPoint2.New(1d) - (ent.Comp.Damage / ent.Comp.MaxDamage) );
return modifier;
return Math.Max(modifiedEv.Tone, ent.Comp.MinimumVascularTone);
}
public FixedPoint2 BloodCirculation(Entity<HeartrateComponent> ent)
[Access(typeof(HeartSystem), typeof(HeartrateComponent))]
public float ComputeMetabolicRate(Entity<HeartrateComponent> ent)
{
FixedPoint4 volume = BloodVolume(ent);
var flow = BloodFlow(ent);
var baseEv = new BaseMetabolicRateEvent(1f);
RaiseLocalEvent(ent, ref baseEv);
return FixedPoint2.Min((FixedPoint2)(volume * flow), 1);
var modifiedEv = new ModifiedMetabolicRateEvent(baseEv.Rate);
RaiseLocalEvent(ent, ref modifiedEv);
return modifiedEv.Rate;
}
public FixedPoint2 BloodOxygenation(Entity<HeartrateComponent> ent)
[Access(typeof(HeartSystem), typeof(HeartrateComponent))]
public float ComputeLungFunction(Entity<HeartrateComponent> ent)
{
var circulation = BloodCirculation(ent);
var damageable = Comp<DamageableComponent>(ent);
if (!damageable.Damage.DamageDict.TryGetValue(ent.Comp.AsphyxiationDamage, out var asphyxiationAmount))
return circulation;
var baseEv = new BaseLungFunctionEvent(1f);
RaiseLocalEvent(ent, ref baseEv);
var oxygenationModifier = FixedPoint2.Clamp(1 - (asphyxiationAmount / ent.Comp.AsphyxiationThreshold), 0, 1);
var modifiedEv = new ModifiedLungFunctionEvent(baseEv.Function);
RaiseLocalEvent(ent, ref modifiedEv);
var evt = new GetOxygenationModifier(oxygenationModifier);
RaiseLocalEvent(ent, ref evt);
return evt.Modifier * circulation;
return Math.Max(modifiedEv.Function, ent.Comp.MinimumLungFunction);
}
private FixedPoint2 RecomputeHeartStrain(Entity<HeartrateComponent> ent)
private float OxygenBalance(Entity<HeartrateComponent> ent)
{
var pain = _pain.GetShock(ent.Owner);
var strain = pain / ent.Comp.ShockStrainDivisor;
var evt = new GetStrainEvent(strain);
RaiseLocalEvent(ent, ref evt);
return FixedPoint2.Clamp(evt.Strain, FixedPoint2.Zero, ent.Comp.MaximumStrain);
return ent.Comp.OxygenSupply / ent.Comp.OxygenDemand;
}
public FixedPoint2 HeartStrain(Entity<HeartrateComponent> ent)
public float Strain(Entity<HeartrateComponent> ent)
{
return ent.Comp.Strain;
return Math.Max(ent.Comp.CompensationStrainCoefficient * ent.Comp.Compensation + ent.Comp.CompensationStrainConstant, 0f);
}
private void OnBloodstreamGetStrain(Entity<BloodstreamComponent> ent, ref GetStrainEvent args)
{
var heartrate = Comp<HeartrateComponent>(ent);
var volume = BloodVolume((ent, heartrate));
var damageable = Comp<DamageableComponent>(ent);
if (damageable.Damage.DamageDict.TryGetValue(heartrate.AsphyxiationDamage, out var asphyxiationAmount))
{
volume *= FixedPoint2.Min(1 - (asphyxiationAmount / heartrate.AsphyxiationThreshold), 1);
}
var strainDelta = FixedPoint2.Zero;
if (volume <= ent.Comp.BloodlossThreshold)
strainDelta += 1;
if (volume <= ent.Comp.BloodlossThreshold/2)
strainDelta += 1;
if (volume <= ent.Comp.BloodlossThreshold/3)
strainDelta += 1;
if (volume <= ent.Comp.BloodlossThreshold/4)
strainDelta += 1;
args.Strain += strainDelta;
}
public (FixedPoint2, FixedPoint2) BloodPressure(Entity<HeartrateComponent> ent)
{
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
var rand = new System.Random(seed);
var volume = BloodCirculation(ent);
var deviationA = rand.Next(-ent.Comp.BloodPressureDeviation, ent.Comp.BloodPressureDeviation);
var deviationB = rand.Next(-ent.Comp.BloodPressureDeviation, ent.Comp.BloodPressureDeviation);
var upper = FixedPoint2.Max((ent.Comp.SystolicBase * volume + deviationA), 0).Int();
var lower = FixedPoint2.Max((ent.Comp.DiastolicBase * volume + deviationB), 0).Int();
return (upper, lower);
}
public FixedPoint2 HeartRate(Entity<HeartrateComponent> ent)
public int HeartRate(Entity<HeartrateComponent> ent)
{
if (!ent.Comp.Running)
return 0;
@@ -373,8 +319,65 @@ public sealed partial class HeartSystem : EntitySystem
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
var rand = new System.Random(seed);
var strain = HeartStrain(ent);
return (FixedPoint2.Max(strain, 0)/ent.Comp.HeartRateStrainDivisor) * ent.Comp.HeartRateStrainFactor + ent.Comp.HeartRateBase + rand.Next(-ent.Comp.HeartRateDeviation, ent.Comp.HeartRateDeviation);
var deviation = rand.Next(-ent.Comp.HeartRateDeviation, ent.Comp.HeartRateDeviation);
return Math.Max((int)MathHelper.Lerp(ent.Comp.HeartRateFullPerfusion, ent.Comp.HeartRateNoPerfusion, Strain(ent)) + deviation, 0);
}
public (int, int) BloodPressure(Entity<HeartrateComponent> ent)
{
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
var rand = new System.Random(seed);
var deviationA = rand.Next(-ent.Comp.BloodPressureDeviation, ent.Comp.BloodPressureDeviation);
var deviationB = rand.Next(-ent.Comp.BloodPressureDeviation, ent.Comp.BloodPressureDeviation);
var upper = (int)Math.Max((ent.Comp.SystolicBase * ent.Comp.Perfusion + deviationA), 0);
var lower = (int)Math.Max((ent.Comp.DiastolicBase * ent.Comp.Perfusion + deviationB), 0);
return (upper, lower);
}
public int Etco2(Entity<HeartrateComponent> ent)
{
var seed = SharedRandomExtensions.HashCodeCombine(new() { (int)_timing.CurTick.Value, GetNetEntity(ent).Id });
var rand = new System.Random(seed);
var deviation = rand.Next(-ent.Comp.Etco2Deviation, ent.Comp.Etco2Deviation);
var baseEtco2 = ent.Comp.Etco2Base * ComputeExhaleEfficiencyModifier(ent);
return Math.Max((int)baseEtco2 + deviation, 0);
}
public float ComputeExhaleEfficiencyModifier(Entity<HeartrateComponent> ent)
{
return Math.Max(ent.Comp.Perfusion, ent.Comp.MinimumPerfusionEtco2Modifier) * ComputeRespiratoryRateModifier(ent);
}
public float ComputeRespiratoryRateModifier(Entity<HeartrateComponent> ent)
{
var balance = ent.Comp.OxygenSupply / ent.Comp.OxygenDemand;
var rate = Math.Max(1f/(ent.Comp.RespiratoryRateCoefficient * balance) + ent.Comp.RespiratoryRateConstant, ent.Comp.MinimumRespiratoryRateModifier);
var modifiedEv = new ModifiedRespiratoryRateEvent(rate);
RaiseLocalEvent(ent, ref modifiedEv);
return modifiedEv.Rate;
}
public int RespiratoryRate(Entity<HeartrateComponent> ent)
{
var breathDuration = ent.Comp.RespiratoryRateNormalBreath * ComputeRespiratoryRateModifier(ent);
if (breathDuration <= 0f)
return 0;
return (int)(60f / breathDuration);
}
public FixedPoint2 Spo2(Entity<HeartrateComponent> ent)
{
return FixedPoint2.Clamp(OxygenBalance(ent), 0, 1);
}
public void TryRestartHeart(Entity<HeartrateComponent?> ent)

View File

@@ -24,14 +24,17 @@ public sealed class HeartrateAlertsSystem : EntitySystem
var heartrate = Comp<HeartrateComponent>(ent);
if (heartrate.Running)
{
var strain = FixedPoint2.Min(_heart.HeartStrain((ent, heartrate)), ent.Comp.MaxStrain);
_alerts.ShowAlert(ent.Owner, ent.Comp.StrainAlert, (short)strain.Int());
var range = _alerts.GetSeverityRange(ent.Comp.StrainAlert);
var min = _alerts.GetMinSeverity(ent.Comp.StrainAlert);
var max = _alerts.GetMaxSeverity(ent.Comp.StrainAlert);
var severity = Math.Min(min + (short)Math.Round(range * _heart.Strain((ent.Owner, heartrate))), max);
_alerts.ShowAlert(ent.Owner, ent.Comp.StrainAlert, (short)severity);
}
else
{
_alerts.ShowAlert(ent.Owner, ent.Comp.StoppedAlert);
}
}
private void OnMapInit(Entity<HeartrateAlertsComponent> ent, ref MapInitEvent args)

View File

@@ -1,4 +1,3 @@
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@@ -10,18 +9,55 @@ namespace Content.Shared._Offbrand.Wounds;
[Access(typeof(HeartSystem))]
public sealed partial class HeartrateComponent : Component
{
/// <summary>
/// The damage type to use when computing oxygenation from the lungs
/// </summary>
[DataField(required: true)]
public ProtoId<DamageTypePrototype> AsphyxiationDamage;
[DataField, AutoNetworkedField]
public float Perfusion = 1f;
/// <summary>
/// The amount of <see cref="AsphyxiationDamage" /> at which lung oxygenation is considered to be 0%
/// </summary>
[DataField(required: true)]
public FixedPoint2 AsphyxiationThreshold;
[DataField, AutoNetworkedField]
public float OxygenSupply = 1f;
[DataField, AutoNetworkedField]
public float OxygenDemand = 1f;
[DataField, AutoNetworkedField]
public float Compensation = 1f;
[DataField(required: true)]
public float RespiratoryRateCoefficient;
[DataField(required: true)]
public float RespiratoryRateConstant;
[DataField(required: true)]
public float CompensationCoefficient;
[DataField(required: true)]
public float CompensationConstant;
[DataField(required: true)]
public float CompensationStrainCoefficient;
[DataField(required: true)]
public float CompensationStrainConstant;
[DataField]
public float MinimumCardiacOutput = 0.005f;
[DataField]
public float MinimumVascularTone = 0.005f;
[DataField]
public float MinimumBloodVolume = 0.005f;
[DataField]
public float MinimumLungFunction = 0.005f;
[DataField]
public float MinimumRespiratoryRateModifier = 0.5f;
[DataField]
public float MinimumPerfusionEtco2Modifier = 0.5f;
#region Damage
/// <summary>
/// The maximum amount of damage that this entity's heart can take
/// </summary>
@@ -42,37 +78,20 @@ public sealed partial class HeartrateComponent : Component
/// </summary>
[DataField(required: true)]
public SortedDictionary<FixedPoint2, (double Chance, FixedPoint2 Amount)> StrainDamageThresholds;
#endregion
#region Heartstop
[DataField, AutoNetworkedField]
public bool Running = true;
/// <summary>
/// The coefficient for how much strain contributes to the blood circulation
/// The status effect to apply when the heart is not running
/// </summary>
[DataField(required: true)]
public FixedPoint4 CirculationStrainModifierCoefficient;
/// <summary>
/// The constant for how much strain contributes to the blood circulation
/// </summary>
[DataField(required: true)]
public FixedPoint4 CirculationStrainModifierConstant;
/// <summary>
/// How much blood circulation there is when the heart is stopped
/// </summary>
[DataField(required: true)]
public FixedPoint2 StoppedBloodCirculationModifier;
/// <summary>
/// Blood circulation will never go below this number from damage
/// </summary>
[DataField(required: true)]
public FixedPoint2 MinimumDamageCirculationModifier;
/// <summary>
/// Shock will be divided by this much before contributing to strain
/// </summary>
[DataField(required: true)]
public FixedPoint2 ShockStrainDivisor;
public EntProtoId HeartStoppedEffect;
#endregion
#region Fluff Numbers
/// <summary>
/// How much reported blood pressure deviates
/// </summary>
@@ -83,13 +102,13 @@ public sealed partial class HeartrateComponent : Component
/// Base number for reported systolic blood pressure
/// </summary>
[DataField(required: true)]
public FixedPoint2 SystolicBase;
public int SystolicBase;
/// <summary>
/// Base number for reported diastolic blood pressure
/// </summary>
[DataField(required: true)]
public FixedPoint2 DiastolicBase;
public int DiastolicBase;
/// <summary>
/// How much the reported heartrate deviates
@@ -98,28 +117,59 @@ public sealed partial class HeartrateComponent : Component
public int HeartRateDeviation;
/// <summary>
/// The base of the reported heartrate
/// The base of the reported heartrate at 100% perfusion
/// </summary>
[DataField(required: true)]
public FixedPoint2 HeartRateBase;
public float HeartRateFullPerfusion;
/// <summary>
/// Strain will be multiplied with this to contribute to the reported heartrate
/// The base of the reported heartrate at 0% perfusion
/// </summary>
[DataField(required: true)]
public FixedPoint2 HeartRateStrainFactor;
public float HeartRateNoPerfusion;
/// <summary>
/// Strain will be divided by this number before being multiplied to contribute to the reported heartrate
/// The base of the reported etco2
/// </summary>
[DataField(required: true)]
public FixedPoint2 HeartRateStrainDivisor;
public float Etco2Base;
/// <summary>
/// The maximum amount of strain possible
/// The deviation of the reported etco2
/// </summary>
[DataField(required: true)]
public FixedPoint2 MaximumStrain;
public int Etco2Deviation;
/// <summary>
/// The assumed time per normal breath in seconds
/// </summary>
[DataField(required: true)]
public float RespiratoryRateNormalBreath;
/// <summary>
/// The name of the Etco2 vital
/// </summary>
[DataField(required: true)]
public LocId Etco2Name;
/// <summary>
/// The name of the gas purged by Etco2
/// </summary>
[DataField(required: true)]
public LocId Etco2GasName;
/// <summary>
/// The name of the Spo2 vital
/// </summary>
[DataField(required: true)]
public LocId Spo2Name;
/// <summary>
/// The name of the gas circulated by Spo2
/// </summary>
[DataField(required: true)]
public LocId Spo2GasName;
#endregion
[DataField, AutoNetworkedField]
public float UpdateIntervalMultiplier = 1f;
@@ -130,21 +180,49 @@ public sealed partial class HeartrateComponent : Component
[ViewVariables]
public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier;
[DataField, AutoNetworkedField]
public FixedPoint2 Strain = FixedPoint2.Zero;
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
[AutoNetworkedField]
public TimeSpan? LastUpdate;
[DataField, AutoNetworkedField]
public bool Running = true;
#region VV Conveniences
private HeartSystem _system => IoCManager.Resolve<IEntityManager>().System<HeartSystem>();
/// <summary>
/// The status effect to apply when the heart is not running
/// </summary>
[DataField(required: true)]
public EntProtoId HeartStoppedEffect;
[ViewVariables]
private float VV000BloodVolume => _system.ComputeBloodVolume((Owner, this));
[ViewVariables]
private float VV001VascularTone => _system.ComputeVascularTone((Owner, this));
[ViewVariables]
private float VV002CardiacOutput => _system.CardiacOutput((Owner, this));
[ViewVariables]
private float VV003Perfusion => MathF.Min(VV000BloodVolume, MathF.Min(VV001VascularTone, VV002CardiacOutput));
[ViewVariables]
private float VV004LungFunction => _system.ComputeLungFunction((Owner, this));
[ViewVariables]
private float VV005GrossSupply => VV004LungFunction * VV003Perfusion;
[ViewVariables]
private float VV006Demand => _system.ComputeMetabolicRate((Owner, this));
[ViewVariables]
private float VV007Compensation => _system.ComputeCompensation((Owner, this), VV005GrossSupply, VV006Demand);
[ViewVariables]
private float VV008NetSupply => VV005GrossSupply * VV007Compensation;
[ViewVariables]
private float VV009Balance => VV008NetSupply / VV006Demand;
[ViewVariables]
private float VV010Strain => _system.Strain((Owner, this));
[ViewVariables]
private float VV011RespiratoryRateModifier => _system.ComputeRespiratoryRateModifier((Owner, this));
#endregion
}
[RegisterComponent]
@@ -155,29 +233,6 @@ public sealed partial class HeartDefibrillatableComponent : Component
public LocId TargetIsDead = "heart-defibrillatable-target-is-dead";
}
[RegisterComponent]
[Access(typeof(HeartSystem))]
public sealed partial class HeartStopOnHypovolemiaComponent : Component
{
/// <summary>
/// How likely the heart is to stop when the volume threshold is dipped below
/// </summary>
[DataField(required: true)]
public float Chance;
/// <summary>
/// The maximum volume at which the heart can stop from hypovolemia
/// </summary>
[DataField(required: true)]
public FixedPoint2 VolumeThreshold;
/// <summary>
/// The warning issued by defibrillators if the heart is restarted with hypovolemia
/// </summary>
[DataField]
public LocId Warning = "heart-defibrillatable-target-hypovolemia";
}
[RegisterComponent]
[Access(typeof(HeartSystem))]
public sealed partial class HeartStopOnHighStrainComponent : Component
@@ -198,31 +253,68 @@ public sealed partial class HeartStopOnHighStrainComponent : Component
/// The warning issued by defibrillators if the heart is restarted with high strain
/// </summary>
[DataField]
public LocId Warning = "heart-defibrillatable-target-pain";
public LocId Warning = "heart-defibrillatable-target-strain";
}
[RegisterComponent]
[Access(typeof(HeartSystem))]
public sealed partial class HeartStopOnBrainHealthComponent : Component
{
/// <summary>
/// How likely the heart is to stop when the brain health threshold is exceeded
/// </summary>
[DataField(required: true)]
public float Chance;
/// <summary>
/// Raised on an entity to determine the base vascular tone
/// </summary>
[ByRefEvent]
public record struct BaseVascularToneEvent(float Tone);
/// <summary>
/// The minimum threshold at which the heart can stop from brain health
/// </summary>
[DataField(required: true)]
public FixedPoint2 Threshold;
/// <summary>
/// Raised on an entity to determine modifiers to the vascular tone
/// </summary>
[ByRefEvent]
public record struct ModifiedVascularToneEvent(float Tone);
/// <summary>
/// The warning issued by defibrillators if the heart is restarted with severe brain damage
/// </summary>
[DataField]
public LocId Warning = "heart-defibrillatable-target-brain-damage";
}
/// <summary>
/// Raised on an entity to determine the base lung function
/// </summary>
[ByRefEvent]
public record struct BaseLungFunctionEvent(float Function);
/// <summary>
/// Raised on an entity to determine modifiers to the lung function
/// </summary>
[ByRefEvent]
public record struct ModifiedLungFunctionEvent(float Function);
/// <summary>
/// Raised on an entity to determine the base cardiac output
/// </summary>
[ByRefEvent]
public record struct BaseCardiacOutputEvent(float Output);
/// <summary>
/// Raised on an entity to determine modifiers to the cardiac output
/// </summary>
[ByRefEvent]
public record struct ModifiedCardiacOutputEvent(float Output);
/// <summary>
/// Raised on an entity to determine the base metabolic rate
/// </summary>
[ByRefEvent]
public record struct BaseMetabolicRateEvent(float Rate);
/// <summary>
/// Raised on an entity to determine modifiers to the metabolic rate
/// </summary>
[ByRefEvent]
public record struct ModifiedMetabolicRateEvent(float Rate);
/// <summary>
/// Raised on an entity to determine modifiers to the respiratory rate
/// </summary>
[ByRefEvent]
public record struct ModifiedRespiratoryRateEvent(float Rate);
/// <summary>
/// Raised on an entity to update its respiratory rate
/// </summary>
[ByRefEvent]
public record struct ApplyRespiratoryRateModifiersEvent(float BreathRate, float PurgeRate);
/// <summary>
/// Raised on an entity to determine if the heart should stop
@@ -230,18 +322,6 @@ public sealed partial class HeartStopOnBrainHealthComponent : Component
[ByRefEvent]
public record struct HeartBeatEvent(bool Stop);
/// <summary>
/// Raised on an entity to determine its oxygenation modifier from air
/// </summary>
[ByRefEvent]
public record struct GetOxygenationModifier(FixedPoint2 Modifier);
/// <summary>
/// Raised on an entity to determine its circulation modifier when stopped
/// </summary>
[ByRefEvent]
public record struct GetStoppedCirculationModifier(FixedPoint2 Modifier);
[ByRefEvent]
public record struct AfterStrainChangedEvent;

View File

@@ -1,5 +1,6 @@
using Content.Shared.Alert;
using Content.Shared.Atmos;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
@@ -22,6 +23,18 @@ public sealed partial class LungDamageComponent : Component
/// </summary>
[DataField(required: true), AutoNetworkedField]
public FixedPoint2 Damage;
/// <summary>
/// The damage type to use when computing oxygenation from the lungs
/// </summary>
[DataField(required: true)]
public ProtoId<DamageTypePrototype> AsphyxiationDamage;
/// <summary>
/// The amount of <see cref="AsphyxiationDamage" /> at which lung oxygenation is considered to be 0%
/// </summary>
[DataField(required: true)]
public FixedPoint2 AsphyxiationThreshold;
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]

View File

@@ -1,4 +1,5 @@
using Content.Shared.Alert;
using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Rejuvenate;
using Robust.Shared.Timing;
@@ -16,6 +17,7 @@ public sealed class LungDamageSystem : EntitySystem
SubscribeLocalEvent<LungDamageComponent, BeforeBreathEvent>(OnBeforeBreath);
SubscribeLocalEvent<LungDamageComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<LungDamageComponent, BaseLungFunctionEvent>(OnBaseLungFunction);
SubscribeLocalEvent<LungDamageAlertsComponent, AfterLungDamageChangedEvent>(OnLungDamageChanged);
SubscribeLocalEvent<LungDamageAlertsComponent, ComponentShutdown>(OnAlertsShutdown);
}
@@ -66,6 +68,22 @@ public sealed class LungDamageSystem : EntitySystem
RaiseLocalEvent(ent, ref evt);
}
private void OnBaseLungFunction(Entity<LungDamageComponent> ent, ref BaseLungFunctionEvent args)
{
var damage = ent.Comp.Damage.Float() / ent.Comp.MaxDamage.Float();
var health = 1f - damage;
var damageable = Comp<DamageableComponent>(ent);
if (!damageable.Damage.DamageDict.TryGetValue(ent.Comp.AsphyxiationDamage, out var asphyxiationAmount))
{
args.Function *= health - damage;
return;
}
var airSupply = Math.Clamp(1f - (asphyxiationAmount.Float() / ent.Comp.AsphyxiationThreshold.Float()), 0, 1);
args.Function *= health * airSupply;
}
private void OnLungDamageChanged(Entity<LungDamageAlertsComponent> ent, ref AfterLungDamageChangedEvent args)
{
var lungDamage = Comp<LungDamageComponent>(ent);

View File

@@ -61,6 +61,20 @@ public sealed partial class PainComponent : Component
public TimeSpan? LastUpdate;
}
[RegisterComponent]
[Access(typeof(PainSystem))]
public sealed partial class PainMetabolicRateComponent : Component
{
[DataField(required: true)]
public float QuadraticFactor;
[DataField(required: true)]
public float LinearFactor;
[DataField(required: true)]
public float ConstantFactor;
}
/// <summary>
/// Raised on an entity after its shock has changed
/// </summary>

View File

@@ -16,6 +16,8 @@ public sealed partial class PainSystem : EntitySystem
SubscribeLocalEvent<PainComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PainComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<PainComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
SubscribeLocalEvent<PainMetabolicRateComponent, BaseMetabolicRateEvent>(OnBaseMetabolicRate);
}
private void OnApplyMetabolicMultiplier(Entity<PainComponent> ent, ref ApplyMetabolicMultiplierEvent args)
@@ -67,7 +69,7 @@ public sealed partial class PainSystem : EntitySystem
var evt = new AfterShockChangeEvent();
RaiseLocalEvent(uid, ref evt);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(uid);
var overlays = new PotentiallyUpdateDamageOverlayEvent(uid);
RaiseLocalEvent(uid, ref overlays, true);
Dirty(uid, pain);
@@ -82,7 +84,7 @@ public sealed partial class PainSystem : EntitySystem
var evt = new AfterShockChangeEvent();
RaiseLocalEvent(ent, ref evt);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
@@ -99,6 +101,12 @@ public sealed partial class PainSystem : EntitySystem
ent.Comp.LastUpdate = _timing.CurTime;
}
private void OnBaseMetabolicRate(Entity<PainMetabolicRateComponent> ent, ref BaseMetabolicRateEvent args)
{
var shock = GetShock(ent.Owner).Float();
args.Rate += MathF.Max(ent.Comp.QuadraticFactor * (shock * shock) + ent.Comp.LinearFactor * shock + ent.Comp.ConstantFactor, 0f);
}
public FixedPoint2 GetShock(Entity<PainComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp, false))

View File

@@ -76,7 +76,7 @@ public sealed partial class ShockThresholdsSystem : EntitySystem
{
args.State = ThresholdHelpers.Max(ent.Comp.CurrentMobState, args.State);
var overlays = new bPotentiallyUpdateDamageOverlayEventb(ent);
var overlays = new PotentiallyUpdateDamageOverlayEvent(ent);
RaiseLocalEvent(ent, ref overlays, true);
}
}

View File

@@ -160,12 +160,6 @@ public record struct HealWoundsEvent(DamageSpecifier Damage);
[ByRefEvent]
public record struct GetPainEvent(FixedPoint2 Pain);
/// <summary>
/// Raised on an entity to get the sum total of heart strain
/// </summary>
[ByRefEvent]
public record struct GetStrainEvent(FixedPoint2 Strain);
/// <summary>
/// Raised on an entity to get the amount it should bleed
/// </summary>

View File

@@ -21,4 +21,4 @@ public record struct BeforeDamageCommitEvent(DamageSpecifier Damage, bool ForceR
/// Raised when the values for a damage overlay may have changed
/// </summary>
[ByRefEvent]
public record struct bPotentiallyUpdateDamageOverlayEventb(EntityUid Target);
public record struct PotentiallyUpdateDamageOverlayEvent(EntityUid Target);

View File

@@ -10,50 +10,44 @@ namespace Content.Shared._Offbrand.Wounds;
public sealed partial class WoundableHealthAnalyzerData
{
[DataField]
public double BrainHealth;
public float BrainHealth;
[DataField]
public AttributeRating BrainHealthRating;
[DataField]
public double HeartHealth;
[DataField]
public AttributeRating HeartHealthRating;
public float HeartHealth;
[DataField]
public (int, int) BloodPressure;
[DataField]
public AttributeRating BloodPressureRating;
[DataField]
public double BloodOxygenation;
[DataField]
public AttributeRating BloodOxygenationRating;
[DataField]
public double BloodFlow;
[DataField]
public AttributeRating BloodFlowRating;
[DataField]
public int HeartRate;
[DataField]
public AttributeRating HeartRateRating;
public int Etco2;
[DataField]
public double LungHealth;
public int RespiratoryRate;
[DataField]
public AttributeRating LungHealthRating;
public float Spo2;
[DataField]
public float LungHealth;
[DataField]
public bool AnyVitalCritical;
[DataField]
public LocId Etco2Name;
[DataField]
public LocId Etco2GasName;
[DataField]
public LocId Spo2Name;
[DataField]
public LocId Spo2GasName;
[DataField]
public List<string>? Wounds;
@@ -62,17 +56,19 @@ public sealed partial class WoundableHealthAnalyzerData
[DataField]
public bool NonMedicalReagents;
[DataField]
public MetricRanking Ranking;
}
[Serializable, NetSerializable]
public enum AttributeRating : byte
public enum MetricRanking : byte
{
Good = 0,
Okay = 1,
Poor = 2,
Bad = 3,
Awful = 4,
Dangerous = 5,
Dangerous = 4,
}
public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
@@ -84,16 +80,6 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
protected const string MedicineGroup = "Medicine";
private AttributeRating RateHigherIsBetter(double value)
{
return RateHigherIsWorse(1d - value);
}
private AttributeRating RateHigherIsWorse(double value)
{
return (AttributeRating)(byte)Math.Clamp(Math.Floor(6d * value), 0d, 5d);
}
public List<string>? SampleWounds(EntityUid uid)
{
if (!_statusEffects.TryEffectsWithComp<AnalyzableWoundComponent>(uid, out var wounds))
@@ -119,6 +105,17 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
return null;
}
public MetricRanking Ranking(Entity<HeartrateComponent> ent)
{
var strain = (MetricRanking)Math.Min((int)MathF.Round(4f * _heart.Strain(ent)), 4);
var spo2 = (MetricRanking)Math.Min((int)MathF.Round(4f * (1f - _heart.Spo2(ent).Float())), 4);
if ((byte)spo2 > (byte)strain)
return spo2;
return strain;
}
public WoundableHealthAnalyzerData? TakeSample(EntityUid uid, bool withWounds = true)
{
if (!HasComp<WoundableComponent>(uid))
@@ -133,14 +130,10 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
if (!TryComp<LungDamageComponent>(uid, out var lungDamage))
return null;
var brainHealth = 1d - ((double)brainDamage.Damage / (double)brainDamage.MaxDamage);
var heartHealth = 1d - ((double)heartrate.Damage / (double)heartrate.MaxDamage);
var lungHealth = 1d - ((double)lungDamage.Damage / (double)lungDamage.MaxDamage);
var strain = _heart.HeartStrain((uid, heartrate)).Double() / 4d;
var brainHealth = 1f - ((float)brainDamage.Damage / (float)brainDamage.MaxDamage);
var heartHealth = 1f - ((float)heartrate.Damage / (float)heartrate.MaxDamage);
var lungHealth = 1f - ((float)lungDamage.Damage / (float)lungDamage.MaxDamage);
var (upper, lower) = _heart.BloodPressure((uid, heartrate));
var oxygenation = _heart.BloodOxygenation((uid, heartrate)).Double();
var circulation = _heart.BloodCirculation((uid, heartrate)).Double();
var flow = _heart.BloodFlow((uid, heartrate)).Double();
var hasNonMedical = false;
var reagents = withWounds ? SampleReagents(uid, out hasNonMedical) : null;
@@ -148,23 +141,22 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
return new WoundableHealthAnalyzerData()
{
BrainHealth = brainHealth,
BrainHealthRating = RateHigherIsBetter(brainHealth),
HeartHealth = heartHealth,
HeartHealthRating = RateHigherIsBetter(heartHealth),
BloodPressure = (upper.Int(), lower.Int()),
BloodPressureRating = RateHigherIsBetter(circulation),
BloodOxygenation = oxygenation,
BloodOxygenationRating = RateHigherIsBetter(oxygenation),
BloodFlow = flow,
BloodFlowRating = RateHigherIsBetter(flow),
HeartRate = _heart.HeartRate((uid, heartrate)).Int(),
HeartRateRating = !heartrate.Running ? AttributeRating.Dangerous : RateHigherIsWorse(strain),
BloodPressure = (upper, lower),
HeartRate = _heart.HeartRate((uid, heartrate)),
Etco2 = _heart.Etco2((uid, heartrate)),
RespiratoryRate = _heart.RespiratoryRate((uid, heartrate)),
Spo2 = _heart.Spo2((uid, heartrate)).Float(),
LungHealth = lungHealth,
LungHealthRating = RateHigherIsBetter(lungHealth),
AnyVitalCritical = _shockThresholds.IsCritical(uid) || _brainDamage.IsCritical(uid) || _heart.IsCritical(uid),
Etco2Name = heartrate.Etco2Name,
Etco2GasName = heartrate.Etco2GasName,
Spo2Name = heartrate.Spo2Name,
Spo2GasName = heartrate.Spo2GasName,
Wounds = withWounds ? SampleWounds(uid) : null,
Reagents = reagents,
NonMedicalReagents = hasNonMedical,
Ranking = Ranking((uid, heartrate)),
};
}
}