resimmed offmed

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

View File

@@ -46,18 +46,21 @@
<Label Name="BloodPressureText" Text="{Loc 'health-analyzer-window-entity-blood-pressure-text'}" />
<RichTextLabel Name="BloodPressureLabel" />
<TextureButton Name="BloodPressureButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="BloodOxygenationText" Text="{Loc 'health-analyzer-window-entity-blood-oxygenation-text'}" />
<RichTextLabel Name="BloodOxygenationLabel" />
<TextureButton Name="BloodOxygenationButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="BloodFlowText" Text="{Loc 'health-analyzer-window-entity-blood-flow-text'}" />
<RichTextLabel Name="BloodFlowLabel" />
<TextureButton Name="BloodFlowButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="HeartRateText" Text="{Loc 'health-analyzer-window-entity-heart-rate-text'}" />
<RichTextLabel Name="HeartRateLabel" />
<TextureButton Name="HeartRateButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="HeartHealthText" Text="{Loc 'health-analyzer-window-entity-heart-health-text'}" />
<RichTextLabel Name="HeartHealthLabel" />
<TextureButton Name="HeartHealthButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="SpO2Text" />
<RichTextLabel Name="SpO2Label" />
<TextureButton Name="SpO2Button" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="EtCO2Text" />
<RichTextLabel Name="EtCO2Label" />
<TextureButton Name="EtCO2Button" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="RespiratoryRateText" Text="{Loc 'health-analyzer-window-entity-respiratory-rate-text'}" />
<RichTextLabel Name="RespiratoryRateLabel" />
<TextureButton Name="RespiratoryRateButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />
<Label Name="LungHealthText" Text="{Loc 'health-analyzer-window-entity-lung-health-text'}" />
<RichTextLabel Name="LungHealthLabel" />
<TextureButton Name="LungHealthButton" StyleClasses="SpeciesInfoDefault" Scale="0.3 0.3" VerticalAlignment="Center" />

View File

@@ -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<HeartrateComponent>(target)));
_heartRateTooltip.Update(msg, (target.Value, damageable, _entityManager.GetComponentOrNull<HeartrateComponent>(target)));
_bloodFlowTooltip.Update(msg);
_heartRateTooltip.Update(msg);
_heartHealthTooltip.Update(msg);
_temperatureTooltip.Update(msg, (target.Value, _entityManager.GetComponentOrNull<CryostasisFactorComponent>(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;

View File

@@ -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}"));
}
}

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox" MaxWidth="450">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -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<DamageableComponent, HeartrateComponent?> 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));
}
}

View File

@@ -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";
}

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox" MaxWidth="450">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -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}"));
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class BloodTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-blood-tooltip";
}

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Text="{Loc 'health-analyzer-blood-tooltip'}" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

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

View File

@@ -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}"));
}
}

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox" MaxWidth="450">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -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}"));
}
}

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class DamageTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-damage-tooltip";
}

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Text="{Loc 'health-analyzer-damage-tooltip'}" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox" MaxWidth="450">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -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<DamageableComponent, HeartrateComponent?> 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));
}
}

View File

@@ -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";
}

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Text="{Loc 'health-analyzer-lung-health-tooltip'}" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
namespace Content.Client.HealthAnalyzer.UI.Tooltips;
public sealed partial class StatusTooltip : StaticTooltip
{
public override LocId Text => "health-analyzer-status-tooltip";
}

View File

@@ -1,8 +0,0 @@
<PanelContainer xmlns="https://spacestation14.io" StyleClasses="tooltipBox">
<BoxContainer
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Text="{Loc 'health-analyzer-status-tooltip'}" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -3,6 +3,6 @@
Orientation="Vertical"
RectClipContent="True"
Margin="4">
<RichTextLabel Name="Label" HorizontalExpand="True" />
<RichTextLabel Access="Protected" Name="Label" HorizontalExpand="True" />
</BoxContainer>
</PanelContainer>

View File

@@ -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)
{
}
}

View File

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

View File

@@ -34,7 +34,7 @@ public sealed class DamageOverlayUiController : UIController
SubscribeLocalEvent<LocalPlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<MobThresholdChecked>(OnThresholdCheck);
SubscribeLocalEvent<bPotentiallyUpdateDamageOverlayEventb>(OnPotentiallyUpdateDamageOverlay); // Offbrand
SubscribeLocalEvent<PotentiallyUpdateDamageOverlayEvent>(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;
}

View File

@@ -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<SpriteSpecifier> Severities = new List<SpriteSpecifier>() { HudGood, HudOkay, HudPoor, HudBad, HudDanger };
public HeartrateOverlay()
{
@@ -40,19 +42,17 @@ public sealed class HeartrateOverlay : Overlay
_transform = _entityManager.System<SharedTransformSystem>();
_sprite = _entityManager.System<SpriteSystem>();
_statusIcon = _entityManager.System<StatusIconSystem>();
_heart = _entityManager.System<HeartSystem>();
}
private SpriteSpecifier GetIcon(Entity<HeartrateComponent> 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)

View File

@@ -14,7 +14,7 @@ namespace Content.Server.Body.Components
/// Volume of our breath in liters
/// </summary>
[DataField]
public float BreathVolume = Atmospherics.BreathVolume;
public float BreathVolume = 0.75f; // Offbrand
/// <summary>
/// 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.
/// </summary>
[DataField]
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
public TimeSpan UpdateInterval = TimeSpan.FromSeconds(2.5); // Offbrand
/// <summary>
/// Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on metabolic rate multiplier.
@@ -41,18 +41,48 @@ namespace Content.Server.Body.Components
[DataField]
public float UpdateIntervalMultiplier = 1f;
/// <summary>
/// Offbrand - Multiplier applied to <see cref="UpdateInterval"/> for adjusting based on body respiratory rate
/// </summary>
[DataField]
public float BreathRateMultiplier = 1f;
/// <summary>
/// Offbrand - Multiplier applied to exhalation to determine how efficient the purging of gases from the body is
/// </summary>
[DataField]
public float ExhaleEfficacyModifier = 1f;
/// <summary>
/// Offbrand - Multiplier that determines if an entity is hyperventilating (should audibly breathe)
/// </summary>
[DataField]
public float HyperventilationThreshold = 0.6f;
/// <summary>
/// Offbrand - Multiplier applied to <see cref="BreathVolume"/> for adjusting based on body respiratory rate
/// </summary>
[ViewVariables]
public float AdjustedBreathVolume => BreathVolume * BreathRateMultiplier * BreathRateMultiplier;
/// <summary>
/// Adjusted update interval based only on body factors, no e.g. stasis
/// </summary>
[ViewVariables]
public TimeSpan BodyAdjustedUpdateInterval => UpdateInterval * BreathRateMultiplier; // Offbrand
/// <summary>
/// Adjusted update interval based off of the multiplier value.
/// </summary>
[ViewVariables]
public TimeSpan AdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier;
public TimeSpan OverallAdjustedUpdateInterval => UpdateInterval * UpdateIntervalMultiplier * BreathRateMultiplier; // Offbrand
/// <summary>
/// Saturation level. Reduced by UpdateInterval each tick.
/// Can be thought of as 'how many seconds you have until you start suffocating' in this configuration.
/// </summary>
[DataField]
public float Saturation = 5.0f;
public float Saturation = 8.0f; // Offbrand
/// <summary>
/// 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;

View File

@@ -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<RespiratorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<RespiratorComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
SubscribeLocalEvent<RespiratorComponent, ApplyRespiratoryRateModifiersEvent>(OnApplyRespiratoryRateModifiers);
// BodyComp stuff
SubscribeLocalEvent<BodyComponent, InhaledGasEvent>(OnGasInhaled);
@@ -60,7 +62,7 @@ public sealed class RespiratorSystem : EntitySystem
private void OnMapInit(Entity<RespiratorComponent> 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<HeartrateComponent>(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<Content.Shared._Offbrand.Wounds.HeartrateComponent>(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<BodyComponent> ent, GasMixture gas)
{
var outGas = new GasMixture(gas.Volume);
var respirator = Comp<RespiratorComponent>(ent); // Offbrand
var organs = _bodySystem.GetBodyOrganEntityComps<LungComponent>((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<RespiratorComponent> ent, ref ApplyRespiratoryRateModifiersEvent args)
{
ent.Comp.BreathRateMultiplier = args.BreathRate;
ent.Comp.ExhaleEfficacyModifier = args.PurgeRate;
}
// End Offbrand
private void OnGasInhaled(Entity<BodyComponent> entity, ref InhaledGasEvent args)
{
if (args.Handled)

View File

@@ -152,10 +152,9 @@ public sealed partial class ZombieSystem
{
RemComp<HeartrateComponent>(target);
RemComp<HeartDefibrillatableComponent>(target);
RemComp<HeartStopOnHypovolemiaComponent>(target);
RemComp<HeartStopOnHighStrainComponent>(target);
RemComp<HeartStopOnBrainHealthComponent>(target);
RemComp<PainComponent>(target);
RemComp<PainMetabolicRateComponent>(target);
RemComp<HeartrateAlertsComponent>(target);
RemComp<ShockThresholdsComponent>(target);
RemComp<ShockAlertsComponent>(target);

View File

@@ -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<LungComponent>(uid))
{
foreach (var gasId in Enum.GetValues<Gas>())
{
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -317,7 +317,7 @@
conditions:
- !type:OrganType
type: Vox
factor: -4
factor: -1 # Offbrand
- type: reagent

View File

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

View File

@@ -365,7 +365,7 @@
# Begin Offbrand
metabolismRate: 0.25
statusEffects:
- statusEffect: StatusEffectHeartStrainEpinephrine
- statusEffect: StatusEffectEpinephrine
effects:
- !type:StartHeart
probability: 0.03

View File

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

View File

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

View File

@@ -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.
<GuideEntityEmbed Entity="OrganHumanHeart" Caption="Human Heart" />
</Box>
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
<Box>
<GuideEntityEmbed Entity="OrganHumanLungs" Caption="Human Lungs" />
</Box>
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.
</Document>