]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
[fix] Fix rotting (#16039)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Wed, 3 May 2023 04:57:47 +0000 (00:57 -0400)
committerGitHub <noreply@github.com>
Wed, 3 May 2023 04:57:47 +0000 (22:57 -0600)
* Fix rotting

* empty

12 files changed:
Content.Server/Atmos/Miasma/AntiRottingContainerComponent.cs [deleted file]
Content.Server/Atmos/Miasma/BodyPreservedComponent.cs [deleted file]
Content.Server/Atmos/Miasma/MiasmaSystem.cs [deleted file]
Content.Server/Atmos/Miasma/PerishableComponent.cs [deleted file]
Content.Server/Atmos/Miasma/RottingComponent.cs [deleted file]
Content.Server/Atmos/Miasma/RottingSystem.cs [new file with mode: 0644]
Content.Server/Chemistry/ReagentEffects/ChemMiasmaPoolSource.cs
Content.Server/Medical/DefibrillatorSystem.cs
Content.Server/Zombies/ZombifyOnDeathSystem.cs
Content.Shared/Atmos/Miasma/AntiRottingContainerComponent.cs [new file with mode: 0644]
Content.Shared/Atmos/Miasma/PerishableComponent.cs [new file with mode: 0644]
Content.Shared/Atmos/Miasma/RottingComponent.cs [new file with mode: 0644]

diff --git a/Content.Server/Atmos/Miasma/AntiRottingContainerComponent.cs b/Content.Server/Atmos/Miasma/AntiRottingContainerComponent.cs
deleted file mode 100644 (file)
index ed37e16..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Atmos.Miasma
-{
-    /// <summary>
-    /// Entities inside this container will not rot.
-    /// </summary>
-    [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 (file)
index adff39b..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Content.Server.Atmos.Miasma
-{
-    /// <summary>
-    /// Way for natural sources of rotting to tell if there are more unnatural preservation forces at play.
-    /// </summary>
-    [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 (file)
index 7274800..0000000
+++ /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
-
-        /// <summary>
-        /// How often the rotting ticks.
-        /// Feel free to weak this if there are perf concerns.
-        /// </summary>
-        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<string> MiasmaDiseasePool = new[]
-        {
-            "VentCough",
-            "AMIV",
-            "SpaceCold",
-            "SpaceFlu",
-            "BirdFlew",
-            "VanAusdallsRobovirus",
-            "BleedersBite",
-            "Plague",
-            "TongueTwister",
-            "MemeticAmirmir"
-        };
-
-        /// <summary>
-        /// The current pool disease.
-        /// </summary>
-        private string _poolDisease = "";
-
-        /// <summary>
-        /// The list of diseases in the pool.
-        /// </summary>
-
-        /// <summary>
-        /// The target time it waits until..
-        /// After that, it resets current time + _poolRepickTime.
-        /// Any infection will also reset it to current time + _poolRepickTime.
-        /// </summary>
-        private TimeSpan _diseaseTime = TimeSpan.FromMinutes(5);
-
-        /// <summary>
-        /// How long without an infection before we pick a new disease.
-        /// </summary>
-        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<RottingComponent, PerishableComponent, MetaDataComponent>())
-            {
-                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<FliesComponent>(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<PhysicsComponent>(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<RottingComponent, ComponentShutdown>(OnShutdown);
-            SubscribeLocalEvent<RottingComponent, OnTemperatureChangeEvent>(OnTempChange);
-            SubscribeLocalEvent<RottingComponent, MobStateChangedEvent>(OnRottingMobStateChanged);
-            SubscribeLocalEvent<PerishableComponent, MobStateChangedEvent>(OnMobStateChanged);
-            SubscribeLocalEvent<PerishableComponent, BeingGibbedEvent>(OnGibbed);
-            SubscribeLocalEvent<PerishableComponent, ExaminedEvent>(OnExamined);
-            SubscribeLocalEvent<RottingComponent, RejuvenateEvent>(OnRejuvenate);
-            // Containers
-            SubscribeLocalEvent<AntiRottingContainerComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
-            SubscribeLocalEvent<AntiRottingContainerComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
-            // Fly audiovisual stuff
-            SubscribeLocalEvent<FliesComponent, ComponentInit>(OnFliesInit);
-            SubscribeLocalEvent<FliesComponent, ComponentShutdown>(OnFliesShutdown);
-
-            // Init disease pool
-            _poolDisease = _random.Pick(MiasmaDiseasePool);
-        }
-
-        private void OnShutdown(EntityUid uid, RottingComponent component, ComponentShutdown args)
-        {
-            RemComp<FliesComponent>(uid);
-            if (TryComp<PerishableComponent>(uid, out var perishable))
-            {
-                perishable.TimeOfDeath = TimeSpan.Zero;
-                perishable.RotNextUpdate = TimeSpan.Zero;
-            }
-        }
-
-        private void OnTempChange(EntityUid uid, RottingComponent component, OnTemperatureChangeEvent args)
-        {
-            if (HasComp<BodyPreservedComponent>(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<RottingComponent>(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);
-        }
-
-        /// <summary>
-        ///     Has enough time passed for <paramref name="perishable"/> to start rotting?
-        /// </summary>
-        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<PhysicsComponent>(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<RottingComponent>(uid);
-        }
-
-        /// Containers
-
-        private void OnEntInserted(EntityUid uid, AntiRottingContainerComponent component, EntInsertedIntoContainerMessage args)
-        {
-            if (TryComp<PerishableComponent>(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<PerishableComponent>(args.Entity, out var perishable) &&
-                TryComp<MetaDataComponent>(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<FliesComponent>(uid);
-                return;
-            }
-
-            RemComp<FliesComponent>(uid);
-        }
-
-        /// <summary>
-        /// Add or remove a preservation source.
-        /// Remove is just "add = false"
-        /// If we have 0 we remove the whole component.
-        /// </summary>
-        public void ModifyPreservationSource(EntityUid uid, bool add)
-        {
-            var component = EnsureComp<BodyPreservedComponent>(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 (file)
index 572125a..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Atmos.Miasma
-{
-    /// <summary>
-    /// 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.
-    /// </summary>
-    [RegisterComponent]
-    public sealed class PerishableComponent : Component
-    {
-        /// <summary>
-        /// Is this progressing?
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        public bool Progressing = true;
-
-        /// <summary>
-        /// How long this creature has been dead.
-        /// </summary>
-        [DataField("timeOfDeath", customTypeSerializer: typeof(TimeOffsetSerializer))]
-        [ViewVariables(VVAccess.ReadWrite)]
-        public TimeSpan TimeOfDeath = TimeSpan.Zero;
-
-        /// <summary>
-        /// When DeathAccumulator is greater than this, start rotting.
-        /// </summary>
-        public TimeSpan RotAfter = TimeSpan.FromMinutes(5);
-
-        /// <summary>
-        /// Gasses are released, this is when the next gas release update will be.
-        /// </summary>
-        [DataField("rotNextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
-        public TimeSpan RotNextUpdate = TimeSpan.Zero;
-
-        /// <summary>
-        /// How many moles of gas released per second, per unit of mass.
-        /// </summary>
-        [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 (file)
index 17219ef..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Content.Server.Atmos.Miasma
-{
-    /// <summary>
-    /// Tracking component for stuff that has started to rot.
-    /// </summary>
-    [RegisterComponent]
-    public sealed class RottingComponent : Component
-    {
-        /// <summary>
-        /// Whether or not the rotting should deal damage
-        /// </summary>
-        [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 (file)
index 0000000..a22054f
--- /dev/null
@@ -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<string> MiasmaDiseasePool = new[]
+    {
+        "VentCough",
+        "AMIV",
+        "SpaceCold",
+        "SpaceFlu",
+        "BirdFlew",
+        "VanAusdallsRobovirus",
+        "BleedersBite",
+        "Plague",
+        "TongueTwister",
+        "MemeticAmirmir"
+    };
+
+    /// <summary>
+    /// The current pool disease.
+    /// </summary>
+    private string _poolDisease = "";
+
+    /// <summary>
+    /// The target time it waits until..
+    /// After that, it resets current time + _poolRepickTime.
+    /// Any infection will also reset it to current time + _poolRepickTime.
+    /// </summary>
+    private TimeSpan _diseaseTime = TimeSpan.FromMinutes(5);
+
+    /// <summary>
+    /// How long without an infection before we pick a new disease.
+    /// </summary>
+    private readonly TimeSpan _poolRepickTime = TimeSpan.FromMinutes(5);
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<PerishableComponent, EntityUnpausedEvent>(OnPerishableUnpaused);
+        SubscribeLocalEvent<PerishableComponent, MobStateChangedEvent>(OnMobStateChanged);
+
+        SubscribeLocalEvent<RottingComponent, EntityUnpausedEvent>(OnRottingUnpaused);
+        SubscribeLocalEvent<RottingComponent, ComponentShutdown>(OnShutdown);
+        SubscribeLocalEvent<RottingComponent, MobStateChangedEvent>(OnRottingMobStateChanged);
+        SubscribeLocalEvent<RottingComponent, BeingGibbedEvent>(OnGibbed);
+        SubscribeLocalEvent<RottingComponent, ExaminedEvent>(OnExamined);
+        SubscribeLocalEvent<RottingComponent, RejuvenateEvent>(OnRejuvenate);
+
+        SubscribeLocalEvent<FliesComponent, ComponentInit>(OnFliesInit);
+        SubscribeLocalEvent<FliesComponent, ComponentShutdown>(OnFliesShutdown);
+
+        SubscribeLocalEvent<TemperatureComponent, IsRottingEvent>(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<FliesComponent>(uid);
+        if (TryComp<PerishableComponent>(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<AntiRottingContainerComponent>(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<PhysicsComponent>(uid, out var physics))
+            return;
+
+        if (!TryComp<PerishableComponent>(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<PerishableComponent>(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<RottingComponent>(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<PerishableComponent>();
+        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<RottingComponent>(uid);
+                rot.NextRotUpdate = _timing.CurTime + rot.RotUpdateRate;
+                EnsureComp<FliesComponent>(uid);
+            }
+
+        }
+
+        var rotQuery = EntityQueryEnumerator<RottingComponent, PerishableComponent, TransformComponent>();
+        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<PhysicsComponent>(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);
+        }
+    }
+}
index dc8a8bfa39d1c37001bbee1fdb728243dd49cc99..e548e2321c3b74c255ea5edf4b8e6b842a971e13 100644 (file)
@@ -18,7 +18,7 @@ namespace Content.Server.Chemistry.ReagentEffects
             if (args.Scale != 1f)
                 return;
 
-            string disease = EntitySystem.Get<MiasmaSystem>().RequestPoolDisease();
+            string disease = EntitySystem.Get<RottingSystem>().RequestPoolDisease();
 
             EntitySystem.Get<DiseaseSystem>().TryAddDisease(args.SolutionEntity, disease);
         }
index 2beb0fe1d59977c45c49968306e95b71bf18fdda..b21211c45e5abe775a6f3b70b150dbc4b8428723 100644 (file)
@@ -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<MobStateComponent>(target, out var mobState) || _miasma.IsRotting(target))
+        if (!TryComp<MobStateComponent>(target, out var mobState) || _rotting.IsRotten(target))
             return false;
 
         if (!_powerCell.HasActivatableCharge(uid, user: user))
index 6ea1a257fff8de91bd7d5bfebdf5479d860d46f5..3d9be04755327dcf38d38483e2b8f17b8465ec6c 100644 (file)
@@ -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<HungerComponent>(target);
             RemComp<ThirstComponent>(target);
 
-            //funny voice
-            EnsureComp<ReplacementAccentComponent>(target).Accent = "zombie";
-            var rotting = EnsureComp<RottingComponent>(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<CombatModeComponent>(target);
diff --git a/Content.Shared/Atmos/Miasma/AntiRottingContainerComponent.cs b/Content.Shared/Atmos/Miasma/AntiRottingContainerComponent.cs
new file mode 100644 (file)
index 0000000..3a05dbc
--- /dev/null
@@ -0,0 +1,11 @@
+namespace Content.Shared.Atmos.Miasma;
+
+/// <summary>
+/// Entities inside this container will not rot.
+/// </summary>
+[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 (file)
index 0000000..17cf310
--- /dev/null
@@ -0,0 +1,46 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Atmos.Miasma;
+
+/// <summary>
+/// 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.
+/// </summary>
+[RegisterComponent]
+public sealed class PerishableComponent : Component
+{
+    /// <summary>
+    /// How long it takes after death to start rotting.
+    /// </summary>
+    [DataField("rotAfter"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan RotAfter = TimeSpan.FromMinutes(5);
+
+    /// <summary>
+    /// How much rotting has occured
+    /// </summary>
+    [DataField("rotAccumulator"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan RotAccumulator = TimeSpan.Zero;
+
+    /// <summary>
+    /// Gasses are released, this is when the next gas release update will be.
+    /// </summary>
+    [DataField("rotNextUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan NextPerishUpdate = TimeSpan.Zero;
+
+    /// <summary>
+    /// How often the rotting ticks.
+    /// Feel free to weak this if there are perf concerns.
+    /// </summary>
+    [DataField("perishUpdateRate"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan PerishUpdateRate = TimeSpan.FromSeconds(5);
+
+    /// <summary>
+    /// How many moles of gas released per second, per unit of mass.
+    /// </summary>
+    [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 (file)
index 0000000..e37fe39
--- /dev/null
@@ -0,0 +1,49 @@
+using Content.Shared.Damage;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Atmos.Miasma;
+
+/// <summary>
+/// Tracking component for stuff that has started to rot.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed class RottingComponent : Component
+{
+    /// <summary>
+    /// Whether or not the rotting should deal damage
+    /// </summary>
+    [DataField("dealDamage"), ViewVariables(VVAccess.ReadWrite)]
+    public bool DealDamage = true;
+
+    /// <summary>
+    /// When the next check will happen for rot progression + effects like damage and miasma
+    /// </summary>
+    [DataField("nextRotUpdate", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan NextRotUpdate = TimeSpan.Zero;
+
+    /// <summary>
+    /// How long in between each rot update.
+    /// </summary>
+    [DataField("rotUpdateRate"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan RotUpdateRate = TimeSpan.FromSeconds(5);
+
+    /// <summary>
+    /// How long has this thing been rotting?
+    /// </summary>
+    [DataField("totalRotTime"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan TotalRotTime = TimeSpan.Zero;
+
+    /// <summary>
+    /// The damage dealt by rotting.
+    /// </summary>
+    [DataField("damage")]
+    public DamageSpecifier Damage = new()
+    {
+        DamageDict = new()
+        {
+            { "Blunt", 0.06 },
+            { "Cellular", 0.06 }
+        }
+    };
+}