resimmed offmed
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user