diff --git a/Content.Server/Atmos/Miasma/AntiRottingContainerComponent.cs b/Content.Server/Atmos/Miasma/AntiRottingContainerComponent.cs
deleted file mode 100644
index ed37e16500..0000000000
--- a/Content.Server/Atmos/Miasma/AntiRottingContainerComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Atmos.Miasma
-{
- ///
- /// Entities inside this container will not rot.
- ///
- [RegisterComponent]
- public sealed class AntiRottingContainerComponent : Component
- {}
-}
diff --git a/Content.Server/Atmos/Miasma/BodyPreservedComponent.cs b/Content.Server/Atmos/Miasma/BodyPreservedComponent.cs
deleted file mode 100644
index adff39b43e..0000000000
--- a/Content.Server/Atmos/Miasma/BodyPreservedComponent.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Content.Server.Atmos.Miasma
-{
- ///
- /// Way for natural sources of rotting to tell if there are more unnatural preservation forces at play.
- ///
- [RegisterComponent]
- public sealed class BodyPreservedComponent : Component
- {
- public int PreservationSources = 0;
- }
-}
diff --git a/Content.Server/Atmos/Miasma/MiasmaSystem.cs b/Content.Server/Atmos/Miasma/MiasmaSystem.cs
deleted file mode 100644
index 72748004a9..0000000000
--- a/Content.Server/Atmos/Miasma/MiasmaSystem.cs
+++ /dev/null
@@ -1,330 +0,0 @@
-using Content.Shared.Damage;
-using Content.Shared.Atmos;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Temperature.Systems;
-using Content.Server.Body.Components;
-using Content.Shared.Examine;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Systems;
-using Content.Shared.Rejuvenate;
-using Robust.Server.GameObjects;
-using Robust.Shared.Containers;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Atmos.Miasma
-{
- public sealed class MiasmaSystem : EntitySystem
- {
- [Dependency] private readonly TransformSystem _transformSystem = default!;
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly DamageableSystem _damageableSystem = default!;
- [Dependency] private readonly MobStateSystem _mobState = default!;
- [Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
-
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
-
- /// System Variables
-
- /// Rotting
-
- ///
- /// How often the rotting ticks.
- /// Feel free to weak this if there are perf concerns.
- ///
- private float _rotUpdateRate = 5f;
-
- /// Miasma Disease Pool
- /// Miasma outbreaks are not per-entity,
- /// so this ensures that each entity in the same incident
- /// receives the same disease.
-
- public readonly IReadOnlyList MiasmaDiseasePool = new[]
- {
- "VentCough",
- "AMIV",
- "SpaceCold",
- "SpaceFlu",
- "BirdFlew",
- "VanAusdallsRobovirus",
- "BleedersBite",
- "Plague",
- "TongueTwister",
- "MemeticAmirmir"
- };
-
- ///
- /// The current pool disease.
- ///
- private string _poolDisease = "";
-
- ///
- /// The list of diseases in the pool.
- ///
-
- ///
- /// The target time it waits until..
- /// After that, it resets current time + _poolRepickTime.
- /// Any infection will also reset it to current time + _poolRepickTime.
- ///
- private TimeSpan _diseaseTime = TimeSpan.FromMinutes(5);
-
- ///
- /// How long without an infection before we pick a new disease.
- ///
- private TimeSpan _poolRepickTime = TimeSpan.FromMinutes(5);
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- // Disease pool
-
- if (_timing.CurTime >= _diseaseTime)
- {
- _diseaseTime = _timing.CurTime + _poolRepickTime;
- _poolDisease = _random.Pick(MiasmaDiseasePool);
- }
-
- // Rotting
- foreach (var (rotting, perishable, metadata) in EntityQuery())
- {
- if (!perishable.Progressing)
- continue;
-
- if (!IsRotting(perishable, metadata))
- continue;
-
- if (_timing.CurTime < perishable.RotNextUpdate) // This is where it starts to get noticable on larger animals, no need to run every second
- continue;
-
- perishable.RotNextUpdate = _timing.CurTime + TimeSpan.FromSeconds(_rotUpdateRate);
-
- EnsureComp(perishable.Owner);
-
- if (rotting.DealDamage)
- {
- DamageSpecifier damage = new();
- damage.DamageDict.Add("Blunt", 0.3); // Slowly accumulate enough to gib after like half an hour
- damage.DamageDict.Add("Cellular", 0.3); // Cloning rework might use this eventually
-
- _damageableSystem.TryChangeDamage(perishable.Owner, damage, true, true, origin: perishable.Owner);
- }
-
- if (!TryComp(perishable.Owner, out var physics))
- continue;
- // We need a way to get the mass of the mob alone without armor etc in the future
-
- float molRate = perishable.MolsPerSecondPerUnitMass * _rotUpdateRate;
-
- var transform = Transform(perishable.Owner);
- var indices = _transformSystem.GetGridOrMapTilePosition(perishable.Owner);
-
- var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
- tileMix?.AdjustMoles(Gas.Miasma, molRate * physics.FixturesMass);
- }
- }
-
- public override void Initialize()
- {
- base.Initialize();
- // Core rotting stuff
- SubscribeLocalEvent(OnShutdown);
- SubscribeLocalEvent(OnTempChange);
- SubscribeLocalEvent(OnRottingMobStateChanged);
- SubscribeLocalEvent(OnMobStateChanged);
- SubscribeLocalEvent(OnGibbed);
- SubscribeLocalEvent(OnExamined);
- SubscribeLocalEvent(OnRejuvenate);
- // Containers
- SubscribeLocalEvent(OnEntInserted);
- SubscribeLocalEvent(OnEntRemoved);
- // Fly audiovisual stuff
- SubscribeLocalEvent(OnFliesInit);
- SubscribeLocalEvent(OnFliesShutdown);
-
- // Init disease pool
- _poolDisease = _random.Pick(MiasmaDiseasePool);
- }
-
- private void OnShutdown(EntityUid uid, RottingComponent component, ComponentShutdown args)
- {
- RemComp(uid);
- if (TryComp(uid, out var perishable))
- {
- perishable.TimeOfDeath = TimeSpan.Zero;
- perishable.RotNextUpdate = TimeSpan.Zero;
- }
- }
-
- private void OnTempChange(EntityUid uid, RottingComponent component, OnTemperatureChangeEvent args)
- {
- if (HasComp(uid))
- return;
- bool decompose = (args.CurrentTemperature > Atmospherics.T0C + 0.85f);
- ToggleDecomposition(uid, decompose);
- }
-
- private void OnMobStateChanged(EntityUid uid, PerishableComponent component, MobStateChangedEvent args)
- {
- if (_mobState.IsDead(uid))
- {
- EnsureComp(uid);
- component.TimeOfDeath = _timing.CurTime;
- }
- }
-
- private void OnRottingMobStateChanged(EntityUid uid, RottingComponent component, MobStateChangedEvent args)
- {
- if (args.NewMobState == MobState.Dead)
- return;
- RemCompDeferred(uid, component);
- }
-
- public bool IsRotting(EntityUid uid, PerishableComponent? perishable = null, MetaDataComponent? metadata = null)
- {
- if (!Resolve(uid, ref perishable, ref metadata, false))
- return true;
- return IsRotting(perishable, metadata);
- }
-
- ///
- /// Has enough time passed for to start rotting?
- ///
- public bool IsRotting(PerishableComponent perishable, MetaDataComponent? metadata = null)
- {
- if (perishable.TimeOfDeath == TimeSpan.Zero)
- return false;
-
- if (_timing.CurTime >= perishable.TimeOfDeath + perishable.RotAfter + _metaDataSystem.GetPauseTime(perishable.Owner, metadata))
- return true;
-
- return false;
- }
-
- private void OnGibbed(EntityUid uid, PerishableComponent component, BeingGibbedEvent args)
- {
- if (!TryComp(uid, out var physics))
- return;
-
- if (!IsRotting(component))
- return;
-
- var molsToDump = (component.MolsPerSecondPerUnitMass * physics.FixturesMass) * (float)(_timing.CurTime - component.TimeOfDeath).TotalSeconds;
- var transform = Transform(uid);
- var indices = _transformSystem.GetGridOrMapTilePosition(uid, transform);
- var tileMix = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
- tileMix?.AdjustMoles(Gas.Miasma, molsToDump);
-
- // Waste of entities to let these through
- foreach (var part in args.GibbedParts)
- EntityManager.DeleteEntity(part);
- }
-
- private void OnExamined(EntityUid uid, PerishableComponent component, ExaminedEvent args)
- {
- if (!IsRotting(component))
- return;
-
- var stage = (_timing.CurTime - component.TimeOfDeath).TotalSeconds / component.RotAfter.TotalSeconds;
- var description = stage switch {
- >= 3 => "miasma-extremely-bloated",
- >= 2 => "miasma-bloated",
- _ => "miasma-rotting"};
- args.PushMarkup(Loc.GetString(description));
- }
-
- private void OnRejuvenate(EntityUid uid, RottingComponent component, RejuvenateEvent args)
- {
- EntityManager.RemoveComponentDeferred(uid);
- }
-
- /// Containers
-
- private void OnEntInserted(EntityUid uid, AntiRottingContainerComponent component, EntInsertedIntoContainerMessage args)
- {
- if (TryComp(args.Entity, out var perishable))
- {
- ModifyPreservationSource(args.Entity, true);
- ToggleDecomposition(args.Entity, false, perishable);
- }
- }
-
- private void OnEntRemoved(EntityUid uid, AntiRottingContainerComponent component, EntRemovedFromContainerMessage args)
- {
- // If we get de-parented due to entity shutdown don't add more flies.
- if (TryComp(args.Entity, out var perishable) &&
- TryComp(uid, out var metadata) && metadata.EntityLifeStage < EntityLifeStage.Terminating)
- {
- ModifyPreservationSource(args.Entity, false);
- ToggleDecomposition(args.Entity, true, perishable);
- }
- }
-
- /// Fly stuff
-
- private void OnFliesInit(EntityUid uid, FliesComponent component, ComponentInit args)
- {
- component.VirtFlies = EntityManager.SpawnEntity("AmbientSoundSourceFlies", Transform(uid).Coordinates);
- }
-
- private void OnFliesShutdown(EntityUid uid, FliesComponent component, ComponentShutdown args)
- {
- if (!Terminating(uid) && !Deleted(uid))
- Del(component.VirtFlies);
- }
-
- /// Public functions
-
- public void ToggleDecomposition(EntityUid uid, bool decompose, PerishableComponent? perishable = null)
- {
- if (Terminating(uid) || !Resolve(uid, ref perishable, false))
- return;
-
- if (decompose == perishable.Progressing) // Saved a few cycles
- return;
-
- perishable.Progressing = decompose;
-
- if (!IsRotting(perishable))
- return;
-
- if (decompose)
- {
- EnsureComp(uid);
- return;
- }
-
- RemComp(uid);
- }
-
- ///
- /// Add or remove a preservation source.
- /// Remove is just "add = false"
- /// If we have 0 we remove the whole component.
- ///
- public void ModifyPreservationSource(EntityUid uid, bool add)
- {
- var component = EnsureComp(uid);
-
- if (add)
- {
- component.PreservationSources++;
- return;
- }
-
- component.PreservationSources--;
-
- if (component.PreservationSources == 0)
- RemCompDeferred(uid, component);
- }
-
- public string RequestPoolDisease()
- {
- // We reset the current time on this outbreak so people don't get unlucky at the transition time
- _diseaseTime = _timing.CurTime + _poolRepickTime;
- return _poolDisease;
- }
- }
-}
diff --git a/Content.Server/Atmos/Miasma/PerishableComponent.cs b/Content.Server/Atmos/Miasma/PerishableComponent.cs
deleted file mode 100644
index 572125aa6c..0000000000
--- a/Content.Server/Atmos/Miasma/PerishableComponent.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Atmos.Miasma
-{
- ///
- /// This makes mobs eventually start rotting when they die.
- /// It may be expanded to food at some point, but it's just for mobs right now.
- ///
- [RegisterComponent]
- public sealed class PerishableComponent : Component
- {
- ///
- /// Is this progressing?
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- public bool Progressing = true;
-
- ///
- /// How long this creature has been dead.
- ///
- [DataField("timeOfDeath", customTypeSerializer: typeof(TimeOffsetSerializer))]
- [ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan TimeOfDeath = TimeSpan.Zero;
-
- ///
- /// When DeathAccumulator is greater than this, start rotting.
- ///
- public TimeSpan RotAfter = TimeSpan.FromMinutes(5);
-
- ///
- /// Gasses are released, this is when the next gas release update will be.
- ///
- [DataField("rotNextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
- public TimeSpan RotNextUpdate = TimeSpan.Zero;
-
- ///
- /// How many moles of gas released per second, per unit of mass.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("molsPerSecondPerUnitMass")]
- public float MolsPerSecondPerUnitMass = 0.0025f;
- }
-}
diff --git a/Content.Server/Atmos/Miasma/RottingComponent.cs b/Content.Server/Atmos/Miasma/RottingComponent.cs
deleted file mode 100644
index 17219efe61..0000000000
--- a/Content.Server/Atmos/Miasma/RottingComponent.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Content.Server.Atmos.Miasma
-{
- ///
- /// Tracking component for stuff that has started to rot.
- ///
- [RegisterComponent]
- public sealed class RottingComponent : Component
- {
- ///
- /// Whether or not the rotting should deal damage
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- public bool DealDamage = true;
- }
-}
diff --git a/Content.Server/Atmos/Miasma/RottingSystem.cs b/Content.Server/Atmos/Miasma/RottingSystem.cs
new file mode 100644
index 0000000000..a22054f48e
--- /dev/null
+++ b/Content.Server/Atmos/Miasma/RottingSystem.cs
@@ -0,0 +1,272 @@
+using Content.Shared.Damage;
+using Content.Shared.Atmos;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Body.Components;
+using Content.Server.Temperature.Components;
+using Content.Shared.Atmos.Miasma;
+using Content.Shared.Examine;
+using Content.Shared.Mobs;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Rejuvenate;
+using Robust.Server.Containers;
+using Robust.Server.GameObjects;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Atmos.Miasma;
+
+public sealed class RottingSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly AtmosphereSystem _atmosphere = default!;
+ [Dependency] private readonly ContainerSystem _container = default!;
+ [Dependency] private readonly DamageableSystem _damageable = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+ [Dependency] private readonly TransformSystem _transform = default!;
+
+ /// Miasma Disease Pool
+ /// Miasma outbreaks are not per-entity,
+ /// so this ensures that each entity in the same incident
+ /// receives the same disease.
+
+ public readonly IReadOnlyList MiasmaDiseasePool = new[]
+ {
+ "VentCough",
+ "AMIV",
+ "SpaceCold",
+ "SpaceFlu",
+ "BirdFlew",
+ "VanAusdallsRobovirus",
+ "BleedersBite",
+ "Plague",
+ "TongueTwister",
+ "MemeticAmirmir"
+ };
+
+ ///
+ /// The current pool disease.
+ ///
+ private string _poolDisease = "";
+
+ ///
+ /// The target time it waits until..
+ /// After that, it resets current time + _poolRepickTime.
+ /// Any infection will also reset it to current time + _poolRepickTime.
+ ///
+ private TimeSpan _diseaseTime = TimeSpan.FromMinutes(5);
+
+ ///
+ /// How long without an infection before we pick a new disease.
+ ///
+ private readonly TimeSpan _poolRepickTime = TimeSpan.FromMinutes(5);
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnPerishableUnpaused);
+ SubscribeLocalEvent(OnMobStateChanged);
+
+ SubscribeLocalEvent(OnRottingUnpaused);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnRottingMobStateChanged);
+ SubscribeLocalEvent(OnGibbed);
+ SubscribeLocalEvent(OnExamined);
+ SubscribeLocalEvent(OnRejuvenate);
+
+ SubscribeLocalEvent(OnFliesInit);
+ SubscribeLocalEvent(OnFliesShutdown);
+
+ SubscribeLocalEvent(OnTempIsRotting);
+
+ // Init disease pool
+ _poolDisease = _random.Pick(MiasmaDiseasePool);
+ }
+
+ private void OnPerishableUnpaused(EntityUid uid, PerishableComponent component, ref EntityUnpausedEvent args)
+ {
+ component.NextPerishUpdate += args.PausedTime;
+ }
+
+ private void OnMobStateChanged(EntityUid uid, PerishableComponent component, MobStateChangedEvent args)
+ {
+ if (!_mobState.IsDead(uid))
+ return;
+
+ component.RotAccumulator = TimeSpan.Zero;
+ component.NextPerishUpdate = _timing.CurTime + component.PerishUpdateRate;
+ }
+
+ private void OnRottingUnpaused(EntityUid uid, RottingComponent component, ref EntityUnpausedEvent args)
+ {
+ component.NextRotUpdate += args.PausedTime;
+ }
+
+ private void OnShutdown(EntityUid uid, RottingComponent component, ComponentShutdown args)
+ {
+ RemComp(uid);
+ if (TryComp(uid, out var perishable))
+ {
+ perishable.NextPerishUpdate = TimeSpan.Zero;
+ }
+ }
+
+ private void OnRottingMobStateChanged(EntityUid uid, RottingComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState == MobState.Dead)
+ return;
+ RemCompDeferred(uid, component);
+ }
+
+ public bool IsRotProgressing(EntityUid uid, PerishableComponent? perishable)
+ {
+ // things don't perish by default.
+ if (!Resolve(uid, ref perishable, false))
+ return false;
+
+ // only dead things perish
+ if (!_mobState.IsDead(uid))
+ return false;
+
+ if (_container.TryGetOuterContainer(uid, Transform(uid), out var container) &&
+ HasComp(container.Owner))
+ {
+ return false;
+ }
+
+ var ev = new IsRottingEvent();
+ RaiseLocalEvent(uid, ref ev);
+
+ return ev.Handled;
+ }
+
+ public bool IsRotten(EntityUid uid, RottingComponent? rotting = null)
+ {
+ return Resolve(uid, ref rotting, false);
+ }
+
+ private void OnGibbed(EntityUid uid, RottingComponent component, BeingGibbedEvent args)
+ {
+ if (!TryComp(uid, out var physics))
+ return;
+
+ if (!TryComp(uid, out var perishable))
+ return;
+
+ var molsToDump = perishable.MolsPerSecondPerUnitMass * physics.FixturesMass * (float) component.TotalRotTime.TotalSeconds;
+ var transform = Transform(uid);
+ var indices = _transform.GetGridOrMapTilePosition(uid, transform);
+ var tileMix = _atmosphere.GetTileMixture(transform.GridUid, transform.MapUid, indices, true);
+ tileMix?.AdjustMoles(Gas.Miasma, molsToDump);
+ }
+
+ private void OnExamined(EntityUid uid, RottingComponent component, ExaminedEvent args)
+ {
+ if (!TryComp(uid, out var perishable))
+ return;
+
+ var stage = (int) (component.TotalRotTime.TotalSeconds / perishable.RotAfter.TotalSeconds);
+ var description = stage switch
+ {
+ >= 2 => "miasma-extremely-bloated",
+ >= 1 => "miasma-bloated",
+ _ => "miasma-rotting"
+ };
+ args.PushMarkup(Loc.GetString(description));
+ }
+
+ private void OnRejuvenate(EntityUid uid, RottingComponent component, RejuvenateEvent args)
+ {
+ RemCompDeferred(uid);
+ }
+
+ /// Containers
+
+
+ #region Fly stuff
+ private void OnFliesInit(EntityUid uid, FliesComponent component, ComponentInit args)
+ {
+ component.VirtFlies = Spawn("AmbientSoundSourceFlies", Transform(uid).Coordinates);
+ }
+
+ private void OnFliesShutdown(EntityUid uid, FliesComponent component, ComponentShutdown args)
+ {
+ if (!Terminating(uid) && !Deleted(uid))
+ Del(component.VirtFlies);
+ }
+ #endregion
+
+ private void OnTempIsRotting(EntityUid uid, TemperatureComponent component, ref IsRottingEvent args)
+ {
+ if (args.Handled)
+ return;
+ args.Handled = component.CurrentTemperature > Atmospherics.T0C + 0.85f;
+ }
+
+ public string RequestPoolDisease()
+ {
+ // We reset the current time on this outbreak so people don't get unlucky at the transition time
+ _diseaseTime = _timing.CurTime + _poolRepickTime;
+ return _poolDisease;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ if (_timing.CurTime >= _diseaseTime)
+ {
+ _diseaseTime = _timing.CurTime + _poolRepickTime;
+ _poolDisease = _random.Pick(MiasmaDiseasePool);
+ }
+
+ var perishQuery = EntityQueryEnumerator();
+ while (perishQuery.MoveNext(out var uid, out var perishable))
+ {
+ if (_timing.CurTime < perishable.NextPerishUpdate)
+ continue;
+ perishable.NextPerishUpdate += perishable.PerishUpdateRate;
+
+ if (IsRotten(uid) || !IsRotProgressing(uid, perishable))
+ continue;
+
+ perishable.RotAccumulator += perishable.PerishUpdateRate;
+ if (perishable.RotAccumulator >= perishable.RotAfter)
+ {
+ var rot = AddComp(uid);
+ rot.NextRotUpdate = _timing.CurTime + rot.RotUpdateRate;
+ EnsureComp(uid);
+ }
+
+ }
+
+ var rotQuery = EntityQueryEnumerator();
+ while (rotQuery.MoveNext(out var uid, out var rotting, out var perishable, out var xform))
+ {
+ if (!IsRotProgressing(uid, perishable))
+ continue;
+
+ if (_timing.CurTime < rotting.NextRotUpdate) // This is where it starts to get noticable on larger animals, no need to run every second
+ continue;
+ rotting.NextRotUpdate += rotting.RotUpdateRate;
+ rotting.TotalRotTime += rotting.RotUpdateRate;
+
+ if (rotting.DealDamage)
+ {
+ var damage = rotting.Damage * rotting.RotUpdateRate.TotalSeconds;
+ _damageable.TryChangeDamage(uid, damage, true, false);
+ }
+
+ if (!TryComp(uid, out var physics))
+ continue;
+ // We need a way to get the mass of the mob alone without armor etc in the future
+ // or just remove the mass mechanics altogether because they aren't good.
+ var molRate = perishable.MolsPerSecondPerUnitMass * (float) rotting.RotUpdateRate.TotalSeconds;
+ var indices = _transform.GetGridOrMapTilePosition(uid);
+ var tileMix = _atmosphere.GetTileMixture(xform.GridUid, null, indices, true);
+ tileMix?.AdjustMoles(Gas.Miasma, molRate * physics.FixturesMass);
+ }
+ }
+}
diff --git a/Content.Server/Chemistry/ReagentEffects/ChemMiasmaPoolSource.cs b/Content.Server/Chemistry/ReagentEffects/ChemMiasmaPoolSource.cs
index dc8a8bfa39..e548e2321c 100644
--- a/Content.Server/Chemistry/ReagentEffects/ChemMiasmaPoolSource.cs
+++ b/Content.Server/Chemistry/ReagentEffects/ChemMiasmaPoolSource.cs
@@ -18,7 +18,7 @@ namespace Content.Server.Chemistry.ReagentEffects
if (args.Scale != 1f)
return;
- string disease = EntitySystem.Get().RequestPoolDisease();
+ string disease = EntitySystem.Get().RequestPoolDisease();
EntitySystem.Get().TryAddDisease(args.SolutionEntity, disease);
}
diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs
index 2beb0fe1d5..b21211c45e 100644
--- a/Content.Server/Medical/DefibrillatorSystem.cs
+++ b/Content.Server/Medical/DefibrillatorSystem.cs
@@ -35,7 +35,7 @@ public sealed class DefibrillatorSystem : EntitySystem
[Dependency] private readonly DoAfterSystem _doAfter = default!;
[Dependency] private readonly ElectrocutionSystem _electrocution = default!;
[Dependency] private readonly EuiManager _euiManager = default!;
- [Dependency] private readonly MiasmaSystem _miasma = default!;
+ [Dependency] private readonly RottingSystem _rotting = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly MobThresholdSystem _mobThreshold = default!;
[Dependency] private readonly PopupSystem _popup = default!;
@@ -156,7 +156,7 @@ public sealed class DefibrillatorSystem : EntitySystem
if (_timing.CurTime < component.NextZapTime)
return false;
- if (!TryComp(target, out var mobState) || _miasma.IsRotting(target))
+ if (!TryComp(target, out var mobState) || _rotting.IsRotten(target))
return false;
if (!_powerCell.HasActivatableCharge(uid, user: user))
diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs
index 6ea1a257ff..3d9be04755 100644
--- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs
+++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs
@@ -1,5 +1,4 @@
using Content.Server.Atmos.Components;
-using Content.Server.Atmos.Miasma;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Chat;
@@ -108,11 +107,6 @@ namespace Content.Server.Zombies
RemComp(target);
RemComp(target);
- //funny voice
- EnsureComp(target).Accent = "zombie";
- var rotting = EnsureComp(target);
- rotting.DealDamage = false;
-
//This is needed for stupid entities that fuck up combat mode component
//in an attempt to make an entity not attack. This is the easiest way to do it.
RemComp(target);
diff --git a/Content.Shared/Atmos/Miasma/AntiRottingContainerComponent.cs b/Content.Shared/Atmos/Miasma/AntiRottingContainerComponent.cs
new file mode 100644
index 0000000000..3a05dbc3b0
--- /dev/null
+++ b/Content.Shared/Atmos/Miasma/AntiRottingContainerComponent.cs
@@ -0,0 +1,11 @@
+namespace Content.Shared.Atmos.Miasma;
+
+///
+/// Entities inside this container will not rot.
+///
+[RegisterComponent]
+public sealed class AntiRottingContainerComponent : Component
+{
+
+}
+
diff --git a/Content.Shared/Atmos/Miasma/PerishableComponent.cs b/Content.Shared/Atmos/Miasma/PerishableComponent.cs
new file mode 100644
index 0000000000..17cf310681
--- /dev/null
+++ b/Content.Shared/Atmos/Miasma/PerishableComponent.cs
@@ -0,0 +1,46 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Atmos.Miasma;
+
+///
+/// This makes mobs eventually start rotting when they die.
+/// It may be expanded to food at some point, but it's just for mobs right now.
+///
+[RegisterComponent]
+public sealed class PerishableComponent : Component
+{
+ ///
+ /// How long it takes after death to start rotting.
+ ///
+ [DataField("rotAfter"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan RotAfter = TimeSpan.FromMinutes(5);
+
+ ///
+ /// How much rotting has occured
+ ///
+ [DataField("rotAccumulator"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan RotAccumulator = TimeSpan.Zero;
+
+ ///
+ /// Gasses are released, this is when the next gas release update will be.
+ ///
+ [DataField("rotNextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextPerishUpdate = TimeSpan.Zero;
+
+ ///
+ /// How often the rotting ticks.
+ /// Feel free to weak this if there are perf concerns.
+ ///
+ [DataField("perishUpdateRate"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan PerishUpdateRate = TimeSpan.FromSeconds(5);
+
+ ///
+ /// How many moles of gas released per second, per unit of mass.
+ ///
+ [DataField("molsPerSecondPerUnitMass"), ViewVariables(VVAccess.ReadWrite)]
+ public float MolsPerSecondPerUnitMass = 0.0025f;
+}
+
+
+[ByRefEvent]
+public record struct IsRottingEvent(bool Handled = false);
diff --git a/Content.Shared/Atmos/Miasma/RottingComponent.cs b/Content.Shared/Atmos/Miasma/RottingComponent.cs
new file mode 100644
index 0000000000..e37fe3996c
--- /dev/null
+++ b/Content.Shared/Atmos/Miasma/RottingComponent.cs
@@ -0,0 +1,49 @@
+using Content.Shared.Damage;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Atmos.Miasma;
+
+///
+/// Tracking component for stuff that has started to rot.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed class RottingComponent : Component
+{
+ ///
+ /// Whether or not the rotting should deal damage
+ ///
+ [DataField("dealDamage"), ViewVariables(VVAccess.ReadWrite)]
+ public bool DealDamage = true;
+
+ ///
+ /// When the next check will happen for rot progression + effects like damage and miasma
+ ///
+ [DataField("nextRotUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan NextRotUpdate = TimeSpan.Zero;
+
+ ///
+ /// How long in between each rot update.
+ ///
+ [DataField("rotUpdateRate"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan RotUpdateRate = TimeSpan.FromSeconds(5);
+
+ ///
+ /// How long has this thing been rotting?
+ ///
+ [DataField("totalRotTime"), ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan TotalRotTime = TimeSpan.Zero;
+
+ ///
+ /// The damage dealt by rotting.
+ ///
+ [DataField("damage")]
+ public DamageSpecifier Damage = new()
+ {
+ DamageDict = new()
+ {
+ { "Blunt", 0.06 },
+ { "Cellular", 0.06 }
+ }
+ };
+}