diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
index 16aded522b..2f4057cee7 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml
@@ -58,6 +58,9 @@
+
+
+
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
index bd8a7d3ee7..52007c15c3 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -44,6 +44,7 @@ namespace Content.Client.HealthAnalyzer.UI
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();
@@ -71,6 +72,7 @@ namespace Content.Client.HealthAnalyzer.UI
TemperatureButton.TooltipSupplier = _ => _temperatureTooltip;
DamageButton.TooltipSupplier = _ => _damageTooltip;
BloodButton.TooltipSupplier = _ => _bloodTooltip;
+ LungHealthButton.TooltipSupplier = _ => _lungHealthTooltip;
// End Offbrand
}
@@ -263,6 +265,11 @@ namespace Content.Client.HealthAnalyzer.UI
BloodPressureLabel.Text = Loc.GetString("health-analyzer-window-entity-blood-pressure-value", ("systolic", systolic), ("diastolic", diastolic), ("rating", woundable.BloodPressureRating));
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));
+ LungHealthButton.Visible = true;
+
BloodLabel.Visible = false;
BloodText.Visible = false;
BloodButton.Visible = false;
@@ -275,18 +282,21 @@ namespace Content.Client.HealthAnalyzer.UI
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;
BloodLabel.Visible = true;
BloodText.Visible = true;
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml b/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml
new file mode 100644
index 0000000000..c77cc2662c
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml.cs b/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml.cs
new file mode 100644
index 0000000000..640f9cfd58
--- /dev/null
+++ b/Content.Client/HealthAnalyzer/UI/Tooltips/LungHealthTooltip.xaml.cs
@@ -0,0 +1,15 @@
+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 LungHealthTooltip : PanelContainer
+{
+ public LungHealthTooltip()
+ {
+ RobustXamlLoader.Load(this);
+ }
+}
diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs
index 0fa3a492b4..eca008d8f4 100644
--- a/Content.Server/Body/Systems/RespiratorSystem.cs
+++ b/Content.Server/Body/Systems/RespiratorSystem.cs
@@ -140,7 +140,15 @@ public sealed class RespiratorSystem : EntitySystem
if (ev.Gas is null)
return;
- var gas = ev.Gas.RemoveVolume(entity.Comp.BreathVolume);
+ // Begin Offbrand
+ var breathEv = new Content.Shared._Offbrand.Wounds.BeforeBreathEvent(entity.Comp.BreathVolume);
+ RaiseLocalEvent(entity, ref breathEv);
+
+ var gas = ev.Gas.RemoveVolume(breathEv.BreathVolume);
+
+ var beforeEv = new Content.Shared._Offbrand.Wounds.BeforeInhaledGasEvent(gas);
+ RaiseLocalEvent(entity, ref beforeEv);
+ // End Offbrand
var inhaleEv = new InhaledGasEvent(gas);
RaiseLocalEvent(entity, ref inhaleEv);
diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs
index 32e9a402e0..536679192d 100644
--- a/Content.Server/Zombies/ZombieSystem.Transform.cs
+++ b/Content.Server/Zombies/ZombieSystem.Transform.cs
@@ -170,6 +170,9 @@ public sealed partial class ZombieSystem
RemComp(target);
RemComp(target);
RemComp(target);
+ RemComp(target);
+ RemComp(target);
+ RemComp(target);
var entProto = _protoManager.Index(AddOnWoundableZombified);
EntityManager.RemoveComponents(target, entProto.Components);
diff --git a/Content.Server/_Offbrand/Wounds/LungDamageTemperatureSystem.cs b/Content.Server/_Offbrand/Wounds/LungDamageTemperatureSystem.cs
new file mode 100644
index 0000000000..cc97ecfe42
--- /dev/null
+++ b/Content.Server/_Offbrand/Wounds/LungDamageTemperatureSystem.cs
@@ -0,0 +1,35 @@
+using Content.Server.Temperature.Components;
+using Content.Shared._Offbrand.Wounds;
+
+namespace Content.Server._Offbrand.Wounds;
+
+public sealed class LungDamageTemperatureSystem : EntitySystem
+{
+ [Dependency] private readonly LungDamageSystem _lungDamage = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnBeforeInhaledGas);
+ }
+
+ private void OnBeforeInhaledGas(Entity ent, ref BeforeInhaledGasEvent args)
+ {
+ var temperature = Comp(ent);
+
+ var heatDamageThreshold = temperature.ParentHeatDamageThreshold ?? temperature.HeatDamageThreshold;
+ var coldDamageThreshold = temperature.ParentColdDamageThreshold ?? temperature.ColdDamageThreshold;
+
+ if (args.Gas.Temperature >= heatDamageThreshold)
+ {
+ var damage = ent.Comp.HeatCoefficient * args.Gas.Temperature + ent.Comp.HeatConstant;
+ _lungDamage.TryModifyDamage(ent.Owner, damage);
+ }
+ else if (args.Gas.Temperature <= coldDamageThreshold)
+ {
+ var damage = ent.Comp.ColdCoefficient * args.Gas.Temperature + ent.Comp.ColdConstant;
+ _lungDamage.TryModifyDamage(ent.Owner, damage);
+ }
+ }
+}
diff --git a/Content.Shared/_Offbrand/EntityEffects/LungDamage.cs b/Content.Shared/_Offbrand/EntityEffects/LungDamage.cs
new file mode 100644
index 0000000000..7c9e8584ac
--- /dev/null
+++ b/Content.Shared/_Offbrand/EntityEffects/LungDamage.cs
@@ -0,0 +1,32 @@
+using Content.Shared._Offbrand.Wounds;
+using Content.Shared.EntityEffects;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._Offbrand.EntityEffects;
+
+public sealed partial class LungDamage : EntityEffectCondition
+{
+ [DataField]
+ public FixedPoint2 Max = FixedPoint2.MaxValue;
+
+ [DataField]
+ public FixedPoint2 Min = FixedPoint2.Zero;
+
+ public override bool Condition(EntityEffectBaseArgs args)
+ {
+ if (args.EntityManager.TryGetComponent(args.TargetEntity, out var lungDamage))
+ {
+ return lungDamage.Damage >= Min && lungDamage.Damage <= Max;
+ }
+
+ return false;
+ }
+
+ public override string GuidebookExplanation(IPrototypeManager prototype)
+ {
+ return Loc.GetString("reagent-effect-condition-guidebook-lung-damage",
+ ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()),
+ ("min", Min.Float()));
+ }
+}
diff --git a/Content.Shared/_Offbrand/EntityEffects/ModifyLungDamage.cs b/Content.Shared/_Offbrand/EntityEffects/ModifyLungDamage.cs
new file mode 100644
index 0000000000..ae1d579ba5
--- /dev/null
+++ b/Content.Shared/_Offbrand/EntityEffects/ModifyLungDamage.cs
@@ -0,0 +1,31 @@
+using Content.Shared._Offbrand.Wounds;
+using Content.Shared.EntityEffects;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._Offbrand.EntityEffects;
+
+public sealed partial class ModifyLungDamage : EntityEffect
+{
+ [DataField(required: true)]
+ public FixedPoint2 Amount;
+
+ protected override string ReagentEffectGuidebookText(IPrototypeManager prototype, IEntitySystemManager entSys)
+ {
+ if (Amount < FixedPoint2.Zero)
+ return Loc.GetString("reagent-effect-guidebook-modify-lung-damage-heals", ("chance", Probability), ("amount", -Amount));
+ else
+ return Loc.GetString("reagent-effect-guidebook-modify-lung-damage-deals", ("chance", Probability), ("amount", Amount));
+ }
+
+ public override void Effect(EntityEffectBaseArgs args)
+ {
+ var scale = FixedPoint2.New(1);
+
+ if (args is EntityEffectReagentArgs reagentArgs)
+ scale = reagentArgs.Scale;
+
+ args.EntityManager.System()
+ .TryModifyDamage(args.TargetEntity, Amount * scale);
+ }
+}
diff --git a/Content.Shared/_Offbrand/Surgery/ChangeLungDamage.cs b/Content.Shared/_Offbrand/Surgery/ChangeLungDamage.cs
new file mode 100644
index 0000000000..fae2ac21f0
--- /dev/null
+++ b/Content.Shared/_Offbrand/Surgery/ChangeLungDamage.cs
@@ -0,0 +1,17 @@
+using Content.Shared._Offbrand.Wounds;
+using Content.Shared.Construction;
+using Content.Shared.FixedPoint;
+
+namespace Content.Shared._Offbrand.Surgery;
+
+[DataDefinition]
+public sealed partial class ChangeLungDamage : IGraphAction
+{
+ [DataField(required: true)]
+ public FixedPoint2 Amount;
+
+ public void PerformAction(EntityUid uid, EntityUid? userUid, IEntityManager entityManager)
+ {
+ entityManager.System().TryModifyDamage(uid, Amount);
+ }
+}
diff --git a/Content.Shared/_Offbrand/Surgery/LungDamage.cs b/Content.Shared/_Offbrand/Surgery/LungDamage.cs
new file mode 100644
index 0000000000..d99595ac9a
--- /dev/null
+++ b/Content.Shared/_Offbrand/Surgery/LungDamage.cs
@@ -0,0 +1,47 @@
+using Content.Shared._Offbrand.Wounds;
+using Content.Shared.Construction;
+using Content.Shared.Examine;
+using Content.Shared.FixedPoint;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._Offbrand.Surgery;
+
+[DataDefinition]
+public sealed partial class LungDamage : IGraphCondition
+{
+ [DataField]
+ public FixedPoint2 Max = FixedPoint2.MaxValue;
+
+ [DataField]
+ public FixedPoint2 Min = FixedPoint2.Zero;
+
+ public bool Condition(EntityUid uid, IEntityManager entityManager)
+ {
+ if (!entityManager.TryGetComponent(uid, out var lungDamage))
+ return false;
+
+ return lungDamage.Damage >= Min && lungDamage.Damage <= Max;
+ }
+
+ public bool DoExamine(ExaminedEvent args)
+ {
+ if (!IoCManager.Resolve().TryGetComponent(args.Examined, out var lungDamage))
+ return false;
+
+ if (lungDamage.Damage >= Min && lungDamage.Damage <= Max)
+ return false;
+
+ args.PushMarkup(Loc.GetString("construction-examine-lung-damage-range", ("min", Min.Float()), ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float())));
+ return true;
+ }
+
+ public IEnumerable GenerateGuideEntry()
+ {
+ yield return new ConstructionGuideEntry()
+ {
+ Localization = "construction-step-lung-damage-range",
+ Arguments =
+ [ ("min", Min.Float()), ("max", Max == FixedPoint2.MaxValue ? (float) int.MaxValue : Max.Float()) ],
+ };
+ }
+}
diff --git a/Content.Shared/_Offbrand/Wounds/LungDamageComponent.cs b/Content.Shared/_Offbrand/Wounds/LungDamageComponent.cs
new file mode 100644
index 0000000000..4704b43923
--- /dev/null
+++ b/Content.Shared/_Offbrand/Wounds/LungDamageComponent.cs
@@ -0,0 +1,92 @@
+using Content.Shared.Alert;
+using Content.Shared.Atmos;
+using Content.Shared.FixedPoint;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._Offbrand.Wounds;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(LungDamageSystem))]
+public sealed partial class LungDamageComponent : Component
+{
+ ///
+ /// The maximum amount of damage this entity's lungs can take
+ ///
+ [DataField(required: true)]
+ public FixedPoint2 MaxDamage;
+
+ ///
+ /// The current amount of accrued damage
+ ///
+ [DataField(required: true), AutoNetworkedField]
+ public FixedPoint2 Damage;
+}
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(LungDamageSystem))]
+public sealed partial class LungDamageAlertsComponent : Component
+{
+ ///
+ /// The alert to display depending on the amount of lung damage. Highest key is selected.
+ ///
+ [DataField(required: true)]
+ public SortedDictionary> AlertThresholds;
+
+ ///
+ /// The alert category of the alerts.
+ ///
+ [DataField(required: true)]
+ public ProtoId AlertCategory;
+
+ [DataField, AutoNetworkedField]
+ public ProtoId? CurrentAlertThresholdState;
+}
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class LungDamageOnInhaledAirTemperatureComponent : Component
+{
+ ///
+ /// The coefficient for how much damage is taken when the air temperature is below 's ColdDamageThreshold
+ ///
+ [DataField(required: true)]
+ public float ColdCoefficient;
+
+ ///
+ /// The constant for how much damage is taken when the air temperature is below 's ColdDamageThreshold
+ ///
+ [DataField(required: true)]
+ public float ColdConstant;
+
+ ///
+ /// The coefficient for how much damage is taken when the air temperature is below 's HeatDamageThreshold
+ ///
+ [DataField(required: true)]
+ public float HeatCoefficient;
+
+ ///
+ /// The constant for how much damage is taken when the air temperature is below 's HeatDamageThreshold
+ ///
+ [DataField(required: true)]
+ public float HeatConstant;
+}
+
+///
+/// Event raised when an entity is about to take a breath
+///
+/// The volume to breathe in.
+[ByRefEvent]
+public record struct BeforeBreathEvent(float BreathVolume);
+
+///
+/// Event raised when an entity successfully inhales a gas, before storing the gas internally
+///
+/// The gas we're inhaling.
+[ByRefEvent]
+public record struct BeforeInhaledGasEvent(GasMixture Gas);
+
+///
+/// Event raised when an entity's lung damage changes
+///
+[ByRefEvent]
+public record struct AfterLungDamageChangedEvent;
diff --git a/Content.Shared/_Offbrand/Wounds/LungDamageSystem.cs b/Content.Shared/_Offbrand/Wounds/LungDamageSystem.cs
new file mode 100644
index 0000000000..fee02f1459
--- /dev/null
+++ b/Content.Shared/_Offbrand/Wounds/LungDamageSystem.cs
@@ -0,0 +1,69 @@
+using Content.Shared.Alert;
+using Content.Shared.FixedPoint;
+using Content.Shared.Rejuvenate;
+
+namespace Content.Shared._Offbrand.Wounds;
+
+public sealed class LungDamageSystem : EntitySystem
+{
+ [Dependency] private readonly AlertsSystem _alerts = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnBeforeBreath);
+ SubscribeLocalEvent(OnRejuvenate);
+ SubscribeLocalEvent(OnLungDamageChanged);
+ SubscribeLocalEvent(OnAlertsShutdown);
+ }
+
+ private void OnBeforeBreath(Entity ent, ref BeforeBreathEvent args)
+ {
+ args.BreathVolume *= 1f - MathF.Pow(ent.Comp.Damage.Float() / ent.Comp.MaxDamage.Float(), 3f);
+ }
+
+ public void TryModifyDamage(Entity ent, FixedPoint2 damage)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ ent.Comp.Damage = FixedPoint2.Clamp(ent.Comp.Damage + damage, FixedPoint2.Zero, ent.Comp.MaxDamage);
+ Dirty(ent);
+
+ var evt = new AfterLungDamageChangedEvent();
+ RaiseLocalEvent(ent, ref evt);
+ }
+
+ private void OnRejuvenate(Entity ent, ref RejuvenateEvent args)
+ {
+ ent.Comp.Damage = FixedPoint2.Zero;
+ Dirty(ent);
+
+ var evt = new AfterLungDamageChangedEvent();
+ RaiseLocalEvent(ent, ref evt);
+ }
+
+ private void OnLungDamageChanged(Entity ent, ref AfterLungDamageChangedEvent args)
+ {
+ var lungDamage = Comp(ent);
+ var targetAlert = ent.Comp.AlertThresholds.HighestMatch(lungDamage.Damage);
+
+ if (targetAlert == ent.Comp.CurrentAlertThresholdState)
+ return;
+
+ if (targetAlert is { } alert)
+ {
+ _alerts.ShowAlert(ent.Owner, alert);
+ }
+ else
+ {
+ _alerts.ClearAlertCategory(ent.Owner, ent.Comp.AlertCategory);
+ }
+ }
+
+ private void OnAlertsShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ _alerts.ClearAlertCategory(ent.Owner, ent.Comp.AlertCategory);
+ }
+}
diff --git a/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs b/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs
index 56d7a8e56a..ec152fde96 100644
--- a/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs
+++ b/Content.Shared/_Offbrand/Wounds/WoundableHealthAnalyzer.cs
@@ -45,6 +45,12 @@ public sealed partial class WoundableHealthAnalyzerData
[DataField]
public AttributeRating HeartRateRating;
+ [DataField]
+ public double LungHealth;
+
+ [DataField]
+ public AttributeRating LungHealthRating;
+
[DataField]
public bool AnyVitalCritical;
@@ -124,8 +130,12 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
if (!TryComp(uid, out var brainDamage))
return null;
+ if (!TryComp(uid, out var lungDamage))
+ return null;
+
var brainHealth = 1d - ((double)brainDamage.Damage / (double)brainDamage.MaxDamage);
var heartHealth = 1d - ((double)heartrate.Damage / (double)heartrate.MaxDamage);
+ var lungHealth = 1d - ((double)lungDamage.Damage / (double)lungDamage.MaxDamage);
var strain = _heart.HeartStrain((uid, heartrate)).Double() / 4d;
var (upper, lower) = _heart.BloodPressure((uid, heartrate));
var oxygenation = _heart.BloodOxygenation((uid, heartrate)).Double();
@@ -149,6 +159,8 @@ public abstract class SharedWoundableHealthAnalyzerSystem : EntitySystem
BloodFlowRating = RateHigherIsBetter(flow),
HeartRate = _heart.HeartRate((uid, heartrate)).Int(),
HeartRateRating = !heartrate.Running ? AttributeRating.Dangerous : RateHigherIsWorse(strain),
+ LungHealth = lungHealth,
+ LungHealthRating = RateHigherIsBetter(lungHealth),
AnyVitalCritical = _shockThresholds.IsCritical(uid) || _brainDamage.IsCritical(uid) || _heart.IsCritical(uid),
Wounds = withWounds ? SampleWounds(uid) : null,
Reagents = reagents,
diff --git a/Resources/Locale/en-US/_Offbrand/alerts.ftl b/Resources/Locale/en-US/_Offbrand/alerts.ftl
index 0f68a68766..7eed45485f 100644
--- a/Resources/Locale/en-US/_Offbrand/alerts.ftl
+++ b/Resources/Locale/en-US/_Offbrand/alerts.ftl
@@ -33,3 +33,12 @@ alerts-critical-brain-damage-desc = Your brain cannot control your body.
alerts-brain-death-name = [color=crimson]Brain Dead[/color]
alerts-brain-death-desc = Your brain has faded away into nothingness.
+
+alerts-minor-lung-damage-name = [color=yellow]Minor Lung Damage[/color]
+alerts-minor-lung-damage-desc = Your lungs have taken minor damage. Turn up the pressure on your internals and seek care.
+
+alerts-severe-lung-damage-name = [color=red]Severe Lung Damage[/color]
+alerts-severe-lung-damage-desc = Your lungs are struggling to breathe. Turn up the pressure on your internals and seek care promptly.
+
+alerts-critical-lung-damage-name = [color=crimson]Lung Failure[/color]
+alerts-critical-lung-damage-desc = You cannot breathe well if at all. Seek care promptly.
diff --git a/Resources/Locale/en-US/_Offbrand/construction.ftl b/Resources/Locale/en-US/_Offbrand/construction.ftl
index 6c1beeeffa..fa4a368df9 100644
--- a/Resources/Locale/en-US/_Offbrand/construction.ftl
+++ b/Resources/Locale/en-US/_Offbrand/construction.ftl
@@ -21,4 +21,20 @@ construction-step-heart-damage-range = { $max ->
}
}
+construction-examine-lung-damage-range = { $max ->
+ [2147483648] The target needs to have at least {NATURALFIXED($min, 2)} lung damage.
+ *[other] { $min ->
+ [0] The target needs to have at most {NATURALFIXED($max, 2)} lung damage.
+ *[other] The target needs to have between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} lung damage.
+ }
+}
+
+construction-step-lung-damage-range = { $max ->
+ [2147483648] The target needs to have at least {NATURALFIXED($min, 2)} lung damage.
+ *[other] { $min ->
+ [0] The target needs to have at most {NATURALFIXED($max, 2)} lung damage.
+ *[other] The target needs to have between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} lung damage.
+ }
+}
+
construction-component-to-perform-header = To perform {$targetName}...
diff --git a/Resources/Locale/en-US/_Offbrand/effects.ftl b/Resources/Locale/en-US/_Offbrand/effects.ftl
index 39ed12a032..425c6d8290 100644
--- a/Resources/Locale/en-US/_Offbrand/effects.ftl
+++ b/Resources/Locale/en-US/_Offbrand/effects.ftl
@@ -24,6 +24,14 @@ reagent-effect-guidebook-modify-heart-damage-deals = { $chance ->
[1] Deals { $amount } heart damage
*[other] deal { $amount } heart damage
}
+reagent-effect-guidebook-modify-lung-damage-heals = { $chance ->
+ [1] Heals { $amount } lung health
+ *[other] heal { $amount } lung health
+}
+reagent-effect-guidebook-modify-lung-damage-deals = { $chance ->
+ [1] Deals { $amount } lung damage
+ *[other] deal { $amount } lung damage
+}
reagent-effect-guidebook-clamp-wounds = { $probability ->
[1] Stops bleeding in wounds with { NATURALPERCENT($chance, 2) } chance per wound
*[other] stop bleeding in wounds with { NATURALPERCENT($chance, 2) } chance per wound
@@ -35,6 +43,13 @@ reagent-effect-condition-guidebook-heart-damage = { $max ->
*[other] it has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} heart damage
}
}
+reagent-effect-condition-guidebook-lung-damage = { $max ->
+ [2147483648] it has at least {NATURALFIXED($min, 2)} lung damage
+ *[other] { $min ->
+ [0] it has at most {NATURALFIXED($max, 2)} lung damage
+ *[other] it has between {NATURALFIXED($min, 2)} and {NATURALFIXED($max, 2)} lung damage
+ }
+}
reagent-effect-condition-guidebook-brain-damage = { $max ->
[2147483648] it has at least {NATURALFIXED($min, 2)} brain damage
*[other] { $min ->
diff --git a/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl b/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl
index c07c54431f..d7c4a3f0d9 100644
--- a/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl
+++ b/Resources/Locale/en-US/_Offbrand/health-analyzer.ftl
@@ -14,9 +14,11 @@ 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-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) }
@@ -101,6 +103,13 @@ health-analyzer-cryostasis-temperature-tooltip =
This temperature has a cryostasis factor of {$factor}%.
+health-analyzer-lung-health-tooltip =
+ The patient's lung health.
+
+ The lower this number, the more difficulty they have breathing.
+
+ If the lung health is low, consider putting the patient on higher-pressure internals.
+
health-analyzer-blood-tooltip =
The patient's blood volume.
diff --git a/Resources/Locale/en-US/_Offbrand/surgery.ftl b/Resources/Locale/en-US/_Offbrand/surgery.ftl
index c3a83b89e4..07cbf831dd 100644
--- a/Resources/Locale/en-US/_Offbrand/surgery.ftl
+++ b/Resources/Locale/en-US/_Offbrand/surgery.ftl
@@ -14,6 +14,12 @@ surgery-heart-treatment-suture-desc = Treats heart damage.
surgery-heart-treatment-medicated-suture-name = heart treatment (medicated sutures)
surgery-heart-treatment-medicated-suture-desc = Treats heart damage.
+surgery-lung-treatment-suture-name = lung treatment (sutures)
+surgery-lung-treatment-suture-desc = Treats lung damage.
+
+surgery-lung-treatment-medicated-suture-name = lung treatment (medicated sutures)
+surgery-lung-treatment-medicated-suture-desc = Treats lung damage.
+
surgery-husking-treatment-name = husking treatment
surgery-husking-treatment-desc = Reconstructs a husked body.
diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml
index f65bc231d3..8ac9c25ec4 100644
--- a/Resources/Prototypes/Alerts/alerts.yml
+++ b/Resources/Prototypes/Alerts/alerts.yml
@@ -7,6 +7,7 @@
- category: Health
- category: Pain # Offbrand
- category: BrainDamage # Offbrand
+ - category: LungDamage # Offbrand
- category: Stamina
- alertType: SuitPower
- category: Internals
diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml
index f16b8c698a..f2b55df2aa 100644
--- a/Resources/Prototypes/Entities/Mobs/Species/base.yml
+++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml
@@ -224,6 +224,7 @@
- BaseMobHeartrate # Offbrand
- BaseMobBrain # Offbrand
- BaseMobCryostasis # Offbrand
+ - BaseMobLungDamage # Offbrand
id: BaseMobSpeciesOrganic
abstract: true
components:
diff --git a/Resources/Prototypes/Reagents/medicine.yml b/Resources/Prototypes/Reagents/medicine.yml
index 5ba4345b99..e3d3161356 100644
--- a/Resources/Prototypes/Reagents/medicine.yml
+++ b/Resources/Prototypes/Reagents/medicine.yml
@@ -202,6 +202,11 @@
conditions:
- !type:Temperature
max: 213.0
+ - !type:ModifyLungDamage
+ amount: -0.3
+ conditions:
+ - !type:Temperature
+ max: 213.0
- !type:EvenHealthChange
conditions:
- !type:Temperature
diff --git a/Resources/Prototypes/Reagents/toxins.yml b/Resources/Prototypes/Reagents/toxins.yml
index c576082b83..299bcdca19 100644
--- a/Resources/Prototypes/Reagents/toxins.yml
+++ b/Resources/Prototypes/Reagents/toxins.yml
@@ -339,6 +339,8 @@
damage:
groups:
Airloss: 10
+ - !type:ModifyLungDamage # Offbrand
+ amount: 2
- type: reagent
id: MindbreakerToxin
@@ -515,6 +517,11 @@
conditions:
- !type:MetaboliteThreshold
min: 15
+ - !type:ModifyLungDamage
+ amount: 0.1
+ conditions:
+ - !type:MetaboliteThreshold
+ min: 15
- !type:AdjustTemperature
amount: 3000
conditions:
diff --git a/Resources/Prototypes/_Offbrand/alerts.yml b/Resources/Prototypes/_Offbrand/alerts.yml
index ca2d6eb300..81cde1aae2 100644
--- a/Resources/Prototypes/_Offbrand/alerts.yml
+++ b/Resources/Prototypes/_Offbrand/alerts.yml
@@ -32,6 +32,36 @@
- type: alertCategory
id: BrainDamage
+- type: alertCategory
+ id: LungDamage
+
+- type: alert
+ id: MinorLungDamage
+ category: LungDamage
+ icons:
+ - sprite: /Textures/_Offbrand/Alerts/lung_damage.rsi
+ state: minor
+ name: alerts-minor-lung-damage-name
+ description: alerts-minor-lung-damage-desc
+
+- type: alert
+ id: SevereLungDamage
+ category: LungDamage
+ icons:
+ - sprite: /Textures/_Offbrand/Alerts/lung_damage.rsi
+ state: severe
+ name: alerts-severe-lung-damage-name
+ description: alerts-severe-lung-damage-desc
+
+- type: alert
+ id: CriticalLungDamage
+ category: LungDamage
+ icons:
+ - sprite: /Textures/_Offbrand/Alerts/lung_damage.rsi
+ state: critical
+ name: alerts-critical-lung-damage-name
+ description: alerts-critical-lung-damage-desc
+
- type: alert
id: SuppressedPain
category: Pain
diff --git a/Resources/Prototypes/_Offbrand/base_mob.yml b/Resources/Prototypes/_Offbrand/base_mob.yml
index b8ca40b6bf..e11fb0798a 100644
--- a/Resources/Prototypes/_Offbrand/base_mob.yml
+++ b/Resources/Prototypes/_Offbrand/base_mob.yml
@@ -297,3 +297,22 @@
- type: CryostasisFactor
temperatureCoefficient: -0.059
temperatureConstant: 19
+
+- type: entity
+ abstract: true
+ id: BaseMobLungDamage
+ components:
+ - type: LungDamage
+ damage: 0
+ maxDamage: 100
+ - type: LungDamageOnInhaledAirTemperature
+ heatCoefficient: 0.005
+ heatConstant: -1.375
+ coldCoefficient: -0.01
+ coldConstant: 3
+ - type: LungDamageAlerts
+ alertThresholds:
+ 15: MinorLungDamage
+ 40: SevereLungDamage
+ 60: CriticalLungDamage
+ alertCategory: LungDamage
diff --git a/Resources/Prototypes/_Offbrand/reagents.yml b/Resources/Prototypes/_Offbrand/reagents.yml
index 83013318c4..ae17ccacee 100644
--- a/Resources/Prototypes/_Offbrand/reagents.yml
+++ b/Resources/Prototypes/_Offbrand/reagents.yml
@@ -169,6 +169,11 @@
conditions:
- !type:HeartDamage
max: 25
+ - !type:ModifyLungDamage
+ amount: -0.2
+ conditions:
+ - !type:LungDamage
+ max: 50
- !type:HealthChange
conditions:
- !type:ReagentThreshold
@@ -211,6 +216,11 @@
conditions:
- !type:Temperature
max: 170.0
+ - !type:ModifyLungDamage
+ amount: -0.6
+ conditions:
+ - !type:Temperature
+ max: 170.0
- !type:EvenHealthChange
conditions:
- !type:Temperature
@@ -336,7 +346,7 @@
- !type:TotalGroupDamage
group: Airloss
min: 4
- - !type:ModifyHeartDamage
+ - !type:ModifyLungDamage
amount: 0.05
- type: reagent
diff --git a/Resources/Prototypes/_Offbrand/surgery_constructions.yml b/Resources/Prototypes/_Offbrand/surgery_constructions.yml
index 3a7488a333..db7a5af37c 100644
--- a/Resources/Prototypes/_Offbrand/surgery_constructions.yml
+++ b/Resources/Prototypes/_Offbrand/surgery_constructions.yml
@@ -31,6 +31,28 @@
category: Surgery
objectType: NodeToNode
+- type: construction
+ hide: true
+ id: LungTreatmentSuture
+ name: surgery-lung-treatment-suture-name
+ description: surgery-lung-treatment-suture-desc
+ graph: SurgeryGraph
+ startNode: Base
+ targetNode: LungTreatmentSuture
+ category: Surgery
+ objectType: NodeToNode
+
+- type: construction
+ hide: true
+ id: LungTreatmentMedicatedSuture
+ name: surgery-lung-treatment-medicated-suture-name
+ description: surgery-lung-treatment-medicated-suture-desc
+ graph: SurgeryGraph
+ startNode: Base
+ targetNode: LungTreatmentMedicatedSuture
+ category: Surgery
+ objectType: NodeToNode
+
- type: construction
hide: true
id: HuskingTreatment
diff --git a/Resources/Prototypes/_Offbrand/surgery_graph.yml b/Resources/Prototypes/_Offbrand/surgery_graph.yml
index b095b6cfa3..ce2fc0fd8f 100644
--- a/Resources/Prototypes/_Offbrand/surgery_graph.yml
+++ b/Resources/Prototypes/_Offbrand/surgery_graph.yml
@@ -157,6 +157,36 @@
steps:
- tool: Drilling
doAfter: 2
+ - to: OpenRibCageRecoupling
+ steps:
+ - tool: Recoupling
+ doAfter: 1
+ - node: OpenRibCageRecoupling
+ edges:
+ - to: OpenRibCage
+ steps:
+ - tool: Clamping
+ doAfter: 1
+ - to: LungTreatmentSuture
+ conditions: &lung-damage-conditions
+ - !type:LungDamage
+ min: 1
+ completed:
+ - !type:ChangeLungDamage
+ amount: -5
+ steps:
+ - material: Suture
+ amount: 1
+ doAfter: 5
+ - to: LungTreatmentMedicatedSuture
+ conditions: *lung-damage-conditions
+ completed:
+ - !type:ChangeLungDamage
+ amount: -10
+ steps:
+ - material: MedicatedSuture
+ amount: 1
+ doAfter: 3
- node: OpenRibCageMending
edges:
- to: OpenRibCageSetting
@@ -204,6 +234,20 @@
- !type:SetNode
node: OpenRibCage
repeatConditions: *heart-damage-conditions
+ - node: LungTreatmentSuture
+ header: construction-component-to-perform-header
+ localizedName: surgery-lung-treatment-suture-name
+ actions:
+ - !type:SetNode
+ node: OpenRibCageRecoupling
+ repeatConditions: *lung-damage-conditions
+ - node: LungTreatmentMedicatedSuture
+ header: construction-component-to-perform-header
+ localizedName: surgery-lung-treatment-medicated-suture-name
+ actions:
+ - !type:SetNode
+ node: OpenRibCageRecoupling
+ repeatConditions: *lung-damage-conditions
- node: HuskingTreatment
header: construction-component-to-perform-header
localizedName: surgery-husking-treatment-name
diff --git a/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/critical.png b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/critical.png
new file mode 100644
index 0000000000..0b0db1a13d
Binary files /dev/null and b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/critical.png differ
diff --git a/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/meta.json b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/meta.json
new file mode 100644
index 0000000000..892d7abd53
--- /dev/null
+++ b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/meta.json
@@ -0,0 +1,20 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-3.0",
+ "copyright": "sprites by sowelipililimute, inspired by sprites from /tg/station",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "minor"
+ },
+ {
+ "name": "severe"
+ },
+ {
+ "name": "critical"
+ }
+ ]
+}
diff --git a/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/minor.png b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/minor.png
new file mode 100644
index 0000000000..ed54499219
Binary files /dev/null and b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/minor.png differ
diff --git a/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/severe.png b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/severe.png
new file mode 100644
index 0000000000..877fed81e1
Binary files /dev/null and b/Resources/Textures/_Offbrand/Alerts/lung_damage.rsi/severe.png differ