diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
index 2f4057cee7..be8eaf482f 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
@@ -46,18 +46,21 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
index 52007c15c3..47b03351cb 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -41,14 +41,15 @@ namespace Content.Client.HealthAnalyzer.UI
private readonly Tooltips.StatusTooltip _statusTooltip = new();
private readonly Tooltips.BrainHealthTooltip _brainHealthTooltip = new();
private readonly Tooltips.BloodPressureTooltip _bloodPressureTooltip = new();
- private readonly Tooltips.BloodOxygenationTooltip _bloodOxygenationTooltip = new();
private readonly Tooltips.HeartRateTooltip _heartRateTooltip = new();
private readonly Tooltips.HeartHealthTooltip _heartHealthTooltip = new();
private readonly Tooltips.LungHealthTooltip _lungHealthTooltip = new();
- private readonly Tooltips.BloodFlowTooltip _bloodFlowTooltip = new();
private readonly Tooltips.BloodTooltip _bloodTooltip = new();
private readonly Tooltips.TemperatureTooltip _temperatureTooltip = new();
private readonly Tooltips.DamageTooltip _damageTooltip = new();
+ private readonly Tooltips.SpO2Tooltip _spo2Tooltip = new();
+ private readonly Tooltips.EtCO2Tooltip _etco2Tooltip = new();
+ private readonly Tooltips.RespiratoryRateTooltip _respiratoryRateTooltip = new();
// End Offbrand
public HealthAnalyzerWindow()
@@ -65,14 +66,15 @@ namespace Content.Client.HealthAnalyzer.UI
StatusButton.TooltipSupplier = _ => _statusTooltip;
BrainHealthButton.TooltipSupplier = _ => _brainHealthTooltip;
BloodPressureButton.TooltipSupplier = _ => _bloodPressureTooltip;
- BloodOxygenationButton.TooltipSupplier = _ => _bloodOxygenationTooltip;
HeartRateButton.TooltipSupplier = _ => _heartRateTooltip;
- BloodFlowButton.TooltipSupplier = _ => _bloodFlowTooltip;
HeartHealthButton.TooltipSupplier = _ => _heartHealthTooltip;
TemperatureButton.TooltipSupplier = _ => _temperatureTooltip;
DamageButton.TooltipSupplier = _ => _damageTooltip;
BloodButton.TooltipSupplier = _ => _bloodTooltip;
LungHealthButton.TooltipSupplier = _ => _lungHealthTooltip;
+ SpO2Button.TooltipSupplier = _ => _spo2Tooltip;
+ EtCO2Button.TooltipSupplier = _ => _etco2Tooltip;
+ RespiratoryRateButton.TooltipSupplier = _ => _respiratoryRateTooltip;
// End Offbrand
}
@@ -91,12 +93,12 @@ namespace Content.Client.HealthAnalyzer.UI
// Begin Offbrand Tooltips
_brainHealthTooltip.Update(msg);
- _bloodPressureTooltip.Update(msg);
- _bloodOxygenationTooltip.Update(msg, (target.Value, damageable, _entityManager.GetComponentOrNull(target)));
- _heartRateTooltip.Update(msg, (target.Value, damageable, _entityManager.GetComponentOrNull(target)));
- _bloodFlowTooltip.Update(msg);
+ _heartRateTooltip.Update(msg);
_heartHealthTooltip.Update(msg);
_temperatureTooltip.Update(msg, (target.Value, _entityManager.GetComponentOrNull(target)));
+ _spo2Tooltip.Update(msg);
+ _etco2Tooltip.Update(msg);
+ _respiratoryRateTooltip.Update(msg);
// End Offbrand Tooltips
// Scan Mode
@@ -236,40 +238,47 @@ namespace Content.Client.HealthAnalyzer.UI
}
BrainHealthText.Visible = true;
BrainHealthLabel.Visible = true;
- BrainHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-brain-health-value", ("value", $"{woundable.BrainHealth * 100:F1}"), ("rating", woundable.BrainHealthRating));
+ BrainHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-brain-health-value", ("value", $"{woundable.BrainHealth * 100:F1}"));
BrainHealthButton.Visible = true;
HeartHealthText.Visible = true;
HeartHealthLabel.Visible = true;
- HeartHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-health-value", ("value", $"{woundable.HeartHealth * 100:F1}"), ("rating", woundable.HeartHealthRating));
+ HeartHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-health-value", ("value", $"{woundable.HeartHealth * 100:F1}"));
HeartHealthButton.Visible = true;
HeartRateText.Visible = true;
HeartRateLabel.Visible = true;
- HeartRateLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-rate-value", ("value", woundable.HeartRate), ("rating", woundable.HeartRateRating));
+ HeartRateLabel.Text = Loc.GetString("health-analyzer-window-entity-heart-rate-value", ("value", woundable.HeartRate));
HeartRateButton.Visible = true;
- BloodOxygenationText.Visible = true;
- BloodOxygenationLabel.Visible = true;
- BloodOxygenationLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-oxygenation-value", ("value", $"{woundable.BloodOxygenation * 100:F1}"), ("rating", woundable.BloodOxygenationRating));
- BloodOxygenationButton.Visible = true;
-
- BloodFlowText.Visible = true;
- BloodFlowLabel.Visible = true;
- BloodFlowLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-flow-value", ("value", $"{woundable.BloodFlow * 100:F1}"), ("rating", woundable.BloodFlowRating));
- BloodFlowButton.Visible = true;
-
var (systolic, diastolic) = woundable.BloodPressure;
BloodPressureText.Visible = true;
BloodPressureLabel.Visible = true;
- BloodPressureLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-pressure-value", ("systolic", systolic), ("diastolic", diastolic), ("rating", woundable.BloodPressureRating));
+ BloodPressureLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-pressure-value", ("systolic", systolic), ("diastolic", diastolic));
BloodPressureButton.Visible = true;
LungHealthText.Visible = true;
LungHealthLabel.Visible = true;
- LungHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-lung-health-value", ("value", $"{woundable.LungHealth * 100:F1}"), ("rating", woundable.LungHealthRating));
+ LungHealthLabel.Text = Loc.GetString("health-analyzer-window-entity-lung-health-value", ("value", $"{woundable.LungHealth * 100:F1}"));
LungHealthButton.Visible = true;
+ SpO2Text.Visible = true;
+ SpO2Text.Text = Loc.GetString("health-analyzer-window-entity-spo2-text", ("spo2", woundable.Spo2Name));
+ SpO2Label.Visible = true;
+ SpO2Label.Text = Loc.GetString("health-analyzer-window-entity-spo2-value", ("value", $"{woundable.Spo2 * 100:F1}"));
+ SpO2Button.Visible = true;
+
+ EtCO2Text.Visible = true;
+ EtCO2Text.Text = Loc.GetString("health-analyzer-window-entity-etco2-text", ("etco2", woundable.Etco2Name));
+ EtCO2Label.Visible = true;
+ EtCO2Label.Text = Loc.GetString("health-analyzer-window-entity-etco2-value", ("value", $"{woundable.Etco2}"));
+ EtCO2Button.Visible = true;
+
+ RespiratoryRateText.Visible = true;
+ RespiratoryRateLabel.Visible = true;
+ RespiratoryRateLabel.Text = Loc.GetString("health-analyzer-window-entity-respiratory-rate-value", ("value", $"{woundable.RespiratoryRate}"));
+ RespiratoryRateButton.Visible = true;
+
BloodLabel.Visible = false;
BloodText.Visible = false;
BloodButton.Visible = false;
@@ -278,25 +287,28 @@ namespace Content.Client.HealthAnalyzer.UI
{
BrainHealthLabel.Visible = false;
BloodPressureLabel.Visible = false;
- BloodOxygenationLabel.Visible = false;
HeartRateLabel.Visible = false;
HeartHealthLabel.Visible = false;
- BloodFlowLabel.Visible = false;
LungHealthLabel.Visible = false;
BrainHealthText.Visible = false;
BloodPressureText.Visible = false;
- BloodOxygenationText.Visible = false;
- BloodFlowText.Visible = false;
HeartRateText.Visible = false;
HeartHealthText.Visible = false;
LungHealthText.Visible = false;
BrainHealthButton.Visible = false;
BloodPressureButton.Visible = false;
- BloodOxygenationButton.Visible = false;
- BloodFlowButton.Visible = false;
HeartRateButton.Visible = false;
HeartHealthButton.Visible = false;
LungHealthButton.Visible = false;
+ SpO2Text.Visible = false;
+ SpO2Label.Visible = false;
+ SpO2Button.Visible = false;
+ EtCO2Text.Visible = false;
+ EtCO2Label.Visible = false;
+ EtCO2Button.Visible = false;
+ RespiratoryRateText.Visible = false;
+ RespiratoryRateLabel.Visible = false;
+ RespiratoryRateButton.Visible = false;
BloodLabel.Visible = true;
BloodText.Visible = true;
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodFlowTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodFlowTooltip.xaml.cs
deleted file mode 100644
index 6d5a7463e6..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodFlowTooltip.xaml.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.MedicalScanner;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class BloodFlowTooltip : PanelContainer
-{
- public BloodFlowTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-
- public void Update(HealthAnalyzerScannedUserMessage msg)
- {
- if (msg.WoundableData is not { } woundable)
- return;
-
- Label.Text = Loc.GetString("health-analyzer-blood-flow-tooltip", ("heartrate", woundable.HeartRate), ("health", $"{woundable.HeartHealth * 100:F1}"));
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodOxygenationTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodOxygenationTooltip.xaml
deleted file mode 100644
index 29945b5e4c..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodOxygenationTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodOxygenationTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodOxygenationTooltip.xaml.cs
deleted file mode 100644
index a76ccc9b45..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodOxygenationTooltip.xaml.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Content.Shared._Offbrand.Wounds;
-using Content.Shared.Damage;
-using Content.Shared.FixedPoint;
-using Content.Shared.MedicalScanner;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class BloodOxygenationTooltip : PanelContainer
-{
- public BloodOxygenationTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-
- public void Update(HealthAnalyzerScannedUserMessage msg, Entity ent)
- {
- if (msg.WoundableData is not { } woundable)
- return;
-
- if (ent.Comp2?.AsphyxiationDamage is not { } damageType)
- return;
-
- var asphyxiation = FixedPoint2.Zero;
- ent.Comp1.Damage.DamageDict.TryGetValue(damageType, out asphyxiation);
- var (systolic, diastolic) = woundable.BloodPressure;
-
- Label.Text = Loc.GetString("health-analyzer-blood-saturation-tooltip", ("rating", woundable.BloodOxygenationRating), ("systolic", systolic), ("diastolic", diastolic), ("asphyxiation", asphyxiation));
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.cs
new file mode 100644
index 0000000000..f96a403bbd
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.cs
@@ -0,0 +1,6 @@
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class BloodPressureTooltip : StaticTooltip
+{
+ public override LocId Text => "health-analyzer-blood-pressure-tooltip";
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.xaml
deleted file mode 100644
index 29945b5e4c..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.xaml.cs
deleted file mode 100644
index a0cc4e7a44..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodPressureTooltip.xaml.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.MedicalScanner;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class BloodPressureTooltip : PanelContainer
-{
- public BloodPressureTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-
- public void Update(HealthAnalyzerScannedUserMessage msg)
- {
- if (msg.WoundableData is not { } woundable)
- return;
-
- Label.Text = Loc.GetString("health-analyzer-blood-pressure-tooltip", ("flow", $"{woundable.BloodFlow * 100:F1}"));
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.cs
new file mode 100644
index 0000000000..b3c3dfd995
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.cs
@@ -0,0 +1,6 @@
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class BloodTooltip : StaticTooltip
+{
+ public override LocId Text => "health-analyzer-blood-tooltip";
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.xaml
deleted file mode 100644
index c56a6bf390..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.xaml.cs
deleted file mode 100644
index 4db8005446..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodTooltip.xaml.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class BloodTooltip : PanelContainer
-{
- public BloodTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.cs
new file mode 100644
index 0000000000..7b37b8a0b5
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.cs
@@ -0,0 +1,14 @@
+using Content.Shared.MedicalScanner;
+
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class BrainHealthTooltip : UpdatableTooltip
+{
+ public override void Update(HealthAnalyzerScannedUserMessage msg)
+ {
+ if (msg.WoundableData is not { } woundable)
+ return;
+
+ Label.Text = Loc.GetString("health-analyzer-brain-health-tooltip", ("dead", woundable.BrainHealth <= 0), ("spo2", $"{woundable.Spo2 * 100:F1}"));
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.xaml
deleted file mode 100644
index 29945b5e4c..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.xaml.cs
deleted file mode 100644
index 9ad93b05ce..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/BrainHealthTooltip.xaml.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.MedicalScanner;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class BrainHealthTooltip : PanelContainer
-{
- public BrainHealthTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-
- public void Update(HealthAnalyzerScannedUserMessage msg)
- {
- if (msg.WoundableData is not { } woundable)
- return;
-
- Label.Text = Loc.GetString("health-analyzer-brain-health-tooltip", ("dead", woundable.BrainHealth <= 0), ("rating", woundable.BrainHealthRating), ("saturation", $"{woundable.BloodOxygenation * 100:F1}"));
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.cs
new file mode 100644
index 0000000000..fb5203668c
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.cs
@@ -0,0 +1,6 @@
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class DamageTooltip : StaticTooltip
+{
+ public override LocId Text => "health-analyzer-damage-tooltip";
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.xaml
deleted file mode 100644
index 2cb2ce3f6b..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.xaml.cs
deleted file mode 100644
index 21a299d81a..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/DamageTooltip.xaml.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class DamageTooltip : PanelContainer
-{
- public DamageTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/EtCO2Tooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/EtCO2Tooltip.cs
new file mode 100644
index 0000000000..72e1f34151
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/EtCO2Tooltip.cs
@@ -0,0 +1,14 @@
+using Content.Shared.MedicalScanner;
+
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class EtCO2Tooltip : UpdatableTooltip
+{
+ public override void Update(HealthAnalyzerScannedUserMessage msg)
+ {
+ if (msg.WoundableData is not { } woundable)
+ return;
+
+ Label.Text = Loc.GetString("health-analyzer-etco2-tooltip", ("gas", woundable.Etco2GasName), ("etco2", woundable.Etco2Name));
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.cs
new file mode 100644
index 0000000000..d2f96500f5
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.cs
@@ -0,0 +1,14 @@
+using Content.Shared.MedicalScanner;
+
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class HeartHealthTooltip : UpdatableTooltip
+{
+ public override void Update(HealthAnalyzerScannedUserMessage msg)
+ {
+ if (msg.WoundableData is not { } woundable)
+ return;
+
+ Label.Text = Loc.GetString("health-analyzer-heart-health-tooltip", ("heartrate", woundable.HeartRate));
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.xaml.cs
deleted file mode 100644
index dc60197631..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.xaml.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.MedicalScanner;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class HeartHealthTooltip : PanelContainer
-{
- public HeartHealthTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-
- public void Update(HealthAnalyzerScannedUserMessage msg)
- {
- if (msg.WoundableData is not { } woundable)
- return;
-
- Label.Text = Loc.GetString("health-analyzer-heart-health-tooltip", ("heartrate", woundable.HeartRate));
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.cs
new file mode 100644
index 0000000000..4eaa8c8638
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.cs
@@ -0,0 +1,14 @@
+using Content.Shared.MedicalScanner;
+
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class HeartRateTooltip : UpdatableTooltip
+{
+ public override void Update(HealthAnalyzerScannedUserMessage msg)
+ {
+ if (msg.WoundableData is not { } woundable)
+ return;
+
+ Label.Text = Loc.GetString("health-analyzer-heart-rate-tooltip", ("spo2", woundable.Spo2Name));
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.xaml
deleted file mode 100644
index 29945b5e4c..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.xaml.cs
deleted file mode 100644
index 334557f628..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartRateTooltip.xaml.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Shared._Offbrand.Wounds;
-using Content.Shared.Damage;
-using Content.Shared.FixedPoint;
-using Content.Shared.MedicalScanner;
-using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.XAML;
-using Robust.Client.UserInterface;
-
-namespace Content.Client.HealthAnalyzer.UI.Tooltips;
-
-[GenerateTypedNameReferences]
-public sealed partial class HeartRateTooltip : PanelContainer
-{
- public HeartRateTooltip()
- {
- RobustXamlLoader.Load(this);
- }
-
- public void Update(HealthAnalyzerScannedUserMessage msg, Entity ent)
- {
- if (msg.WoundableData is not { } woundable)
- return;
-
- if (ent.Comp2?.AsphyxiationDamage is not { } damageType)
- return;
-
- var asphyxiation = FixedPoint2.Zero;
- ent.Comp1.Damage.DamageDict.TryGetValue(damageType, out asphyxiation);
-
- Label.Text = Loc.GetString("health-analyzer-heart-rate-tooltip", ("asphyxiation", asphyxiation));
- }
-}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.cs
new file mode 100644
index 0000000000..bdbffc39d3
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.cs
@@ -0,0 +1,6 @@
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class LungHealthTooltip : StaticTooltip
+{
+ public override LocId Text => "health-analyzer-lung-health-tooltip";
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml
deleted file mode 100644
index c77cc2662c..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/RespiratoryRateTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/RespiratoryRateTooltip.xaml.cs
new file mode 100644
index 0000000000..34d9d81941
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/RespiratoryRateTooltip.xaml.cs
@@ -0,0 +1,14 @@
+using Content.Shared.MedicalScanner;
+
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class RespiratoryRateTooltip : UpdatableTooltip
+{
+ public override void Update(HealthAnalyzerScannedUserMessage msg)
+ {
+ if (msg.WoundableData is not { } woundable)
+ return;
+
+ Label.Text = Loc.GetString("health-analyzer-respiratory-rate-tooltip", ("etco2gas", woundable.Etco2GasName), ("etco2", woundable.Etco2Name), ("spo2gas", woundable.Spo2GasName), ("spo2", woundable.Spo2Name));
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/SpO2Tooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/SpO2Tooltip.cs
new file mode 100644
index 0000000000..cdb9c4a0d1
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/SpO2Tooltip.cs
@@ -0,0 +1,14 @@
+using Content.Shared.MedicalScanner;
+
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class SpO2Tooltip : UpdatableTooltip
+{
+ public override void Update(HealthAnalyzerScannedUserMessage msg)
+ {
+ if (msg.WoundableData is not { } woundable)
+ return;
+
+ Label.Text = Loc.GetString("health-analyzer-spo2-tooltip", ("gas", woundable.Spo2GasName), ("spo2", woundable.Spo2Name));
+ }
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/BloodFlowTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/StaticTooltip.xaml
similarity index 100%
rename from Content.Client/HealthAnalyzer/UI/Tooltips/BloodFlowTooltip.xaml
rename to Content.Client/HealthAnalyzer/UI/Tooltips/StaticTooltip.xaml
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/StaticTooltip.xaml.cs
similarity index 54%
rename from Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml.cs
rename to Content.Client/HealthAnalyzer/UI/Tooltips/StaticTooltip.xaml.cs
index 640f9cfd58..6d098c32d9 100644
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/StaticTooltip.xaml.cs
@@ -1,3 +1,4 @@
+using Content.Shared.MedicalScanner;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -6,10 +7,14 @@ using Robust.Client.UserInterface;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
[GenerateTypedNameReferences]
-public sealed partial class LungHealthTooltip : PanelContainer
+[Virtual]
+public partial class StaticTooltip : PanelContainer
{
- public LungHealthTooltip()
+ public StaticTooltip()
{
RobustXamlLoader.Load(this);
+ Label.Text = Loc.GetString(Text);
}
+
+ public virtual LocId Text => throw new NotImplementedException();
}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.cs
new file mode 100644
index 0000000000..0d9d190e37
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.cs
@@ -0,0 +1,6 @@
+namespace Content.Client.HealthAnalyzer.UI.Tooltips;
+
+public sealed partial class StatusTooltip : StaticTooltip
+{
+ public override LocId Text => "health-analyzer-status-tooltip";
+}
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.xaml
deleted file mode 100644
index 6457a79174..0000000000
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.xaml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/UpdatableTooltip.xaml
similarity index 73%
rename from Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.xaml
rename to Content.Client/HealthAnalyzer/UI/Tooltips/UpdatableTooltip.xaml
index 29945b5e4c..114cbfeecc 100644
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/HeartHealthTooltip.xaml
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/UpdatableTooltip.xaml
@@ -3,6 +3,6 @@
Orientation="Vertical"
RectClipContent="True"
Margin="4">
-
+
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/UpdatableTooltip.xaml.cs
similarity index 57%
rename from Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.xaml.cs
rename to Content.Client/HealthAnalyzer/UI/Tooltips/UpdatableTooltip.xaml.cs
index 58ef9b7be5..b90d83133a 100644
--- a/Content.Client/HealthAnalyzer/UI/Tooltips/StatusTooltip.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/UpdatableTooltip.xaml.cs
@@ -1,3 +1,4 @@
+using Content.Shared.MedicalScanner;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -6,10 +7,15 @@ using Robust.Client.UserInterface;
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
[GenerateTypedNameReferences]
-public sealed partial class StatusTooltip : PanelContainer
+[Virtual]
+public partial class UpdatableTooltip : PanelContainer
{
- public StatusTooltip()
+ public UpdatableTooltip()
{
RobustXamlLoader.Load(this);
}
+
+ public virtual void Update(HealthAnalyzerScannedUserMessage msg)
+ {
+ }
}
diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
index 4dac478727..6b52ab7881 100644
--- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
+++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
@@ -239,9 +239,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
}
else if (sensor.WoundableData is { } woundableSummary)
{
- var worstRating = Math.Max((byte)woundableSummary.BloodPressureRating, Math.Max((byte)woundableSummary.BloodOxygenationRating, (byte)woundableSummary.HeartRateRating));
- var index = MathF.Round(4f * ((float)worstRating)/5f);
- specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "health" + index);
+ specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), $"health{(byte)woundableSummary.Ranking}");
}
// End Offbrand Additions
@@ -330,10 +328,10 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
if (sensor.WoundableData is { } woundable)
{
- vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-heart-rate", ("rate", woundable.HeartRate), ("rating", woundable.HeartRateRating)) });
+ vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-heart-rate", ("rate", woundable.HeartRate)) });
var (systolic, diastolic) = woundable.BloodPressure;
- vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-blood-pressure", ("systolic", systolic), ("diastolic", diastolic), ("rating", woundable.BloodPressureRating)) });
- vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-oxygenation", ("oxygenation", $"{woundable.BloodOxygenation * 100:F1}"), ("rating", woundable.BloodOxygenationRating)) });
+ vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-blood-pressure", ("systolic", systolic), ("diastolic", diastolic)) });
+ vitalsContainer.AddChild(new RichTextLabel() { Text = Loc.GetString("offbrand-crew-monitoring-spo2", ("value", $"{woundable.Spo2 * 100:F1}"), ("spo2", woundable.Spo2Name)) });
}
mainContainer.AddChild(vitalsContainer);
diff --git a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs
index 1ea661386a..22ae257bdb 100644
--- a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs
+++ b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs
@@ -34,7 +34,7 @@ public sealed class DamageOverlayUiController : UIController
SubscribeLocalEvent(OnPlayerDetached);
SubscribeLocalEvent(OnMobStateChanged);
SubscribeLocalEvent(OnThresholdCheck);
- SubscribeLocalEvent(OnPotentiallyUpdateDamageOverlay); // Offbrand
+ SubscribeLocalEvent(OnPotentiallyUpdateDamageOverlay); // Offbrand
}
private void OnPlayerAttach(LocalPlayerAttachedEvent args)
@@ -86,7 +86,7 @@ public sealed class DamageOverlayUiController : UIController
TryUpdateWoundableOverlays(entity);
}
- private void OnPotentiallyUpdateDamageOverlay(ref bPotentiallyUpdateDamageOverlayEventb args)
+ private void OnPotentiallyUpdateDamageOverlay(ref PotentiallyUpdateDamageOverlayEvent args)
{
if (args.Target != _playerManager.LocalEntity)
return;
@@ -113,7 +113,7 @@ public sealed class DamageOverlayUiController : UIController
{
_overlay.CritLevel = FixedPoint2.Clamp(brainDamage.Damage / maxBrain, 0, 1).Float();
_overlay.PainLevel = FixedPoint2.Clamp(_pain.GetShock((entity, pain)) / maxShock, 0, 1).Float();
- _overlay.OxygenLevel = FixedPoint2.Clamp(1 - _heart.BloodOxygenation((entity, heartrate)), 0, 1).Float();
+ _overlay.OxygenLevel = FixedPoint2.Clamp(1 - _heart.Spo2((entity, heartrate)), 0, 1).Float();
_overlay.DeadLevel = 0;
break;
}
diff --git a/Content.Client/_Offbrand/Overlays/HeartateOverlay.cs b/Content.Client/_Offbrand/Overlays/HeartrateOverlay.cs
similarity index 89%
rename from Content.Client/_Offbrand/Overlays/HeartateOverlay.cs
rename to Content.Client/_Offbrand/Overlays/HeartrateOverlay.cs
index a7c05a4bda..b44239ef4a 100644
--- a/Content.Client/_Offbrand/Overlays/HeartateOverlay.cs
+++ b/Content.Client/_Offbrand/Overlays/HeartrateOverlay.cs
@@ -18,6 +18,7 @@ public sealed class HeartrateOverlay : Overlay
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
+ private readonly HeartSystem _heart;
private readonly SharedTransformSystem _transform;
private readonly SpriteSystem _sprite;
private readonly StatusIconSystem _statusIcon;
@@ -32,6 +33,7 @@ public sealed class HeartrateOverlay : Overlay
private static readonly SpriteSpecifier HudPoor = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_poor");
private static readonly SpriteSpecifier HudBad = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_bad");
private static readonly SpriteSpecifier HudDanger = new SpriteSpecifier.Rsi(new("/Textures/_Offbrand/heart_rate_hud.rsi"), "hud_danger");
+ private static readonly IReadOnlyList Severities = new List() { HudGood, HudOkay, HudPoor, HudBad, HudDanger };
public HeartrateOverlay()
{
@@ -40,19 +42,17 @@ public sealed class HeartrateOverlay : Overlay
_transform = _entityManager.System();
_sprite = _entityManager.System();
_statusIcon = _entityManager.System();
+ _heart = _entityManager.System();
}
private SpriteSpecifier GetIcon(Entity ent)
{
- var strain = ent.Comp.Strain;
- return strain.Double() switch {
- _ when !ent.Comp.Running => HudStopped,
- >= 4 => HudDanger,
- >= 3 => HudBad,
- >= 2 => HudPoor,
- >= 1 => HudOkay,
- _ => HudGood,
- };
+ if (!ent.Comp.Running)
+ return HudStopped;
+
+ var max = 4;
+ var severity = Math.Min((int)Math.Round(max * _heart.Strain(ent)), max);
+ return Severities[severity];
}
protected override void Draw(in OverlayDrawArgs args)
diff --git a/Content.Server/Body/Components/RespiratorComponent.cs b/Content.Server/Body/Components/RespiratorComponent.cs
index 2ba7b04b3e..599548bcc8 100644
--- a/Content.Server/Body/Components/RespiratorComponent.cs
+++ b/Content.Server/Body/Components/RespiratorComponent.cs
@@ -14,7 +14,7 @@ namespace Content.Server.Body.Components
/// Volume of our breath in liters
///
[DataField]
- public float BreathVolume = Atmospherics.BreathVolume;
+ public float BreathVolume = 0.75f; // Offbrand
///
/// How much of the gas we inhale is metabolized? Value range is (0, 1]
@@ -33,7 +33,7 @@ namespace Content.Server.Body.Components
/// so a full cycle takes twice as long.
///
[DataField]
- public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
+ public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2.5); // Offbrand
///
/// Multiplier applied to for adjusting based on metabolic rate multiplier.
@@ -41,18 +41,48 @@ namespace Content.Server.Body.Components
[DataField]
public float UpdateIntervalMultiplier = 1f;
+ ///
+ /// Offbrand - Multiplier applied to for adjusting based on body respiratory rate
+ ///
+ [DataField]
+ public float BreathRateMultiplier = 1f;
+
+ ///
+ /// Offbrand - Multiplier applied to exhalation to determine how efficient the purging of gases from the body is
+ ///
+ [DataField]
+ public float ExhaleEfficacyModifier = 1f;
+
+ ///
+ /// Offbrand - Multiplier that determines if an entity is hyperventilating (should audibly breathe)
+ ///
+ [DataField]
+ public float HyperventilationThreshold = 0.6f;
+
+ ///
+ /// Offbrand - Multiplier applied to for adjusting based on body respiratory rate
+ ///
+ [ViewVariables]
+ public float AdjustedBreathVolume => BreathVolume * BreathRateMultiplier * BreathRateMultiplier;
+
+ ///
+ /// Adjusted update interval based only on body factors, no e.g. stasis
+ ///
+ [ViewVariables]
+ public TimeSpan BodyAdjustedUpdateInterval => UpdateInterval * BreathRateMultiplier; // Offbrand
+
///
/// Adjusted update interval based off of the multiplier value.
///
[ViewVariables]
- public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier;
+ public TimeSpan OverallAdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier * BreathRateMultiplier; // Offbrand
///
/// Saturation level. Reduced by UpdateInterval each tick.
/// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration.
///
[DataField]
- public float Saturation = 5.0f;
+ public float Saturation = 8.0f; // Offbrand
///
/// At what level of saturation will you begin to suffocate?
@@ -61,7 +91,7 @@ namespace Content.Server.Body.Components
public float SuffocationThreshold;
[DataField]
- public float MaxSaturation = 5.0f;
+ public float MaxSaturation = 8.0f; // Offbrand
[DataField]
public float MinSaturation = -2.0f;
diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs
index eca008d8f4..55351a2e19 100644
--- a/Content.Server/Body/Systems/RespiratorSystem.cs
+++ b/Content.Server/Body/Systems/RespiratorSystem.cs
@@ -20,6 +20,7 @@ using Content.Shared.Mobs.Systems;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
+using Content.Shared._Offbrand.Wounds; // Offbrand
namespace Content.Server.Body.Systems;
@@ -49,6 +50,7 @@ public sealed class RespiratorSystem : EntitySystem
UpdatesAfter.Add(typeof(MetabolizerSystem));
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnApplyMetabolicMultiplier);
+ SubscribeLocalEvent(OnApplyRespiratoryRateModifiers);
// BodyComp stuff
SubscribeLocalEvent(OnGasInhaled);
@@ -60,7 +62,7 @@ public sealed class RespiratorSystem : EntitySystem
private void OnMapInit(Entity ent, ref MapInitEvent args)
{
- ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.AdjustedUpdateInterval;
+ ent.Comp.NextUpdate = _gameTiming.CurTime + ent.Comp.OverallAdjustedUpdateInterval;
}
public override void Update(float frameTime)
@@ -73,14 +75,14 @@ public sealed class RespiratorSystem : EntitySystem
if (_gameTiming.CurTime < respirator.NextUpdate)
continue;
- respirator.NextUpdate += respirator.AdjustedUpdateInterval;
+ respirator.NextUpdate += respirator.OverallAdjustedUpdateInterval; // Offbrand
if (_mobState.IsDead(uid))
continue;
- UpdateSaturation(uid, -(float)respirator.UpdateInterval.TotalSeconds, respirator);
+ UpdateSaturation(uid, -(float)respirator.BodyAdjustedUpdateInterval.TotalSeconds, respirator); // Offbrand
- if (!_mobState.IsIncapacitated(uid)) // cannot breathe in crit.
+ if (!_mobState.IsIncapacitated(uid) || HasComp(uid)) // Offbrand - simplemobs get crit behaviour, heartmobs get hyperventilation
{
switch (respirator.Status)
{
@@ -95,10 +97,10 @@ public sealed class RespiratorSystem : EntitySystem
}
}
- // Begin Offbrand - Respirators gasp when their heart has stopped
+ // Begin Offbrand - Respirators gasp when hyperventilating
var isSuffocating = respirator.Saturation < respirator.SuffocationThreshold;
- var shouldGaspFromHeart = TryComp(uid, out var heartrate) && !heartrate.Running;
- if (isSuffocating || shouldGaspFromHeart)
+ var hyperventilation = respirator.BreathRateMultiplier <= respirator.HyperventilationThreshold;
+ if (isSuffocating || hyperventilation)
{
if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown)
{
@@ -116,7 +118,7 @@ public sealed class RespiratorSystem : EntitySystem
continue;
}
}
- // End Offbrand - Respirators gasp when their heart has stopped
+ // End Offbrand - Respirators gasp when hyperventilating
StopSuffocation((uid, respirator));
respirator.SuffocationCycles = 0;
@@ -141,7 +143,7 @@ public sealed class RespiratorSystem : EntitySystem
return;
// Begin Offbrand
- var breathEv = new Content.Shared._Offbrand.Wounds.BeforeBreathEvent(entity.Comp.BreathVolume);
+ var breathEv = new Content.Shared._Offbrand.Wounds.BeforeBreathEvent(entity.Comp.AdjustedBreathVolume); // Offbrand - modify breath volume
RaiseLocalEvent(entity, ref breathEv);
var gas = ev.Gas.RemoveVolume(breathEv.BreathVolume);
@@ -299,6 +301,7 @@ public sealed class RespiratorSystem : EntitySystem
public void RemoveGasFromBody(Entity ent, GasMixture gas)
{
var outGas = new GasMixture(gas.Volume);
+ var respirator = Comp(ent); // Offbrand
var organs = _bodySystem.GetBodyOrganEntityComps((ent, ent.Comp));
if (organs.Count == 0)
@@ -306,8 +309,7 @@ public sealed class RespiratorSystem : EntitySystem
foreach (var (organUid, lung, _) in organs)
{
- _atmosSys.Merge(outGas, lung.Air);
- lung.Air.Clear();
+ _atmosSys.Merge(outGas, lung.Air.RemoveRatio(respirator.ExhaleEfficacyModifier * 1.1f)); // Offbrand - efficacy, 1.1 magic constant is to unstuck 0.01u of exhalants if the body is imperfect
if (_solutionContainerSystem.ResolveSolution(organUid, lung.SolutionName, ref lung.Solution))
_solutionContainerSystem.RemoveAllSolution(lung.Solution.Value);
@@ -434,6 +436,14 @@ public sealed class RespiratorSystem : EntitySystem
ent.Comp.UpdateIntervalMultiplier = args.Multiplier;
}
+ // Begin Offbrand
+ private void OnApplyRespiratoryRateModifiers(Entity ent, ref ApplyRespiratoryRateModifiersEvent args)
+ {
+ ent.Comp.BreathRateMultiplier = args.BreathRate;
+ ent.Comp.ExhaleEfficacyModifier = args.PurgeRate;
+ }
+ // End Offbrand
+
private void OnGasInhaled(Entity entity, ref InhaledGasEvent args)
{
if (args.Handled)
diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs
index 536679192d..be75306921 100644
--- a/Content.Server/Zombies/ZombieSystem.Transform.cs
+++ b/Content.Server/Zombies/ZombieSystem.Transform.cs
@@ -152,10 +152,9 @@ public sealed partial class ZombieSystem
{
RemComp(target);
RemComp(target);
- RemComp(target);
RemComp(target);
- RemComp(target);
RemComp(target);
+ RemComp(target);
RemComp(target);
RemComp(target);
RemComp(target);
diff --git a/Content.Server/_Offbrand/Wounds/WoundableHealthAnalyzerSystem.cs b/Content.Server/_Offbrand/Wounds/WoundableHealthAnalyzerSystem.cs
index 5759f1eb9b..dbc8225dba 100644
--- a/Content.Server/_Offbrand/Wounds/WoundableHealthAnalyzerSystem.cs
+++ b/Content.Server/_Offbrand/Wounds/WoundableHealthAnalyzerSystem.cs
@@ -1,5 +1,7 @@
+using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Shared._Offbrand.Wounds;
+using Content.Shared.Atmos;
using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry.EntitySystems;
@@ -11,6 +13,7 @@ namespace Content.Server._Offbrand.Wounds;
public sealed class WoundableHealthAnalyzerSystem : SharedWoundableHealthAnalyzerSystem
{
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedBodySystem _body = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
@@ -62,6 +65,29 @@ public sealed class WoundableHealthAnalyzerSystem : SharedWoundableHealthAnalyze
}
}
+ foreach (var lung in _body.GetBodyOrganEntityComps(uid))
+ {
+ foreach (var gasId in Enum.GetValues())
+ {
+ var idx = (int) gasId;
+ var moles = lung.Comp1.Air[idx];
+ if (moles <= 0)
+ continue;
+
+ if (_atmosphere.GasReagents[idx] is not { } reagent)
+ continue;
+
+ var amount = FixedPoint2.New(moles * Atmospherics.BreathMolesToReagentMultiplier);
+ if (amount <= 0)
+ continue;
+
+ if (!ret.ContainsKey(reagent))
+ ret[reagent] = (0, 0);
+
+ ret[reagent] = (ret[reagent].InBloodstream + amount, ret[reagent].Metabolites);
+ }
+ }
+
return ret;
}
}
diff --git a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs
index e6d1e56056..781d789ad4 100644
--- a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs
+++ b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs
@@ -443,7 +443,7 @@ public sealed class MobThresholdSystem : EntitySystem
// Begin Offbrand
private void MobThresholdMapInit(Entity 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
diff --git a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs
index 8c52aaa031..93205c7e93 100644
--- a/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs
+++ b/Content.Shared/StatusEffectNew/StatusEffectSystem.Relay.cs
@@ -30,15 +30,17 @@ public sealed partial class StatusEffectsSystem
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
- SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
- SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
- SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
+ SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
+ SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
+ SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
+ SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
+ SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
SubscribeLocalEvent(RefRelayStatusEffectEvent); // Offbrand
diff --git a/Content.Shared/_Offbrand/StatusEffects/AssistedCirculationStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/AssistedCirculationStatusEffectComponent.cs
deleted file mode 100644
index 3171f96349..0000000000
--- a/Content.Shared/_Offbrand/StatusEffects/AssistedCirculationStatusEffectComponent.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Shared.FixedPoint;
-
-namespace Content.Shared._Offbrand.StatusEffects;
-
-[RegisterComponent]
-[Access(typeof(AssistedCirculationStatusEffectSystem))]
-public sealed partial class AssistedCirculationStatusEffectComponent : Component
-{
- ///
- /// How much blood circulation to add
- ///
- [DataField(required: true)]
- public FixedPoint2 Amount;
-}
diff --git a/Content.Shared/_Offbrand/StatusEffects/AssistedCirculationStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/AssistedCirculationStatusEffectSystem.cs
deleted file mode 100644
index 173574cfe5..0000000000
--- a/Content.Shared/_Offbrand/StatusEffects/AssistedCirculationStatusEffectSystem.cs
+++ /dev/null
@@ -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>(OnGetStoppedCirculationModifier);
- }
-
- private void OnGetStoppedCirculationModifier(Entity ent, ref StatusEffectRelayedEvent args)
- {
- args.Args = args.Args with { Modifier = FixedPoint2.Clamp(args.Args.Modifier + ent.Comp.Amount, 0, 1) };
- }
-}
diff --git a/Content.Shared/_Offbrand/StatusEffects/BloodOxygenationModifierStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/BloodOxygenationModifierStatusEffectSystem.cs
deleted file mode 100644
index 5290b9e7c7..0000000000
--- a/Content.Shared/_Offbrand/StatusEffects/BloodOxygenationModifierStatusEffectSystem.cs
+++ /dev/null
@@ -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>(OnGetOxygenationModifier);
- }
-
- private void OnGetOxygenationModifier(Entity ent, ref StatusEffectRelayedEvent args)
- {
- var theirs = args.Args.Modifier.Double();
- var ours = ent.Comp.MinimumOxygenation.Double();
-
- args.Args = args.Args with { Modifier = theirs + ours - (theirs * ours) };
- }
-}
diff --git a/Content.Shared/_Offbrand/StatusEffects/BloodOyxgenationModifierStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/BloodOyxgenationModifierStatusEffectComponent.cs
deleted file mode 100644
index 4eedc8f58b..0000000000
--- a/Content.Shared/_Offbrand/StatusEffects/BloodOyxgenationModifierStatusEffectComponent.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Content.Shared.FixedPoint;
-
-namespace Content.Shared._Offbrand.StatusEffects;
-
-[RegisterComponent]
-[Access(typeof(BloodOxygenationModifierStatusEffectSystem))]
-public sealed partial class BloodOxygenationModifierStatusEffectComponent : Component
-{
- ///
- /// The minimum lung oxygenation this status effect guarantees
- ///
- [DataField(required: true)]
- public FixedPoint2 MinimumOxygenation;
-}
diff --git a/Content.Shared/_Offbrand/StatusEffects/CardiacOutputModifierStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/CardiacOutputModifierStatusEffectComponent.cs
new file mode 100644
index 0000000000..941d8a4c40
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/CardiacOutputModifierStatusEffectComponent.cs
@@ -0,0 +1,12 @@
+namespace Content.Shared._Offbrand.StatusEffects;
+
+[RegisterComponent]
+[Access(typeof(CardiacOutputModifierStatusEffectSystem))]
+public sealed partial class CardiacOutputModifierStatusEffectComponent : Component
+{
+ ///
+ /// The minimum cardiac output this status effect guarantees
+ ///
+ [DataField(required: true)]
+ public float Output;
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/CardiacOutputModifierStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/CardiacOutputModifierStatusEffectSystem.cs
new file mode 100644
index 0000000000..e6ff93696d
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/CardiacOutputModifierStatusEffectSystem.cs
@@ -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>(OnModifiedCardiacOutput);
+ }
+
+ private void OnModifiedCardiacOutput(Entity ent, ref StatusEffectRelayedEvent args)
+ {
+ args.Args = args.Args with { Output = MathF.Max(ent.Comp.Output, args.Args.Output) };
+ }
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/LungFunctionModifierStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/LungFunctionModifierStatusEffectComponent.cs
new file mode 100644
index 0000000000..394e82b771
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/LungFunctionModifierStatusEffectComponent.cs
@@ -0,0 +1,12 @@
+namespace Content.Shared._Offbrand.StatusEffects;
+
+[RegisterComponent]
+[Access(typeof(LungFunctionModifierStatusEffectSystem))]
+public sealed partial class LungFunctionModifierStatusEffectComponent : Component
+{
+ ///
+ /// The minimum lung function this status effect guarantees
+ ///
+ [DataField(required: true)]
+ public float Function;
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/LungFunctionModifierStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/LungFunctionModifierStatusEffectSystem.cs
new file mode 100644
index 0000000000..8f15d6465a
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/LungFunctionModifierStatusEffectSystem.cs
@@ -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>(OnModifiedVascularTone);
+ }
+
+ private void OnModifiedVascularTone(Entity ent, ref StatusEffectRelayedEvent args)
+ {
+ args.Args = args.Args with { Function = MathF.Max(ent.Comp.Function, args.Args.Function) };
+ }
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/MetabolicRateModifierStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/MetabolicRateModifierStatusEffectComponent.cs
new file mode 100644
index 0000000000..e7d2554d1a
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/MetabolicRateModifierStatusEffectComponent.cs
@@ -0,0 +1,24 @@
+namespace Content.Shared._Offbrand.StatusEffects;
+
+[RegisterComponent]
+[Access(typeof(MetabolicRateModifierStatusEffectSystem))]
+public sealed partial class MetabolicRateModifierStatusEffectComponent : Component
+{
+ ///
+ /// The modifier applied to the metabolic rate
+ ///
+ [DataField(required: true)]
+ public float Delta;
+
+ ///
+ /// The minimum metabolic rate that can happen as a result of this status effect
+ ///
+ [DataField]
+ public float Min = 1f;
+
+ ///
+ /// The maximum metabolic rate that can happen as a result of this status effect
+ ///
+ [DataField]
+ public float Max = float.PositiveInfinity;
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/MetabolicRateModifierStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/MetabolicRateModifierStatusEffectSystem.cs
new file mode 100644
index 0000000000..10cc10d996
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/MetabolicRateModifierStatusEffectSystem.cs
@@ -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>(OnModifiedMetabolicRate);
+ }
+
+ private void OnModifiedMetabolicRate(Entity ent, ref StatusEffectRelayedEvent args)
+ {
+ args.Args = args.Args with { Rate = Math.Clamp(args.Args.Rate + ent.Comp.Delta, ent.Comp.Min, ent.Comp.Max) };
+ }
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/ModifyBrainDamageChanceStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/ModifyBrainDamageChanceStatusEffectSystem.cs
index 1680ea0447..7da1c6177a 100644
--- a/Content.Shared/_Offbrand/StatusEffects/ModifyBrainDamageChanceStatusEffectSystem.cs
+++ b/Content.Shared/_Offbrand/StatusEffects/ModifyBrainDamageChanceStatusEffectSystem.cs
@@ -20,7 +20,7 @@ public sealed class ModifyBrainDamageChanceStatusEffectSystem : EntitySystem
if (Comp(ent).AppliedTo is not { } target)
return;
- var oxygenation = _heart.BloodOxygenation((target, Comp(target)));
+ var oxygenation = _heart.Spo2((target, Comp(target)));
if (ent.Comp.OxygenationModifierThresholds.LowestMatch(oxygenation) is not { } modifier)
return;
diff --git a/Content.Shared/_Offbrand/StatusEffects/ModifyBrainOxygenDepletionChanceStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/ModifyBrainOxygenDepletionChanceStatusEffectSystem.cs
index 632d22ee39..2874d27ac1 100644
--- a/Content.Shared/_Offbrand/StatusEffects/ModifyBrainOxygenDepletionChanceStatusEffectSystem.cs
+++ b/Content.Shared/_Offbrand/StatusEffects/ModifyBrainOxygenDepletionChanceStatusEffectSystem.cs
@@ -20,7 +20,7 @@ public sealed class ModifyBrainOxygenDepletionChanceStatusEffectSystem : EntityS
if (Comp(ent).AppliedTo is not { } target)
return;
- var oxygenation = _heart.BloodOxygenation((target, Comp(target)));
+ var oxygenation = _heart.Spo2((target, Comp(target)));
if (ent.Comp.OxygenationModifierThresholds.LowestMatch(oxygenation) is not { } modifier)
return;
diff --git a/Content.Shared/_Offbrand/StatusEffects/RespiratoryRateModifierStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/RespiratoryRateModifierStatusEffectComponent.cs
new file mode 100644
index 0000000000..2073ca3a9a
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/RespiratoryRateModifierStatusEffectComponent.cs
@@ -0,0 +1,12 @@
+namespace Content.Shared._Offbrand.StatusEffects;
+
+[RegisterComponent]
+[Access(typeof(RespiratoryRateModifierStatusEffectSystem))]
+public sealed partial class RespiratoryRateModifierStatusEffectComponent : Component
+{
+ ///
+ /// The minimum respiratory rate this status effect guarantees
+ ///
+ [DataField(required: true)]
+ public float Rate;
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/RespiratoryRateModifierStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/RespiratoryRateModifierStatusEffectSystem.cs
new file mode 100644
index 0000000000..20fa00a322
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/RespiratoryRateModifierStatusEffectSystem.cs
@@ -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>(OnModifiedRespiratoryRate);
+ }
+
+ private void OnModifiedRespiratoryRate(Entity ent, ref StatusEffectRelayedEvent args)
+ {
+ args.Args = args.Args with { Rate = MathF.Max(ent.Comp.Rate, args.Args.Rate) };
+ }
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/StrainStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/StrainStatusEffectComponent.cs
deleted file mode 100644
index 237f9e802b..0000000000
--- a/Content.Shared/_Offbrand/StatusEffects/StrainStatusEffectComponent.cs
+++ /dev/null
@@ -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;
-}
diff --git a/Content.Shared/_Offbrand/StatusEffects/StrainStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/StrainStatusEffectSystem.cs
deleted file mode 100644
index 653e59d1fe..0000000000
--- a/Content.Shared/_Offbrand/StatusEffects/StrainStatusEffectSystem.cs
+++ /dev/null
@@ -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>(OnGetStrain);
- }
-
- private void OnGetStrain(Entity ent, ref StatusEffectRelayedEvent args)
- {
- args.Args = args.Args with { Strain = args.Args.Strain + ent.Comp.Delta };
- }
-}
diff --git a/Content.Shared/_Offbrand/StatusEffects/VascularToneModifierStatusEffectComponent.cs b/Content.Shared/_Offbrand/StatusEffects/VascularToneModifierStatusEffectComponent.cs
new file mode 100644
index 0000000000..0fcc8fa497
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/VascularToneModifierStatusEffectComponent.cs
@@ -0,0 +1,12 @@
+namespace Content.Shared._Offbrand.StatusEffects;
+
+[RegisterComponent]
+[Access(typeof(VascularToneModifierStatusEffectSystem))]
+public sealed partial class VascularToneModifierStatusEffectComponent : Component
+{
+ ///
+ /// The minimum vascular tone this status effect guarantees
+ ///
+ [DataField(required: true)]
+ public float Tone;
+}
diff --git a/Content.Shared/_Offbrand/StatusEffects/VascularToneModifierStatusEffectSystem.cs b/Content.Shared/_Offbrand/StatusEffects/VascularToneModifierStatusEffectSystem.cs
new file mode 100644
index 0000000000..73e438c62b
--- /dev/null
+++ b/Content.Shared/_Offbrand/StatusEffects/VascularToneModifierStatusEffectSystem.cs
@@ -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>(OnModifiedVascularTone);
+ }
+
+ private void OnModifiedVascularTone(Entity ent, ref StatusEffectRelayedEvent args)
+ {
+ args.Args = args.Args with { Tone = MathF.Max(ent.Comp.Tone, args.Args.Tone) };
+ }
+}
diff --git a/Content.Shared/_Offbrand/Wounds/BrainDamageSystem.cs b/Content.Shared/_Offbrand/Wounds/BrainDamageSystem.cs
index 12907051fd..1213bb77d8 100644
--- a/Content.Shared/_Offbrand/Wounds/BrainDamageSystem.cs
+++ b/Content.Shared/_Offbrand/Wounds/BrainDamageSystem.cs
@@ -20,6 +20,7 @@ public sealed partial class BrainDamageSystem : EntitySystem
SubscribeLocalEvent(OnSuicide);
SubscribeLocalEvent(OnRejuvenate);
SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnBaseVascularTone);
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(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 ent, ref BaseVascularToneEvent args)
+ {
+ args.Tone *= 1f - ent.Comp.Damage.Float() / ent.Comp.MaxDamage.Float();
+ }
+
private void OnApplyMetabolicMultiplier(Entity 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 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 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);
diff --git a/Content.Shared/_Offbrand/Wounds/BrainDamageThresholdsSystem.cs b/Content.Shared/_Offbrand/Wounds/BrainDamageThresholdsSystem.cs
index 4a730064f7..daedbac607 100644
--- a/Content.Shared/_Offbrand/Wounds/BrainDamageThresholdsSystem.cs
+++ b/Content.Shared/_Offbrand/Wounds/BrainDamageThresholdsSystem.cs
@@ -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);
}
}
diff --git a/Content.Shared/_Offbrand/Wounds/HeartSystem.cs b/Content.Shared/_Offbrand/Wounds/HeartSystem.cs
index 1fa4c92c12..733f17a01c 100644
--- a/Content.Shared/_Offbrand/Wounds/HeartSystem.cs
+++ b/Content.Shared/_Offbrand/Wounds/HeartSystem.cs
@@ -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(OnApplyMetabolicMultiplier);
SubscribeLocalEvent(OnRejuvenate);
- SubscribeLocalEvent(OnBloodstreamGetStrain);
-
- SubscribeLocalEvent(OnHeartBeatHypovolemia);
SubscribeLocalEvent(OnHeartBeatStrain);
- SubscribeLocalEvent(OnHeartBeatBrain);
-
- SubscribeLocalEvent(OnHeartBeatHypovolemiaMessage);
SubscribeLocalEvent(OnHeartBeatStrainMessage);
- SubscribeLocalEvent(OnHeartBeatBrainMessage);
SubscribeLocalEvent(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 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 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(ent)));
- args.Stop = args.Stop || rand.Prob(ent.Comp.Chance) && volume < ent.Comp.VolumeThreshold;
- }
-
private void OnHeartBeatStrain(Entity 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(ent))
return;
- var strain = HeartStrain((ent.Owner, Comp(ent)));
+ var strain = Strain((ent.Owner, Comp(ent)));
args.Stop = args.Stop || rand.Prob(ent.Comp.Chance) && strain > ent.Comp.Threshold;
}
- private void OnHeartBeatBrain(Entity 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(ent))
- return;
-
- var damage = Comp(ent).Damage;
- args.Stop = args.Stop || rand.Prob(ent.Comp.Chance) && damage > ent.Comp.Threshold;
- }
-
- private void OnHeartBeatHypovolemiaMessage(Entity ent, ref BeforeTargetDefibrillatedEvent args)
- {
- var volume = BloodVolume((ent.Owner, Comp(ent)));
- if (volume >= ent.Comp.VolumeThreshold)
- return;
-
- args.Messages.Add(ent.Comp.Warning);
- }
-
private void OnHeartBeatStrainMessage(Entity ent, ref BeforeTargetDefibrillatedEvent args)
{
if (_statusEffects.HasEffectComp(ent))
return;
- var strain = RecomputeHeartStrain((ent.Owner, Comp(ent)));
+ var heartrate = Comp(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 ent, ref BeforeTargetDefibrillatedEvent args)
- {
- if (_statusEffects.HasEffectComp(ent))
- return;
-
- var damage = Comp(ent).Damage;
- if (damage <= ent.Comp.Threshold)
- return;
-
- args.Messages.Add(ent.Comp.Warning);
- }
-
public void ChangeHeartDamage(Entity ent, FixedPoint2 amount)
{
if (!Resolve(ent, ref ent.Comp, false))
@@ -252,120 +201,117 @@ public sealed partial class HeartSystem : EntitySystem
}
}
- public FixedPoint2 BloodVolume(Entity ent)
+ private void RecomputeVitals(Entity 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 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 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 ent)
{
var bloodstream = Comp(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 ent)
+ [Access(typeof(HeartSystem), typeof(HeartrateComponent))]
+ public float ComputeVascularTone(Entity 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 ent)
+ [Access(typeof(HeartSystem), typeof(HeartrateComponent))]
+ public float ComputeMetabolicRate(Entity 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 ent)
+ [Access(typeof(HeartSystem), typeof(HeartrateComponent))]
+ public float ComputeLungFunction(Entity ent)
{
- var circulation = BloodCirculation(ent);
- var damageable = Comp(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 ent)
+ private float OxygenBalance(Entity 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 ent)
+ public float Strain(Entity ent)
{
- return ent.Comp.Strain;
+ return Math.Max(ent.Comp.CompensationStrainCoefficient * ent.Comp.Compensation + ent.Comp.CompensationStrainConstant, 0f);
}
- private void OnBloodstreamGetStrain(Entity ent, ref GetStrainEvent args)
- {
- var heartrate = Comp(ent);
- var volume = BloodVolume((ent, heartrate));
- var damageable = Comp(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 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 ent)
+ public int HeartRate(Entity 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 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 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 ent)
+ {
+ return Math.Max(ent.Comp.Perfusion, ent.Comp.MinimumPerfusionEtco2Modifier) * ComputeRespiratoryRateModifier(ent);
+ }
+
+ public float ComputeRespiratoryRateModifier(Entity 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 ent)
+ {
+ var breathDuration = ent.Comp.RespiratoryRateNormalBreath * ComputeRespiratoryRateModifier(ent);
+ if (breathDuration <= 0f)
+ return 0;
+
+ return (int)(60f / breathDuration);
+ }
+
+ public FixedPoint2 Spo2(Entity ent)
+ {
+ return FixedPoint2.Clamp(OxygenBalance(ent), 0, 1);
}
public void TryRestartHeart(Entity ent)
diff --git a/Content.Shared/_Offbrand/Wounds/HeartrateAlertsSystem.cs b/Content.Shared/_Offbrand/Wounds/HeartrateAlertsSystem.cs
index 4a594881db..6c4fbd2b23 100644
--- a/Content.Shared/_Offbrand/Wounds/HeartrateAlertsSystem.cs
+++ b/Content.Shared/_Offbrand/Wounds/HeartrateAlertsSystem.cs
@@ -24,14 +24,17 @@ public sealed class HeartrateAlertsSystem : EntitySystem
var heartrate = Comp(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 ent, ref MapInitEvent args)
diff --git a/Content.Shared/_Offbrand/Wounds/HeartrateComponent.cs b/Content.Shared/_Offbrand/Wounds/HeartrateComponent.cs
index a0dfbc521d..e4e2c79ab7 100644
--- a/Content.Shared/_Offbrand/Wounds/HeartrateComponent.cs
+++ b/Content.Shared/_Offbrand/Wounds/HeartrateComponent.cs
@@ -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
{
- ///
- /// The damage type to use when computing oxygenation from the lungs
- ///
- [DataField(required: true)]
- public ProtoId AsphyxiationDamage;
+ [DataField, AutoNetworkedField]
+ public float Perfusion = 1f;
- ///
- /// The amount of at which lung oxygenation is considered to be 0%
- ///
- [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
///
/// The maximum amount of damage that this entity's heart can take
///
@@ -42,37 +78,20 @@ public sealed partial class HeartrateComponent : Component
///
[DataField(required: true)]
public SortedDictionary StrainDamageThresholds;
+ #endregion
+
+ #region Heartstop
+ [DataField, AutoNetworkedField]
+ public bool Running = true;
///
- /// The coefficient for how much strain contributes to the blood circulation
+ /// The status effect to apply when the heart is not running
///
[DataField(required: true)]
- public FixedPoint4 CirculationStrainModifierCoefficient;
-
- ///
- /// The constant for how much strain contributes to the blood circulation
- ///
- [DataField(required: true)]
- public FixedPoint4 CirculationStrainModifierConstant;
-
- ///
- /// How much blood circulation there is when the heart is stopped
- ///
- [DataField(required: true)]
- public FixedPoint2 StoppedBloodCirculationModifier;
-
- ///
- /// Blood circulation will never go below this number from damage
- ///
- [DataField(required: true)]
- public FixedPoint2 MinimumDamageCirculationModifier;
-
- ///
- /// Shock will be divided by this much before contributing to strain
- ///
- [DataField(required: true)]
- public FixedPoint2 ShockStrainDivisor;
+ public EntProtoId HeartStoppedEffect;
+ #endregion
+ #region Fluff Numbers
///
/// How much reported blood pressure deviates
///
@@ -83,13 +102,13 @@ public sealed partial class HeartrateComponent : Component
/// Base number for reported systolic blood pressure
///
[DataField(required: true)]
- public FixedPoint2 SystolicBase;
+ public int SystolicBase;
///
/// Base number for reported diastolic blood pressure
///
[DataField(required: true)]
- public FixedPoint2 DiastolicBase;
+ public int DiastolicBase;
///
/// How much the reported heartrate deviates
@@ -98,28 +117,59 @@ public sealed partial class HeartrateComponent : Component
public int HeartRateDeviation;
///
- /// The base of the reported heartrate
+ /// The base of the reported heartrate at 100% perfusion
///
[DataField(required: true)]
- public FixedPoint2 HeartRateBase;
+ public float HeartRateFullPerfusion;
///
- /// Strain will be multiplied with this to contribute to the reported heartrate
+ /// The base of the reported heartrate at 0% perfusion
///
[DataField(required: true)]
- public FixedPoint2 HeartRateStrainFactor;
+ public float HeartRateNoPerfusion;
///
- /// Strain will be divided by this number before being multiplied to contribute to the reported heartrate
+ /// The base of the reported etco2
///
[DataField(required: true)]
- public FixedPoint2 HeartRateStrainDivisor;
+ public float Etco2Base;
///
- /// The maximum amount of strain possible
+ /// The deviation of the reported etco2
///
[DataField(required: true)]
- public FixedPoint2 MaximumStrain;
+ public int Etco2Deviation;
+
+ ///
+ /// The assumed time per normal breath in seconds
+ ///
+ [DataField(required: true)]
+ public float RespiratoryRateNormalBreath;
+
+ ///
+ /// The name of the Etco2 vital
+ ///
+ [DataField(required: true)]
+ public LocId Etco2Name;
+
+ ///
+ /// The name of the gas purged by Etco2
+ ///
+ [DataField(required: true)]
+ public LocId Etco2GasName;
+
+ ///
+ /// The name of the Spo2 vital
+ ///
+ [DataField(required: true)]
+ public LocId Spo2Name;
+
+ ///
+ /// The name of the gas circulated by Spo2
+ ///
+ [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().System();
- ///
- /// The status effect to apply when the heart is not running
- ///
- [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
-{
- ///
- /// How likely the heart is to stop when the volume threshold is dipped below
- ///
- [DataField(required: true)]
- public float Chance;
-
- ///
- /// The maximum volume at which the heart can stop from hypovolemia
- ///
- [DataField(required: true)]
- public FixedPoint2 VolumeThreshold;
-
- ///
- /// The warning issued by defibrillators if the heart is restarted with hypovolemia
- ///
- [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
///
[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
-{
- ///
- /// How likely the heart is to stop when the brain health threshold is exceeded
- ///
- [DataField(required: true)]
- public float Chance;
+///
+/// Raised on an entity to determine the base vascular tone
+///
+[ByRefEvent]
+public record struct BaseVascularToneEvent(float Tone);
- ///
- /// The minimum threshold at which the heart can stop from brain health
- ///
- [DataField(required: true)]
- public FixedPoint2 Threshold;
+///
+/// Raised on an entity to determine modifiers to the vascular tone
+///
+[ByRefEvent]
+public record struct ModifiedVascularToneEvent(float Tone);
- ///
- /// The warning issued by defibrillators if the heart is restarted with severe brain damage
- ///
- [DataField]
- public LocId Warning = "heart-defibrillatable-target-brain-damage";
-}
+///
+/// Raised on an entity to determine the base lung function
+///
+[ByRefEvent]
+public record struct BaseLungFunctionEvent(float Function);
+
+///
+/// Raised on an entity to determine modifiers to the lung function
+///
+[ByRefEvent]
+public record struct ModifiedLungFunctionEvent(float Function);
+
+///
+/// Raised on an entity to determine the base cardiac output
+///
+[ByRefEvent]
+public record struct BaseCardiacOutputEvent(float Output);
+
+///
+/// Raised on an entity to determine modifiers to the cardiac output
+///
+[ByRefEvent]
+public record struct ModifiedCardiacOutputEvent(float Output);
+
+///
+/// Raised on an entity to determine the base metabolic rate
+///
+[ByRefEvent]
+public record struct BaseMetabolicRateEvent(float Rate);
+
+///
+/// Raised on an entity to determine modifiers to the metabolic rate
+///
+[ByRefEvent]
+public record struct ModifiedMetabolicRateEvent(float Rate);
+
+///
+/// Raised on an entity to determine modifiers to the respiratory rate
+///
+[ByRefEvent]
+public record struct ModifiedRespiratoryRateEvent(float Rate);
+
+///
+/// Raised on an entity to update its respiratory rate
+///
+[ByRefEvent]
+public record struct ApplyRespiratoryRateModifiersEvent(float BreathRate, float PurgeRate);
///
/// 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);
-///
-/// Raised on an entity to determine its oxygenation modifier from air
-///
-[ByRefEvent]
-public record struct GetOxygenationModifier(FixedPoint2 Modifier);
-
-///
-/// Raised on an entity to determine its circulation modifier when stopped
-///
-[ByRefEvent]
-public record struct GetStoppedCirculationModifier(FixedPoint2 Modifier);
-
[ByRefEvent]
public record struct AfterStrainChangedEvent;
diff --git a/Content.Shared/_Offbrand/Wounds/LungDamageComponent.cs b/Content.Shared/_Offbrand/Wounds/LungDamageComponent.cs
index 3ab3fa956a..917b11aaac 100644
--- a/Content.Shared/_Offbrand/Wounds/LungDamageComponent.cs
+++ b/Content.Shared/_Offbrand/Wounds/LungDamageComponent.cs
@@ -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
///
[DataField(required: true), AutoNetworkedField]
public FixedPoint2 Damage;
+
+ ///
+ /// The damage type to use when computing oxygenation from the lungs
+ ///
+ [DataField(required: true)]
+ public ProtoId AsphyxiationDamage;
+
+ ///
+ /// The amount of at which lung oxygenation is considered to be 0%
+ ///
+ [DataField(required: true)]
+ public FixedPoint2 AsphyxiationThreshold;
}
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
diff --git a/Content.Shared/_Offbrand/Wounds/LungDamageSystem.cs b/Content.Shared/_Offbrand/Wounds/LungDamageSystem.cs
index c4549e3267..d33b47f595 100644
--- a/Content.Shared/_Offbrand/Wounds/LungDamageSystem.cs
+++ b/Content.Shared/_Offbrand/Wounds/LungDamageSystem.cs
@@ -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(OnBeforeBreath);
SubscribeLocalEvent(OnRejuvenate);
+ SubscribeLocalEvent(OnBaseLungFunction);
SubscribeLocalEvent(OnLungDamageChanged);
SubscribeLocalEvent(OnAlertsShutdown);
}
@@ -66,6 +68,22 @@ public sealed class LungDamageSystem : EntitySystem
RaiseLocalEvent(ent, ref evt);
}
+ private void OnBaseLungFunction(Entity ent, ref BaseLungFunctionEvent args)
+ {
+ var damage = ent.Comp.Damage.Float() / ent.Comp.MaxDamage.Float();
+ var health = 1f - damage;
+ var damageable = Comp(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 ent, ref AfterLungDamageChangedEvent args)
{
var lungDamage = Comp(ent);
diff --git a/Content.Shared/_Offbrand/Wounds/PainComponent.cs b/Content.Shared/_Offbrand/Wounds/PainComponent.cs
index f757edbd32..d6525fdaad 100644
--- a/Content.Shared/_Offbrand/Wounds/PainComponent.cs
+++ b/Content.Shared/_Offbrand/Wounds/PainComponent.cs
@@ -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;
+}
+
///
/// Raised on an entity after its shock has changed
///
diff --git a/Content.Shared/_Offbrand/Wounds/PainSystem.cs b/Content.Shared/_Offbrand/Wounds/PainSystem.cs
index 1f2b4b3e20..61b67ee116 100644
--- a/Content.Shared/_Offbrand/Wounds/PainSystem.cs
+++ b/Content.Shared/_Offbrand/Wounds/PainSystem.cs
@@ -16,6 +16,8 @@ public sealed partial class PainSystem : EntitySystem
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnRejuvenate);
SubscribeLocalEvent(OnApplyMetabolicMultiplier);
+
+ SubscribeLocalEvent(OnBaseMetabolicRate);
}
private void OnApplyMetabolicMultiplier(Entity 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 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 ent)
{
if (!Resolve(ent, ref ent.Comp, false))
diff --git a/Content.Shared/_Offbrand/Wounds/ShockThresholdsSystem.cs b/Content.Shared/_Offbrand/Wounds/ShockThresholdsSystem.cs
index 2c7f9a14d4..0afdcaca21 100644
--- a/Content.Shared/_Offbrand/Wounds/ShockThresholdsSystem.cs
+++ b/Content.Shared/_Offbrand/Wounds/ShockThresholdsSystem.cs
@@ -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);
}
}
diff --git a/Content.Shared/_Offbrand/Wounds/WoundComponent.cs b/Content.Shared/_Offbrand/Wounds/WoundComponent.cs
index 76b462b731..efaf142aa4 100644
--- a/Content.Shared/_Offbrand/Wounds/WoundComponent.cs
+++ b/Content.Shared/_Offbrand/Wounds/WoundComponent.cs
@@ -160,12 +160,6 @@ public record struct HealWoundsEvent(DamageSpecifier Damage);
[ByRefEvent]
public record struct GetPainEvent(FixedPoint2 Pain);
-///
-/// Raised on an entity to get the sum total of heart strain
-///
-[ByRefEvent]
-public record struct GetStrainEvent(FixedPoint2 Strain);
-
///
/// Raised on an entity to get the amount it should bleed
///
diff --git a/Content.Shared/_Offbrand/Wounds/WoundableComponent.cs b/Content.Shared/_Offbrand/Wounds/WoundableComponent.cs
index 88cb12be84..3696e6c272 100644
--- a/Content.Shared/_Offbrand/Wounds/WoundableComponent.cs
+++ b/Content.Shared/_Offbrand/Wounds/WoundableComponent.cs
@@ -21,4 +21,4 @@ public record struct BeforeDamageCommitEvent(DamageSpecifier Damage, bool ForceR
/// Raised when the values for a damage overlay may have changed
///
[ByRefEvent]
-public record struct bPotentiallyUpdateDamageOverlayEventb(EntityUid Target);
+public record struct PotentiallyUpdateDamageOverlayEvent(EntityUid Target);
diff --git a/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs b/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs
index ec152fde96..77a242c963 100644
--- a/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs
+++ b/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs
@@ -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? 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? SampleWounds(EntityUid uid)
{
if (!_statusEffects.TryEffectsWithComp(uid, out var wounds))
@@ -119,6 +105,17 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
return null;
}
+ public MetricRanking Ranking(Entity 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(uid))
@@ -133,14 +130,10 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
if (!TryComp(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)),
};
}
}
diff --git a/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl b/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl
index d7c4a3f0d9..51bbda41c4 100644
--- a/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl
+++ b/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl
@@ -1,28 +1,20 @@
--health-analyzer-rating = { $rating ->
- [good] ([color=#00D3B8]good[/color])
- [okay] ([color=#30CC19]okay[/color])
- [poor] ([color=#bdcc00]poor[/color])
- [bad] ([color=#E8CB2D]bad[/color])
- [awful] ([color=#EF973C]awful[/color])
- [dangerous] ([color=#FF6C7F]dangerous[/color])
- *[other] (unknown)
- }
-
health-analyzer-window-entity-brain-health-text = Brain Activity:
health-analyzer-window-entity-blood-pressure-text = Blood Pressure:
-health-analyzer-window-entity-blood-oxygenation-text = Blood Saturation:
-health-analyzer-window-entity-blood-flow-text = Blood Flow:
health-analyzer-window-entity-heart-rate-text = Heart Rate:
health-analyzer-window-entity-heart-health-text = Heart Health:
health-analyzer-window-entity-lung-health-text = Lung Health:
+health-analyzer-window-entity-spo2-text = {LOC($spo2)}:
+health-analyzer-window-entity-etco2-text = {LOC($etco2)}:
+health-analyzer-window-entity-respiratory-rate-text = Respiratory Rate:
-health-analyzer-window-entity-brain-health-value = {$value}% { -health-analyzer-rating(rating: $rating) }
-health-analyzer-window-entity-heart-health-value = {$value}% { -health-analyzer-rating(rating: $rating) }
-health-analyzer-window-entity-lung-health-value = {$value}% { -health-analyzer-rating(rating: $rating) }
-health-analyzer-window-entity-heart-rate-value = {$value}bpm { -health-analyzer-rating(rating: $rating) }
-health-analyzer-window-entity-blood-oxygenation-value = {$value}% { -health-analyzer-rating(rating: $rating) }
-health-analyzer-window-entity-blood-pressure-value = {$systolic}/{$diastolic} { -health-analyzer-rating(rating: $rating) }
-health-analyzer-window-entity-blood-flow-value = {$value}% { -health-analyzer-rating(rating: $rating) }
+health-analyzer-window-entity-brain-health-value = {$value}%
+health-analyzer-window-entity-heart-health-value = {$value}%
+health-analyzer-window-entity-lung-health-value = {$value}%
+health-analyzer-window-entity-heart-rate-value = {$value}bpm
+health-analyzer-window-entity-blood-pressure-value = {$systolic}/{$diastolic}
+health-analyzer-window-entity-respiratory-rate-value = {$value}breaths/minute
+health-analyzer-window-entity-spo2-value = {$value}%
+health-analyzer-window-entity-etco2-value = {$value}mmHg
health-analyzer-window-entity-non-medical-reagents = [color=yellow]Patient has non-medical reagents in bloodstream.[/color]
wound-bone-death = [color=red]Patient has systemic bone failure.[/color]
@@ -33,6 +25,13 @@ wound-retracted = [color=red]Patient has retracted skin.[/color]
wound-ribcage-open = [color=red]Patient has open ribcage.[/color]
wound-arterial-bleeding = [color=red]Patient has arterial bleeding.[/color]
+etco2-carbon-dioxide = EtCO2
+etco2-ammonia = EtNH3
+etco2-nitrous-oxide = EtN2O
+
+spo2-oxygen = SpO2
+spo2-nitrogen = SpN2
+
health-analyzer-window-no-patient-damages = Patient has no injuries.
health-analyzer-status-tooltip =
@@ -40,52 +39,70 @@ health-analyzer-status-tooltip =
{"[bold]"}Critical[/bold]: The patient is unconscious and will die without intervention.
{"[bold]"}Dead[/bold]: The patient is dead and will rot without intervention.
-health-analyzer-blood-saturation-tooltip =
- A measure of how much oxygen (or nitrogen, etc.) the patient's brain is getting.
-
- { $rating ->
- [good] Your patient's brain is not at risk.
- [okay] Your patient's brain may be damaged slightly.
- [poor] Your patient's brain may be damaged.
- [bad] Your patient's brain may be damaged substantially.
- [awful] Your patient's brain is at [color=red]severe risk[/color] for fatal injury.
- [dangerous] Your patient's brain is at [color=red]life-threatening risk[/color] for fatal injury.
- *[other] Your patient is an enigma. Report this to developers if you see this message.
- }
-
- Relevant metrics:
- {"[color=#7af396]"}Blood Pressure[/color]: {$systolic}/{$diastolic}
- {"[color=#7af396]"}Asphyxiation[/color]: {$asphyxiation}
-
health-analyzer-blood-pressure-tooltip =
- A measure of how much blood is in use by the body.
+ A measure of how much blood is making it throughout the body.
- If [color=#7af396]Blood Flow[/color] is high but [color=#7af396]Blood Pressure[/color] is not, ensure your patient has adequate [color=#7af396]Blood Volume[/color].
+ IV stands can be used to replenish blood volume.
Relevant metrics:
- {"[color=#7af396]"}Blood Flow[/color]: {$flow}%
+ {"[color=#7af396]"}Blood Volume[/color]:
+ Low blood volume can result in reduced blood pressure.
-health-analyzer-blood-flow-tooltip =
- A measure of how much the patient's body can circulate available blood.
+ {"[color=#7af396]"}Brain Activity[/color]:
+ Low brain activity can result in reduced blood pressure.
- This primarily depends on your patient's heart having a pulse and being in good condition.
- CPR can be administered if the heart is not providing enough blood flow.
+ {"[color=#7af396]"}Heart Rate and Heart Health[/color]:
+ Damage to the heart or a stopped heart can result in reduced blood pressure.
+
+health-analyzer-spo2-tooltip =
+ A measure of how much {LOC($gas)} is making it to the patient's body, compared to what the patient needs.
Relevant metrics:
- {"[color=#7af396]"}Heart Rate[/color]: {$heartrate}bpm
- {"[color=#7af396]"}Heart Health[/color]: {$health}%
+ {"[color=#7af396]"}Metabolic Rate[/color]:
+ Physical trauma and pain can cause the body's {LOC($gas)} demand to increase.
+
+ {"[color=#7af396]"}Blood Pressure[/color]:
+ Low blood pressure can result in reduced {LOC($spo2)}.
+
+ {"[color=#7af396]"}Lung Health[/color]:
+ Low lung health can result in reduced {LOC($spo2)}.
+
+ {"[color=#7af396]"}Asphyxiation[/color]:
+ Asphyxiation can result in reduced {LOC($spo2)}.
+
+ {"[color=#7af396]"}Respiratory Rate[/color]:
+ Hyperventilation can result in the patient breathing less air per breath.
health-analyzer-heart-rate-tooltip =
A measure of how fast the patient's heart is beating.
- It will raise due to pain and asphyxiation.
+ The heartrate increases in response to inadequate {LOC($spo2)}.
- It can stop due to severe pain, lack of blood, or severe brain damage.
+health-analyzer-respiratory-rate-tooltip =
+ A measure of how fast the patient is breathing.
- {"[color=#731024]"}Inaprovaline[/color] can be administered to reduce the patient's heartrate.
+ Breathing too fast can result in less air per breath, causing asphyxiation.
+
+ Inaprovaline can encourage healthy breathing.
Relevant metrics:
- {"[color=#7af396]"}Asphyxiation[/color]: {$asphyxiation}
+ {"[color=#7af396]"}{LOC($spo2)}[/color]:
+ Inadequate access to {LOC($spo2gas)} can result in faster breathing.
+
+ {"[color=#7af396]"}Metabolic Rate[/color]:
+ Physical trauma and pain can cause the body to breathe faster.
+
+health-analyzer-etco2-tooltip =
+ A measure of how much {LOC($gas)} is being exhaled with each breath.
+
+ Low {LOC($etco2)} can result in toxic {LOC($gas)} buildup.
+
+ Relevant metrics:
+ {"[color=#7af396]"}Respiratory Rate[/color]:
+ Irregular breathing can cause the patient to not fully exhale all {LOC($gas)}.
+
+ {"[color=#7af396]"}Blood Pressure[/color]:
+ Low Blood Pressure can cause the patient to hold onto more {LOC($gas)}.
health-analyzer-heart-health-tooltip =
A measure of the heart's integrity.
@@ -118,24 +135,14 @@ health-analyzer-damage-tooltip =
health-analyzer-brain-health-tooltip = { $dead ->
[true] {-health-analyzer-brain-health-tooltip-dead}
- *[false] {-health-analyzer-brain-health-tooltip-alive(rating: $rating, saturation: $saturation)}
+ *[false] {-health-analyzer-brain-health-tooltip-alive(spo2: $spo2)}
}
-health-analyzer-brain-health-tooltip-alive =
- { $rating ->
- [good] Your patient is fine, and does not need any intervention.
- [okay] Your patient has slight brain damage, but can likely heal it over time.
- [poor] Your patient has brain damage.
- [bad] Your patient has a large amount of brain damage. Administer [color=#731024]Inaprovaline[/color] to stabilize the brain before proceeding with treatment.
- [awful] Your patient has a severe amount of brain damage. [bold]Administer [color=#731024]Inaprovaline[/color] to stabilize the brain immediately.[/bold] Consider moving to a cryopod or stasis bed if you do not have a treatment plan.
- [dangerous] Your patient is at [color=red]severe risk of death[/color]. [bold]Administer [color=#731024]Inaprovaline[/color], and move the patient to a cryopod or stasis bed if you do not have a treatment plan.[/bold]
- *[other] Your patient is an enigma. Report this to developers if you see this message.
- }
-
- {"[color=#fedb79]"}Mannitol[/color] can be administered to heal brain damage if the [color=#7af396]Blood Saturation[/color] permits.
+ {"[color=#fedb79]"}Mannitol[/color] can be administered to heal brain damage if the [color=#7af396]SpO2[/color] permits.
Relevant metrics:
- {"[color=#7af396]"}Blood Saturation[/color]: {$saturation}%
+ {"[color=#7af396]"}SpO2[/color]: {$spo2}%
-health-analyzer-brain-health-tooltip-dead =
The patient has 0% brain activity and is dead.
diff --git a/Resources/Locale/en-US/_Offbrand/messages.ftl b/Resources/Locale/en-US/_Offbrand/messages.ftl
index be4d6e6928..cfbb30bc65 100644
--- a/Resources/Locale/en-US/_Offbrand/messages.ftl
+++ b/Resources/Locale/en-US/_Offbrand/messages.ftl
@@ -1,7 +1,5 @@
heart-defibrillatable-target-is-dead = Severe neurological decay makes rescuitation impossible. Further attempts futile.
-heart-defibrillatable-target-hypovolemia = Patient is in hypovolemic shock and will require a blood transfuion: rescuitation will likely fail.
-heart-defibrillatable-target-brain-damage = Patient has severe neurological decay: rescuitation will likely fail without epinephrine.
-heart-defibrillatable-target-pain = Patient is in severe shock: the heart may stop without intervention.
+heart-defibrillatable-target-strain = Patient's vitals are outside of acceptable parameters: the patient will likely re-enter cardiac arrest.
mmi-extractor-no-mind = No neurological activity detected in patient; brain will not be extracted.
mmi-extractor-probing = Probing patient for neurological activity...
diff --git a/Resources/Locale/en-US/_Offbrand/vitals.ftl b/Resources/Locale/en-US/_Offbrand/vitals.ftl
index 7a49ee33db..2d060d0478 100644
--- a/Resources/Locale/en-US/_Offbrand/vitals.ftl
+++ b/Resources/Locale/en-US/_Offbrand/vitals.ftl
@@ -1,13 +1,3 @@
--crew-monitor-vitals-rating = { $rating ->
- [good] [color=#00D3B8]{$text}[/color]
- [okay] [color=#30CC19]{$text}[/color]
- [poor] [color=#bdcc00]{$text}[/color]
- [bad] [color=#E8CB2D]{$text}[/color]
- [awful] [color=#EF973C]{$text}[/color]
- [dangerous] [color=#FF6C7F]{$text}[/color]
- *[other] unknown
- }
-
-offbrand-crew-monitoring-heart-rate = { -crew-monitor-vitals-rating(text: $rate, rating: $rating) }bpm
-offbrand-crew-monitoring-blood-pressure = { -crew-monitor-vitals-rating(text: $systolic, rating: $rating) }/{ -crew-monitor-vitals-rating(text: $diastolic, rating: $rating) }
-offbrand-crew-monitoring-oxygenation = { -crew-monitor-vitals-rating(text: $oxygenation, rating: $rating) }% air
+offbrand-crew-monitoring-heart-rate = [color=white]{ $rate }[/color]bpm
+offbrand-crew-monitoring-blood-pressure = [color=white]{ $systolic }[/color]/[color=white]{ $diastolic }[/color]
+offbrand-crew-monitoring-spo2 = [color=white]{ $value }[/color]% { LOC($spo2) }
diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml
index 781fa6c912..fdeafc7548 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml
@@ -20,6 +20,13 @@
maxItemSize: Large
storageInsertSound:
path: /Audio/Voice/Slime/slime_squish.ogg
+ # Begin Offbrand Additions
+ - type: Heartrate
+ etco2Name: etco2-nitrous-oxide
+ etco2GasName: reagent-name-nitrous-oxide
+ spo2Name: spo2-nitrogen
+ spo2GasName: reagent-name-nitrogen
+ # End Offbrand Additions
- type: ContainerContainer
containers:
storagebase: !type:Container
diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml
index 44687ac544..45e3580ac9 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml
@@ -30,29 +30,38 @@
- type: Damageable
damageContainer: Biological
damageModifierSet: Vox
+ # Begin Offbrand Additions
+ - type: Heartrate
+ etco2Name: etco2-ammonia
+ etco2GasName: reagent-name-ammonia
+ spo2Name: spo2-nitrogen
+ spo2GasName: reagent-name-nitrogen
+ # End Offbrand Additions
- type: Destructible
thresholds:
- - trigger:
- !type:DamageTypeTrigger
- damageType: Blunt
- damage: 400
- behaviors:
- - !type:GibBehavior { }
- - trigger:
- !type:DamageTypeTrigger
- damageType: Heat
- damage: 1500
- behaviors:
- - !type:SpawnEntitiesBehavior
- spawnInContainer: true
- spawn:
- FoodMeatChickenFriedVox:
- min: 3
- max: 5
- - !type:BurnBodyBehavior { }
- - !type:PlaySoundBehavior
- sound:
- collection: MeatLaserImpact
+ # Begin Offbrand Removals
+ # - trigger:
+ # !type:DamageTypeTrigger
+ # damageType: Blunt
+ # damage: 400
+ # behaviors:
+ # - !type:GibBehavior { }
+ # - trigger:
+ # !type:DamageTypeTrigger
+ # damageType: Heat
+ # damage: 1500
+ # behaviors:
+ # - !type:SpawnEntitiesBehavior
+ # spawnInContainer: true
+ # spawn:
+ # FoodMeatChickenFriedVox:
+ # min: 3
+ # max: 5
+ # - !type:BurnBodyBehavior { }
+ # - !type:PlaySoundBehavior
+ # sound:
+ # collection: MeatLaserImpact
+ # End Offbrand Removals
- trigger:
!type:DamageTypeTrigger
damageType: Radiation
diff --git a/Resources/Prototypes/Reagents/botany.yml b/Resources/Prototypes/Reagents/botany.yml
index eb4525732e..eff8fb0817 100644
--- a/Resources/Prototypes/Reagents/botany.yml
+++ b/Resources/Prototypes/Reagents/botany.yml
@@ -317,7 +317,7 @@
conditions:
- !type:OrganType
type: Vox
- factor: -4
+ factor: -1 # Offbrand
- type: reagent
diff --git a/Resources/Prototypes/Reagents/gases.yml b/Resources/Prototypes/Reagents/gases.yml
index 812a0e1a78..b13a09e356 100644
--- a/Resources/Prototypes/Reagents/gases.yml
+++ b/Resources/Prototypes/Reagents/gases.yml
@@ -247,13 +247,13 @@
damage:
types:
Poison:
- 0.8
+ 0.2 # Offbrand
- !type:Oxygenate # carbon dioxide displaces oxygen from the bloodstream, causing asphyxiation
conditions:
- !type:OrganType
type: Plant
shouldHave: false
- factor: -4
+ factor: -1 # Offbrand
# We need a metabolism effect on reagent removal
#- !type:AdjustAlert
# alertType: CarbonDioxide
diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml
index ca594280d9..ff866513d9 100644
--- a/Resources/Prototypes/Reagents/medicine.yml
+++ b/Resources/Prototypes/Reagents/medicine.yml
@@ -365,7 +365,7 @@
# Begin Offbrand
metabolismRate: 0.25
statusEffects:
- - statusEffect: StatusEffectHeartStrainEpinephrine
+ - statusEffect: StatusEffectEpinephrine
effects:
- !type:StartHeart
probability: 0.03
diff --git a/Resources/Prototypes/_Offbrand/base_mob.yml b/Resources/Prototypes/_Offbrand/base_mob.yml
index b85ee9457e..4778b18827 100644
--- a/Resources/Prototypes/_Offbrand/base_mob.yml
+++ b/Resources/Prototypes/_Offbrand/base_mob.yml
@@ -149,39 +149,41 @@
id: BaseMobHeartrate
components:
- type: Heartrate
- asphyxiationDamage: Asphyxiation
- asphyxiationThreshold: 100
maxDamage: 100
damage: 0
strainDamageThresholds:
- 1: [0.04, 0.1]
- 2: [0.1, 0.1]
- 3: [0.3, 0.1]
- 4: [0.4, 2]
- circulationStrainModifierCoefficient: 0.075
- circulationStrainModifierConstant: 1.025
- stoppedBloodCirculationModifier: 0.2
- minimumDamageCirculationModifier: 0.3
- shockStrainDivisor: 40
+ 0.25: [0.2, 0.1]
+ 0.5: [0.3, 0.1]
+ 0.75: [0.4, 0.1]
+ 1: [0.5, 2]
+ respiratoryRateCoefficient: -3
+ respiratoryRateConstant: 1.333
+ respiratoryRateNormalBreath: 5
+ compensationCoefficient: 0.7
+ compensationConstant: 1
+ compensationStrainCoefficient: 1.42
+ compensationStrainConstant: -1.42
bloodPressureDeviation: 5
systolicBase: 120
diastolicBase: 80
heartRateDeviation: 15
- heartRateBase: 75
- heartRateStrainFactor: 200
- heartRateStrainDivisor: 6
- maximumStrain: 6
+ heartRateFullPerfusion: 75
+ heartRateNoPerfusion: 200
+ etco2Base: 40
+ etco2Deviation: 4
+ etco2Name: etco2-carbon-dioxide
+ etco2GasName: reagent-name-carbon-dioxide
+ spo2Name: spo2-oxygen
+ spo2GasName: reagent-name-oxygen
heartStoppedEffect: StatusEffectHeartStopped
- type: HeartDefibrillatable
- - type: HeartStopOnHypovolemia
- chance: 0.8
- volumeThreshold: 0.3
+ - type: PainMetabolicRate
+ quadraticFactor: 0.00005
+ linearFactor: -0.0025
+ constantFactor: 0
- type: HeartStopOnHighStrain
chance: 0.05
- threshold: 4
- - type: HeartStopOnBrainHealth
- chance: 0.8
- threshold: 70
+ threshold: 0.75
- type: HeartrateAlerts
strainAlert: HeartRate
stoppedAlert: HeartStopped
@@ -305,6 +307,8 @@
- type: LungDamage
damage: 0
maxDamage: 100
+ asphyxiationDamage: Asphyxiation
+ asphyxiationThreshold: 100
- type: LungDamageOnInhaledAirTemperature
heatCoefficient: 0.005
heatConstant: -1.375
diff --git a/Resources/Prototypes/_Offbrand/status_effects.yml b/Resources/Prototypes/_Offbrand/status_effects.yml
index 8dcb8b915c..3f03c0462c 100644
--- a/Resources/Prototypes/_Offbrand/status_effects.yml
+++ b/Resources/Prototypes/_Offbrand/status_effects.yml
@@ -105,40 +105,48 @@
id: StatusEffectHeartStrainOxycodoneAlcohol
- type: entity
- parent: StatusEffectHeartStrainHyperzine
- id: StatusEffectHeartStrainEpinephrine
+ parent: MobStatusEffectBase
+ id: StatusEffectEpinephrine
+ name: counteracts the effects of low brain health on blood pressure and increases the metabolic rate
+ components:
+ - type: VascularToneModifierStatusEffect
+ tone: 0.9
+ - type: MetabolicRateModifierStatusEffect
+ delta: 0.1
- type: entity
parent: MobStatusEffectBase
id: StatusEffectHeartStrainSedin
- name: severely increased heart rate
+ name: severely increased metabolic rate
components:
- - type: StrainStatusEffect
- delta: 2
+ - type: MetabolicRateModifierStatusEffect
+ delta: 0.25
- type: entity
parent: MobStatusEffectBase
id: StatusEffectHeartStrainRomerol
- name: increased heart rate
+ name: increased metabolic rate
components:
- - type: StrainStatusEffect
- delta: 1.5
+ - type: MetabolicRateModifierStatusEffect
+ delta: 0.25
- type: entity
parent: MobStatusEffectBase
id: StatusEffectHeartStabilizationInaprovaline
- name: reduced heartrate
+ name: reduced metabolic rate and regulated respiratory rate
components:
- - type: StrainStatusEffect
- delta: -2
+ - type: MetabolicRateModifierStatusEffect
+ delta: -0.5
+ - type: RespiratoryRateModifierStatusEffect
+ rate: 0.8
- type: entity
parent: MobStatusEffectBase
id: StatusEffectHeartStrainDesoxyephedrine
- name: increased heart rate
+ name: increased metabolic rate
components:
- - type: StrainStatusEffect
- delta: 1
+ - type: MetabolicRateModifierStatusEffect
+ delta: 0.15
- type: entity
parent: StatusEffectHeartStrainDesoxyephedrine
@@ -147,27 +155,27 @@
- type: entity
parent: MobStatusEffectBase
id: StatusEffectHeartStrainNicotine
- name: slightly increased heart rate
+ name: slightly increased metabolic rate
components:
- - type: StrainStatusEffect
- delta: 0.5
+ - type: MetabolicRateModifierStatusEffect
+ delta: 0.1
- type: entity
parent: MobStatusEffectBase
id: StatusEffectHeartStrainHyperzine
- name: severely increased heart rate & forced heartbeat
+ name: severely increased metabolic rate & forced heartbeat
components:
- - type: StrainStatusEffect
- delta: 2
- type: PreventHeartStopFromStrainStatusEffect
+ - type: MetabolicRateModifierStatusEffect
+ delta: 0.5
- type: entity
parent: MobStatusEffectBase
id: StatusEffectHeartStabilizationTHC
- name: minor heart stabilization
+ name: 20 pain relief
components:
- - type: StrainStatusEffect
- delta: -1
+ - type: PainkillerStatusEffect
+ effectiveness: 20
- type: entity
parent: StatusEffectHeartStabilizationInaprovaline
@@ -209,8 +217,8 @@
id: StatusEffectMinorOxygenationDexalin
name: 50% artificial respiration
components:
- - type: BloodOxygenationModifierStatusEffect
- minimumOxygenation: 0.5
+ - type: LungFunctionModifierStatusEffect
+ function: 0.5
- type: entity
parent: StatusEffectMinorOxygenationDexalin
@@ -225,8 +233,8 @@
id: StatusEffectMajorOxygenationDexalinPlus
name: 80% artificial respiration
components:
- - type: BloodOxygenationModifierStatusEffect
- minimumOxygenation: 0.8
+ - type: LungFunctionModifierStatusEffect
+ function: 0.8
- type: entity
parent: StatusEffectMajorOxygenationDexalinPlus
@@ -399,8 +407,8 @@
id: StatusEffectActiveCPR
name: ongoing CPR
components:
- - type: AssistedCirculationStatusEffect
- amount: 0.4
+ - type: CardiacOutputModifierStatusEffect
+ output: 0.8
- type: entity
abstract: true
diff --git a/Resources/ServerInfo/_Offbrand/Body.xml b/Resources/ServerInfo/_Offbrand/Body.xml
index 52d2db0a95..c82ba965be 100644
--- a/Resources/ServerInfo/_Offbrand/Body.xml
+++ b/Resources/ServerInfo/_Offbrand/Body.xml
@@ -14,44 +14,29 @@ The more an injury threatens your patient's brain, the more important it is to t
[color=#00d3b8][bold]Once a brain dies, it is dead, and the body cannot be rescuitated.[/bold][/color]
A patient's brain requires adequate saturation (oxygenation for most species) in the bloodstream to survive.
+This is usually measured as SpO2 for oxygen-breathing species, or SpN2 for nitrogen-breathing species.
[color=#00d3b8][bold]The primary goal in medical care is to ensure the brain has adequate saturation.[/bold][/color]
-Symptoms of a patient with severe brain damage include, but are not limited to:
+Symptoms of a patient with severe brain trauma include, but are not limited to:
-- [color=cyan]Impaired communication[/color]: A patient experiencing severe brain damage can have difficulty speaking (i.e. stuttering).
+- [color=cyan]Impaired communication[/color]: A patient experiencing severe brain trauma can have difficulty speaking (i.e. stuttering).
- [color=cyan]Impaired motor control[/color]: A patient experiencing pain may show sluggishness or an inability to aim precisely.
-- [color=cyan]Inability to stand[/color]: A patient experiencing severe brain damage may be unable to stand.
-- [color=cyan]Gasping[/color]: A patient experiencing severe brain damage may have trouble breathing, and gasp as a result.
-- [color=cyan]Nonresponsiveness[/color]: A patient experiencing severe brain damage may be unable to respond to environmental stimuli.
+- [color=cyan]Inability to stand[/color]: A patient experiencing severe brain trauma may be unable to stand.
+- [color=cyan]Gasping[/color]: A patient experiencing severe brain trauma may have trouble breathing, and gasp as a result.
+- [color=cyan]Nonresponsiveness[/color]: A patient experiencing severe brain trauma may be unable to respond to environmental stimuli.
-### Diagnosis
+## Saturation (SpO2/SpN2)
-You can gauge your patient's brain activity with your health analyzer, listed as [color=#7af396]Brain Activity[/color].
-The left-most icon on your medical HUD also indicates a rough read of brain activity, with the blue cross being replaced by a red-and-white exclamation mark if the patient's brain is at risk of fatal injury.
+Saturation is the most important metric for ensuring your patient's brain continues to survive.
-If your patient's [color=#7af396]Brain Activity[/color] is lowering, ensure their [color=#7af396]Blood Saturation[/color] is at safe levels.
+It is a balance between the [color=#00d3b8][bold]Supply of Oxygen[/bold][/color] and [color=#00d3b8][bold]Oxygen Demand[/bold][/color].
-## Bloodstream
+If there is more demand than there is supply, the saturation will drop below 100%.
-Blood saturation is the primary measure for how satiated a patient's brain is.
+Supply comes from the lungs, the heart, and the bloodstream.
+Any of these faltering will cause the overall supply to drop.
-Blood saturation is affected by:
-- [color=cyan]Blood volume[/color]: Blood can be restored with saline for most species, or blood bags.
-- [color=cyan]Breathable air[/color]: Ensure the patient can breathe. Dexalin (plus) can be used to artificially oxygenate the bloodstream.
-- [color=cyan]Blood flow[/color]: The heart is responsible for circulating blood around your patient's body. CPR can be administered if the patient's heart has stopped, followed by surgical intervention as soon as possible.
-
-### Diagnosis
-
-You can gauge your patient's blood vitals with your health analyzer, listed as [color=#7af396]Blood Saturation[/color], [color=#7af396]Blood Pressure[/color], and [color=#7af396]Blood Flow[/color].
-
-If your patient's [color=#7af396]Blood Saturation[/color] is low, ensure their [color=#7af396]Blood Pressure[/color] is at safe levels, and make sure the patient has access to air.
-Medicines such as Dexalin can be used to ensure the bloodstream has access to air, and to speed up the process of respiration after prolonged suffocation.
-
-If your patient's [color=#7af396]Blood Flow[/color] is low, ensure their [color=#7af396]Heart Rate[/color] and [color=#7af396]Heart Health[/color] are at safe levels.
-In the case of a stopped heart, you can give CPR to ensure the blood is moving.
-
-If your patient's [color=#7af396]Blood Pressure[/color] is low, ensure their [color=#7af396]Heart Rate[/color] and blood volume are at safe levels.
-Saline and blood bags can be used to increase the patient's blood volume.
+The body always requires air to live, but physical trauma and pain can result in its need for air to drastically increase, outpacing what even a healthy cardiovascular system can provide.
## The Heart
@@ -59,35 +44,94 @@ Saline and blood bags can be used to increase the patient's blood volume.
-The heart is responsible for blood flow.
+The heart is the primary organ overseeing bloodflow in the patient's body.
[color=#00d3b8][bold]If a patient's heart has stopped, they are priority number one.[/bold][/color]
+Immediately administer CPR, keep administering CPR, and attempt to restart the heart.
-Blood flow increases with heartrate, and decreases with heart damage, coming to near-zero if the heart stops.
+The heart is the body's first line of defense against inadequate air supply.
+When the body's saturation (SpO2) drops, the heart rate increases to compensate for this.
+However, an increased heartrate can only do so much before it begins to become ineffective or dangerous.
-The heart will increase its heartrate in response to pain, lack of access to breathable air, and lowered blood volume.
+[bold]Excesssively high heartrates can damage the heart or lead to cardiac arrest. If your patient's heartrate is high, seek to address the causes that might be increasing it before it worsens.[/bold]
-The heart will stop from:
-- [color=cyan]Ventricular tachycardia[/color]: Excessively high pulse can cause the patient's heart to stop, usually from pain. Administer painkillers and inaprovaline before restarting the heart.
-- [color=cyan]Hypovolemia[/color]: If a patient loses too much blood, their heart will stop. Stop bleeding and restore blood levels before restarting the heart.
-- [color=cyan]Accumulated damage[/color]: If a patient's heart accumulates too much damage, it will stop. Surgical intervention will be required before restarting the heart.
+### Heart Rate
-The heart can be restarted using a defibrillator.
+The [color=#7af396]Heart Rate[/color
+] is the primary indicator of how hard the patient's heart is working.
+It increases in response to other vitals dropping, and can potentially stop the heart if it is too high for too long.
-If the heart cannot be restarted, medical professionals and other crew alike can administer CPR to ensure blood flow.
+High heartrate can also reduce the [color=#7af396]Heart Health[/color].
-### Diagnosis
+### Heart Health
-You can gauge your patient's heart vitals with your health analyzer, listed as [color=#7af396]Heart Health[/color] and [color=#7af396]Heart Rate[/color].
+The [color=#7af396]Heart Health[/color] is an indicator of how much damage the heart has taken, either from strain caused by excessive heartrates, or by direct injury.
-If your patient's [color=#7af396]Heart Rate[/color] is low, ensure their blood volume is adequate, and use a defibrillator or stimulant medications as necessary.
+## The Bloodstream
-If your patient's [color=#7af396]Heart Rate[/color] is high, ensure that the patient's pain is under management, the patient's blood volume is sufficient, and that the patient has adequate access to oxygen.
-Medicines such as Inaprovaline can be administered to stabilize the heart rate, at the risk of reducing the patient's overall [color=#7af396]Blood Saturation[/color] if other conditions are not treated.
+The bloodstream is the primary vehicle that carries air from your lungs to the brain, with help from the heart.
-If your patient's [color=#7af396]Heart Health[/color] is declining, ensure that their [color=#7af396]Heart Rate[/color] is not excessively high.
+It is primarily measured by the [color=#7af396]Blood Pressure[/color], which is affected by three factors.
-If your patient's [color=#7af396]Heart Health[/color] is dangerously low, surgery will be required to repair injuries to the heart.
+### Blood Volume
+
+Blood is responsible for carrying air, and if there's no blood, there's nothing to carry air.
+Low blood volume causes low blood pressure.
+
+Missing blood can be replaced using IV stands.
+
+In a pinch, Saline, Iron, and Copper can help the patient's body regenerate more blood.
+
+### Vascular Tone
+
+A patient's brain is responsible for many things, and among those, regulating the patient's bloodstream.
+
+If your patient is suffering from injuries to the brain, its ability to keep blood vessels tight will drop, and the blood pressure will drop.
+
+Epinephrine can help ensure vascular tone in this scenario.
+
+### Cardiac Output
+
+A patient's heart is responsible for circulating blood through the body.
+
+A stopped or damaged heart can cause [color=#7af396]Blood Pressure[/color] to drop dramatically.
+
+If the patient's heart has stopped, CPR can be given to keep blood moving.
+
+## Lungs
+
+
+
+
+
+Lungs are responsible for taking in oxygen (for most species) from the air, and exhaling carbon dioxide (for most species).
+
+If lungs cannot breathe enough, your patient's SpO2 drops, potentially causing injury to the brain.
+If lungs cannot exhale enough, your patient's EtCO2 will drop, potentially causing buildup of toxic waste gases.
+
+Dexalin and Dexalin Plus can be used to substitute for lung function for SpO2, and Inaprovaline can help the patient breathe normally, helping with EtCO2 and SpO2.
+
+### Lung Health
+
+Your patient's lungs can be damaged from breathing in dangerously cold or hot air.
+
+This reduces their ability to inhale air and their ability to provide inhaled air to the body.
+
+### Respiratory Rate
+
+If your patient's body has more demand for air, or their supply of air begins to drop, they will breathe faster to attempt to keep supply and demand in balance.
+
+Past a certain point, breaths can become too fast and shallow, leading to audible gasping and asphyxiation.
+
+### Exhaled Waste Gases (EtCO2/EtNH3/EtN2O)
+
+As a part of life, your patient's body will create waste gases from the inhaled air, primarily Carbon Dioxide (CO2) for most species.
+
+A functional bloodstream ([color=#7af396]Blood Pressure[/color]) is important to carry waste gases to your patient's lungs, so that they can be expelled.
+Inadequate [color=#7af396]Blood Pressure[/color] can reduce the EtCO2, resulting in buildup of toxic waste gases.
+
+The [color=#7af396]Respiratory Rate[/color] also affects how much the patient is exhaling.
+If your patient is breathing too fast and cannot exhale fully, toxic waste gases will build up.
## Pain
@@ -95,17 +139,10 @@ In response to injuries, your patient will experience pain.
Fresh wounds are more painful than older and untended wounds.
-The effect of pain can primarily felt through the increase in a patient's heart rate, as well as shock in cases of severe pain.
+Pain increases the body's demand for air, causing the cardiovascular system to work overtime.
-Symptoms of a patient experiencing pain include, but are not limited to:
+Possible symptoms include increased heartrate, increased breathing rate, and potentially unconsciousness if the pain is great enough to cause the patient to enter shock.
-- [color=cyan]Impaired communication[/color]: A patient experiencing pain can have difficulty speaking (i.e. stuttering or volume), and may be unable to operate their headset effectively
-- [color=cyan]Imapired motor control[/color]: A patient experiencing pain may show sluggishness or an inability to aim precisely.
-- [color=cyan]Inability to stand[/color]: A patient experiencing pain may be unable to stand.
-
-### Diagnosis
-
-The patient's pain level can primarily be gauged by asking the patient how they feel, or in cases where they cannot communicate, their [color=#7af396]Heart Rate[/color] or displayed symptoms.
-Note that pain is not the only thing that can cause the heart rate to increase.
+Pain can be treated temporarily by giving painkillers, and treated long-term by healing the injuries that are causing pain.