using Content.Server.Administration.Logs; using Content.Server.Atmos.Components; using Content.Shared.Alert; using Content.Shared.Atmos; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.FixedPoint; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Robust.Shared.Containers; namespace Content.Server.Atmos.EntitySystems { public sealed class BarotraumaSystem : EntitySystem { [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly AlertsSystem _alertsSystem = default!; [Dependency] private readonly IAdminLogManager _adminLogger= default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; private const float UpdateTimer = 1f; private float _timer; public override void Initialize() { SubscribeLocalEvent(OnPressureProtectionEquipped); SubscribeLocalEvent(OnPressureProtectionUnequipped); SubscribeLocalEvent(OnUpdateResistance); SubscribeLocalEvent(OnUpdateResistance); SubscribeLocalEvent(OnPressureImmuneInit); SubscribeLocalEvent(OnPressureImmuneRemove); } private void OnPressureImmuneInit(EntityUid uid, PressureImmunityComponent pressureImmunity, ComponentInit args) { if (TryComp(uid, out var barotrauma)) { barotrauma.HasImmunity = true; } } private void OnPressureImmuneRemove(EntityUid uid, PressureImmunityComponent pressureImmunity, ComponentRemove args) { if (TryComp(uid, out var barotrauma)) { barotrauma.HasImmunity = false; } } /// /// Generic method for updating resistance on component Lifestage events /// private void OnUpdateResistance(EntityUid uid, PressureProtectionComponent pressureProtection, EntityEventArgs args) { if (TryComp(uid, out var barotrauma)) { UpdateCachedResistances(uid, barotrauma); } } private void OnPressureProtectionEquipped(EntityUid uid, PressureProtectionComponent pressureProtection, GotEquippedEvent args) { if (TryComp(args.Equipee, out var barotrauma) && barotrauma.ProtectionSlots.Contains(args.Slot)) { UpdateCachedResistances(args.Equipee, barotrauma); } } private void OnPressureProtectionUnequipped(EntityUid uid, PressureProtectionComponent pressureProtection, GotUnequippedEvent args) { if (TryComp(args.Equipee, out var barotrauma) && barotrauma.ProtectionSlots.Contains(args.Slot)) { UpdateCachedResistances(args.Equipee, barotrauma); } } /// /// Computes the pressure resistance for the entity coming from the equipment and any innate resistance. /// The ProtectionSlots field of the Barotrauma component specifies which parts must be protected for the protection to have any effet. /// private void UpdateCachedResistances(EntityUid uid, BarotraumaComponent barotrauma) { if (barotrauma.ProtectionSlots.Count != 0) { if (!TryComp(uid, out InventoryComponent? inv) || !TryComp(uid, out ContainerManagerComponent? contMan)) { return; } var hPModifier = float.MinValue; var hPMultiplier = float.MinValue; var lPModifier = float.MaxValue; var lPMultiplier = float.MaxValue; foreach (var slot in barotrauma.ProtectionSlots) { if (!_inventorySystem.TryGetSlotEntity(uid, slot, out var equipment, inv, contMan) || !TryComp(equipment, out PressureProtectionComponent? protection)) { // Missing protection, skin is exposed. hPModifier = 0f; hPMultiplier = 1f; lPModifier = 0f; lPMultiplier = 1f; break; } // The entity is as protected as its weakest part protection hPModifier = Math.Max(hPModifier, protection.HighPressureModifier); hPMultiplier = Math.Max(hPMultiplier, protection.HighPressureMultiplier); lPModifier = Math.Min(lPModifier, protection.LowPressureModifier); lPMultiplier = Math.Min(lPMultiplier, protection.LowPressureMultiplier); } barotrauma.HighPressureModifier = hPModifier; barotrauma.HighPressureMultiplier = hPMultiplier; barotrauma.LowPressureModifier = lPModifier; barotrauma.LowPressureMultiplier = lPMultiplier; } // any innate pressure resistance ? if (TryComp(uid, out var innatePressureProtection)) { barotrauma.HighPressureModifier += innatePressureProtection.HighPressureModifier; barotrauma.HighPressureMultiplier *= innatePressureProtection.HighPressureMultiplier; barotrauma.LowPressureModifier += innatePressureProtection.LowPressureModifier; barotrauma.LowPressureMultiplier *= innatePressureProtection.LowPressureMultiplier; } } /// /// Returns adjusted pressure after having applied resistances from equipment and innate (if any), to check against a low pressure hazard threshold /// public float GetFeltLowPressure(EntityUid uid, BarotraumaComponent barotrauma, float environmentPressure) { if (barotrauma.HasImmunity) { return Atmospherics.OneAtmosphere; } return (environmentPressure + barotrauma.LowPressureModifier) * (barotrauma.LowPressureMultiplier); } /// /// Returns adjusted pressure after having applied resistances from equipment and innate (if any), to check against a high pressure hazard threshold /// public float GetFeltHighPressure(EntityUid uid, BarotraumaComponent barotrauma, float environmentPressure) { if (barotrauma.HasImmunity) { return Atmospherics.OneAtmosphere; } return (environmentPressure + barotrauma.HighPressureModifier) * (barotrauma.HighPressureMultiplier); } public override void Update(float frameTime) { _timer += frameTime; if (_timer < UpdateTimer) return; _timer -= UpdateTimer; var enumerator = EntityQueryEnumerator(); while (enumerator.MoveNext(out var uid, out var barotrauma, out var damageable)) { var totalDamage = FixedPoint2.Zero; foreach (var (barotraumaDamageType, _) in barotrauma.Damage.DamageDict) { if (!damageable.Damage.DamageDict.TryGetValue(barotraumaDamageType, out var damage)) continue; totalDamage += damage; } if (totalDamage >= barotrauma.MaxDamage) continue; var pressure = 1f; if (_atmosphereSystem.GetContainingMixture(uid) is {} mixture) { pressure = MathF.Max(mixture.Pressure, 1f); } switch (pressure) { // Low pressure. case <= Atmospherics.WarningLowPressure: pressure = GetFeltLowPressure(uid, barotrauma, pressure); if (pressure > Atmospherics.WarningLowPressure) goto default; // Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear. _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * Atmospherics.LowPressureDamage, true, false); if (!barotrauma.TakingDamage) { barotrauma.TakingDamage = true; _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage"); } if (pressure <= Atmospherics.HazardLowPressure) { _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 2); break; } _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 1); break; // High pressure. case >= Atmospherics.WarningHighPressure: pressure = GetFeltHighPressure(uid, barotrauma, pressure); if (pressure < Atmospherics.WarningHighPressure) goto default; var damageScale = MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage); // Deal damage and ignore resistances. Resistance to pressure damage should be done via pressure protection gear. _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false); if (!barotrauma.TakingDamage) { barotrauma.TakingDamage = true; _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking high pressure damage"); } if (pressure >= Atmospherics.HazardHighPressure) { _alertsSystem.ShowAlert(uid, AlertType.HighPressure, 2); break; } _alertsSystem.ShowAlert(uid, AlertType.HighPressure, 1); break; // Normal pressure. default: if (barotrauma.TakingDamage) { barotrauma.TakingDamage = false; _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} stopped taking pressure damage"); } _alertsSystem.ClearAlertCategory(uid, AlertCategory.Pressure); break; } } } } }