using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Rejuvenate; using Robust.Shared.Containers; using Robust.Shared.Timing; namespace Content.Shared.Atmos.Rotting; public abstract class SharedRottingSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly MobStateSystem _mobState = default!; public const int MaxStages = 3; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnPerishableMapInit); SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnPerishableExamined); SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnRottingMobStateChanged); SubscribeLocalEvent(OnRejuvenate); SubscribeLocalEvent(OnExamined); } private void OnPerishableMapInit(EntityUid uid, PerishableComponent component, MapInitEvent args) { component.RotNextUpdate = _timing.CurTime + component.PerishUpdateRate; } private void OnMobStateChanged(EntityUid uid, PerishableComponent component, MobStateChangedEvent args) { if (args.NewMobState != MobState.Dead && args.OldMobState != MobState.Dead) return; if (HasComp(uid)) return; component.RotAccumulator = TimeSpan.Zero; component.RotNextUpdate = _timing.CurTime + component.PerishUpdateRate; } private void OnPerishableExamined(Entity perishable, ref ExaminedEvent args) { int stage = PerishStage(perishable, MaxStages); if (stage < 1 || stage > MaxStages) { // We dont push an examined string if it hasen't started "perishing" or it's already rotting return; } var isMob = HasComp(perishable); var description = "perishable-" + stage + (!isMob ? "-nonmob" : string.Empty); args.PushMarkup(Loc.GetString(description, ("target", Identity.Entity(perishable, EntityManager)))); } private void OnShutdown(EntityUid uid, RottingComponent component, ComponentShutdown args) { if (TryComp(uid, out var perishable)) { perishable.RotNextUpdate = TimeSpan.Zero; } } private void OnRottingMobStateChanged(EntityUid uid, RottingComponent component, MobStateChangedEvent args) { if (args.NewMobState == MobState.Dead) return; RemCompDeferred(uid, component); } private void OnRejuvenate(EntityUid uid, RottingComponent component, RejuvenateEvent args) { RemCompDeferred(uid); } private void OnExamined(EntityUid uid, RottingComponent component, ExaminedEvent args) { var stage = RotStage(uid, component); var description = stage switch { >= 2 => "rotting-extremely-bloated", >= 1 => "rotting-bloated", _ => "rotting-rotting" }; if (!HasComp(uid)) description += "-nonmob"; args.PushMarkup(Loc.GetString(description, ("target", Identity.Entity(uid, EntityManager)))); } /// /// Return an integer from 0 to maxStage representing how close to rotting an entity is. Used to /// generate examine messages for items that are starting to rot. /// public int PerishStage(Entity perishable, int maxStages) { if (perishable.Comp.RotAfter.TotalSeconds == 0 || perishable.Comp.RotAccumulator.TotalSeconds == 0) return 0; return (int)(1 + maxStages * perishable.Comp.RotAccumulator.TotalSeconds / perishable.Comp.RotAfter.TotalSeconds); } public bool IsRotProgressing(EntityUid uid, PerishableComponent? perishable) { // things don't perish by default. if (!Resolve(uid, ref perishable, false)) return false; // Overrides all the other checks. if (perishable.ForceRotProgression) return true; // only dead things or inanimate objects can rot if (TryComp(uid, out var mobState) && !_mobState.IsDead(uid, mobState)) 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); } public void ReduceAccumulator(EntityUid uid, TimeSpan time) { if (!TryComp(uid, out var perishable)) return; if (!TryComp(uid, out var rotting)) { perishable.RotAccumulator -= time; return; } var total = (rotting.TotalRotTime + perishable.RotAccumulator) - time; if (total < perishable.RotAfter) { RemCompDeferred(uid, rotting); perishable.RotAccumulator = total; } else rotting.TotalRotTime = total - perishable.RotAfter; } /// /// Return the rot stage, usually from 0 to 2 inclusive. /// public int RotStage(EntityUid uid, RottingComponent? comp = null, PerishableComponent? perishable = null) { if (!Resolve(uid, ref comp, ref perishable)) return 0; return (int) (comp.TotalRotTime.TotalSeconds / perishable.RotAfter.TotalSeconds); } }