diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs index e2b07a5d77..c014c3650e 100644 --- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs +++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs @@ -218,6 +218,7 @@ namespace Content.Client.HealthAnalyzer.UI return mobState switch { MobState.Alive => Loc.GetString("health-analyzer-window-entity-alive-text"), + MobState.SoftCritical => Loc.GetString("health-analyzer-window-entity-critical-text"), MobState.Critical => Loc.GetString("health-analyzer-window-entity-critical-text"), MobState.Dead => Loc.GetString("health-analyzer-window-entity-dead-text"), _ => Loc.GetString("health-analyzer-window-entity-unknown-text"), diff --git a/Content.Client/Interaction/DragDropSystem.cs b/Content.Client/Interaction/DragDropSystem.cs index d249766bbc..f647005d11 100644 --- a/Content.Client/Interaction/DragDropSystem.cs +++ b/Content.Client/Interaction/DragDropSystem.cs @@ -495,7 +495,7 @@ public sealed class DragDropSystem : SharedDragDropSystem // CanInteract() doesn't support checking a second "target" entity. // Doing so manually: var ev = new GettingInteractedWithAttemptEvent(user, dragged); - RaiseLocalEvent(dragged, ref ev); + RaiseLocalEvent(dragged, ev); if (ev.Cancelled) return false; diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs index 193635bda7..b04ee5bf6f 100644 --- a/Content.Client/Overlays/EntityHealthBarOverlay.cs +++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs @@ -132,7 +132,7 @@ public sealed class EntityHealthBarOverlay : Overlay if (dmg.HealthBarThreshold != null && dmg.TotalDamage < dmg.HealthBarThreshold) return null; - if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholds) && + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.SoftCritical, MobState.Critical, out var threshold, thresholds) && !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out threshold, thresholds)) return (1, false); @@ -142,7 +142,7 @@ public sealed class EntityHealthBarOverlay : Overlay if (_mobStateSystem.IsCritical(uid, component)) { - if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, thresholds) || + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.SoftCritical, MobState.Critical, out var critThreshold, thresholds) || !_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Dead, out var deadThreshold, thresholds)) { return (1, true); diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs index 99d85350b5..5c454bcdbb 100644 --- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs +++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs @@ -29,7 +29,7 @@ public sealed partial class ReplaySpectatorSystem private void OnInteractAttempt(Entity ent, ref InteractionAttemptEvent args) { - args.Cancelled = true; + args.Cancel(); } private void OnAttempt(EntityUid uid, ReplaySpectatorComponent component, CancellableEntityEventArgs args) diff --git a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs index c10c33a990..b9563d4233 100644 --- a/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs +++ b/Content.Client/UserInterface/Systems/DamageOverlays/DamageOverlayUiController.cs @@ -35,7 +35,7 @@ public sealed class DamageOverlayUiController : UIController ClearOverlay(); if (!EntityManager.TryGetComponent(args.Entity, out var mobState)) return; - if (mobState.CurrentState != MobState.Dead) + if (mobState.CurrentState.IsCritOrAlive()) UpdateOverlays(args.Entity, mobState); _overlayManager.AddOverlay(_overlay); } @@ -89,6 +89,7 @@ public sealed class DamageOverlayUiController : UIController var critThreshold = foundThreshold.Value; _overlay.State = mobState.CurrentState; + _overlay.OxygenLevelOverride = mobState.GetOxyDamageOverlay(); switch (mobState.CurrentState) { @@ -113,6 +114,7 @@ public sealed class DamageOverlayUiController : UIController _overlay.DeadLevel = 0; break; } + case MobState.SoftCritical: case MobState.Critical: { if (!_mobThresholdSystem.TryGetDeadPercentage(entity, @@ -124,7 +126,7 @@ public sealed class DamageOverlayUiController : UIController _overlay.DeadLevel = 0; break; } - case MobState.Dead: + case MobState.Dead: // todo: move (some of?) the stuff above and below to mobstate parameters { _overlay.BruteLevel = 0; _overlay.CritLevel = 0; diff --git a/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs b/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs index fd74815316..cf817f3125 100644 --- a/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs +++ b/Content.Client/UserInterface/Systems/DamageOverlays/Overlays/DamageOverlay.cs @@ -1,4 +1,5 @@ using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; using Robust.Client.Graphics; using Robust.Client.Player; using Robust.Shared.Enums; @@ -21,7 +22,6 @@ public sealed class DamageOverlay : Overlay private readonly ShaderInstance _bruteShader; public MobState State = MobState.Alive; - /// /// Handles the red pulsing overlay /// @@ -33,6 +33,7 @@ public sealed class DamageOverlay : Overlay /// Handles the darkening overlay. /// public float OxygenLevel = 0f; + public float? OxygenLevelOverride; // for darkening in crit private float _oldOxygenLevel = 0f; @@ -168,7 +169,7 @@ public sealed class DamageOverlay : Overlay _oldBruteLevel = BruteLevel; } - level = State != MobState.Critical ? _oldOxygenLevel : 1f; + level = OxygenLevelOverride ?? _oldOxygenLevel; if (level > 0f) { diff --git a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs index cfc8007306..3ed1f7ae18 100644 --- a/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs +++ b/Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs @@ -1,4 +1,4 @@ -using Content.Server.Administration.Commands; +using Content.Server.Administration.Commands; using Content.Server.Administration.Systems; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -72,6 +72,10 @@ namespace Content.IntegrationTests.Tests.Commands Assert.That(mobStateSystem.IsAlive(human, mobState), Is.False); Assert.That(mobStateSystem.IsCritical(human, mobState), Is.False); Assert.That(mobStateSystem.IsDead(human, mobState), Is.True); + // This now may not be true for all mobs since the introduction of MobState parameters. + // Even though being dead should be incapacitating makes perfect sense. + // If you're fucking around with parameters for MobState.Dead and this + // throws at you, feel free to comment it. Or reconsider what you're doing. Assert.That(mobStateSystem.IsIncapacitated(human, mobState), Is.True); }); @@ -84,6 +88,7 @@ namespace Content.IntegrationTests.Tests.Commands Assert.That(mobStateSystem.IsAlive(human, mobState), Is.True); Assert.That(mobStateSystem.IsCritical(human, mobState), Is.False); Assert.That(mobStateSystem.IsDead(human, mobState), Is.False); + // see above, same thing but for MobState.Alive Assert.That(mobStateSystem.IsIncapacitated(human, mobState), Is.False); Assert.That(damageable.TotalDamage, Is.EqualTo(FixedPoint2.Zero)); diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 2aaab767c6..b3733965cf 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -203,7 +203,7 @@ public sealed partial class AdminVerbSystem Act = () => { int damageToDeal; - if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Critical, out var criticalThreshold)) { + if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.SoftCritical, MobState.Critical, out var criticalThreshold)) { // We can't crit them so try killing them. if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Dead, out var deadThreshold)) diff --git a/Content.Server/Administration/Systems/SuperBonkSystem.cs b/Content.Server/Administration/Systems/SuperBonkSystem.cs index 5cd62d8357..a243bce136 100644 --- a/Content.Server/Administration/Systems/SuperBonkSystem.cs +++ b/Content.Server/Administration/Systems/SuperBonkSystem.cs @@ -27,7 +27,7 @@ public sealed class SuperBonkSystem : EntitySystem //The other check in the code to stop when the target dies does not work if the target is already dead. if (stopWhenDead && TryComp(target, out var mState)) { - if (mState.CurrentState == MobState.Dead) + if (mState.CurrentState.IsDead()) return; } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 5ad0fb35ac..b1b13dfb86 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -21,6 +21,7 @@ using Content.Shared.Mood; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Timing; +using Content.Shared.Mobs.Components; namespace Content.Server.Body.Systems; @@ -83,12 +84,13 @@ public sealed class RespiratorSystem : EntitySystem UpdateSaturation(uid, -(float) respirator.UpdateInterval.TotalSeconds, respirator); - if (!_mobState.IsIncapacitated(uid) && !HasComp(uid)) // Shitmed: cannot breathe in crit or when no brain. + float mul = _mobState.BreatheMultiplier(uid); + if (mul > 0f && !HasComp(uid)) // Shitmed: cannot breathe in crit or when no brain. { switch (respirator.Status) { case RespiratorStatus.Inhaling: - Inhale(uid, body); + Inhale(uid, body, mul); respirator.Status = RespiratorStatus.Exhaling; break; case RespiratorStatus.Exhaling: @@ -106,7 +108,7 @@ public sealed class RespiratorSystem : EntitySystem _popupSystem.PopupEntity(Loc.GetString("lung-behavior-gasp"), uid); } - TakeSuffocationDamage((uid, respirator)); + TakeSuffocationDamage((uid, respirator), 1 - mul); respirator.SuffocationCycles += 1; continue; } @@ -116,7 +118,7 @@ public sealed class RespiratorSystem : EntitySystem } } - public void Inhale(EntityUid uid, BodyComponent? body = null) + public void Inhale(EntityUid uid, BodyComponent? body = null, float breathVolumeMultiplier = 1f) { if (!Resolve(uid, ref body, logMissing: false)) return; @@ -134,7 +136,7 @@ public sealed class RespiratorSystem : EntitySystem return; } - var actualGas = ev.Gas.RemoveVolume(Atmospherics.BreathVolume); + var actualGas = ev.Gas.RemoveVolume(Atmospherics.BreathVolume * breathVolumeMultiplier); var lungRatio = 1.0f / organs.Count; var gas = organs.Count == 1 ? actualGas : actualGas.RemoveRatio(lungRatio); @@ -286,7 +288,7 @@ public sealed class RespiratorSystem : EntitySystem return saturation; } - private void TakeSuffocationDamage(Entity ent) + private void TakeSuffocationDamage(Entity ent, float damageMultiplier = 1f) { if (ent.Comp.SuffocationCycles == 2) _adminLogger.Add(LogType.Asphyxiation, $"{ToPrettyString(ent):entity} started suffocating"); @@ -301,8 +303,11 @@ public sealed class RespiratorSystem : EntitySystem } RaiseLocalEvent(ent, new MoodEffectEvent("Suffocating")); } - - _damageableSys.TryChangeDamage(ent, HasComp(ent) ? ent.Comp.Damage * 4.5f : ent.Comp.Damage, interruptsDoAfters: false); + var damageToTake = ent.Comp.Damage; + if (HasComp(ent)) + damageToTake *= 4.5f; + damageToTake *= damageMultiplier; + _damageableSys.TryChangeDamage(ent, damageToTake, interruptsDoAfters: false); } private void StopSuffocation(Entity ent) diff --git a/Content.Server/Carrying/CarryingSystem.cs b/Content.Server/Carrying/CarryingSystem.cs index 224e38bcbd..72cfeff91d 100644 --- a/Content.Server/Carrying/CarryingSystem.cs +++ b/Content.Server/Carrying/CarryingSystem.cs @@ -168,7 +168,7 @@ namespace Content.Server.Carrying var targetParent = Transform(args.Target.Value).ParentUid; if (args.Target.Value != component.Carrier && targetParent != component.Carrier && targetParent != uid) - args.Cancelled = true; + args.Cancel(); } /// @@ -202,7 +202,7 @@ namespace Content.Server.Carrying private void OnInteractedWith(EntityUid uid, BeingCarriedComponent component, GettingInteractedWithAttemptEvent args) { if (args.Uid != component.Carrier) - args.Cancelled = true; + args.Cancel(); } private void OnPullAttempt(EntityUid uid, BeingCarriedComponent component, PullAttemptEvent args) diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index af4ecfcbe1..36719f1a6a 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -314,7 +314,7 @@ public sealed partial class ChatSystem : SharedChatSystem sendType = InGameOOCChatType.Dead; // If crit player LOOC is disabled, don't send the message at all. - if (!_critLoocEnabled && _mobStateSystem.IsCritical(source)) + if (!_critLoocEnabled && _mobStateSystem.IsHardCritical(source)) return; switch (sendType) @@ -618,7 +618,7 @@ public sealed partial class ChatSystem : SharedChatSystem else if (!_loocEnabled) return; // If crit player LOOC is disabled, don't send the message at all. - if (!_critLoocEnabled && _mobStateSystem.IsCritical(source)) + if (!_critLoocEnabled && _mobStateSystem.IsHardCritical(source)) return; var wrappedMessage = Loc.GetString("chat-manager-entity-looc-wrap-message", diff --git a/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs b/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs index 383096c03d..75488e2f73 100644 --- a/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs +++ b/Content.Server/Chat/TelepathicChatSystem.Psychognomy.cs @@ -17,6 +17,7 @@ using Robust.Shared.Enums; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Random; +using Content.Shared.Mobs; namespace Content.Server.Chat; public sealed partial class TelepathicChatSystem @@ -76,7 +77,7 @@ public sealed partial class TelepathicChatSystem private void DescribeMobState(EntityUid uid, MobStateComponent component, GetPsychognomicDescriptorEvent ev) { - if (component.CurrentState != Shared.Mobs.MobState.Critical) + if (!component.CurrentState.IsCrit()) return; ev.Descriptors.Add(Loc.GetString("p-descriptor-liminal")); diff --git a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs index 39ddca3412..11c7e2a700 100644 --- a/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs +++ b/Content.Server/Chemistry/EntitySystems/InjectorSystem.cs @@ -18,7 +18,6 @@ using Content.Shared.Mobs.Components; using Content.Shared.Stacks; using Robust.Server.Player; - namespace Content.Server.Chemistry.EntitySystems; public sealed class InjectorSystem : SharedInjectorSystem @@ -190,7 +189,7 @@ public sealed class InjectorSystem : SharedInjectorSystem // Check if the target is incapacitated or in combat mode and modify time accordingly. - if (MobState.IsIncapacitated(target)) + if (MobState.IsIncapacitated(target) /*|| _statusEffects.TryGetTime(target, "Stun", out var _)*/) { actualDelay /= 2.5f; } diff --git a/Content.Server/Cloning/CloningSystem.Utility.cs b/Content.Server/Cloning/CloningSystem.Utility.cs index 336ed50a6a..459f88bc52 100644 --- a/Content.Server/Cloning/CloningSystem.Utility.cs +++ b/Content.Server/Cloning/CloningSystem.Utility.cs @@ -354,7 +354,7 @@ public sealed partial class CloningSystem { if (!clonePodComp.DoGeneticDamage || !HasComp(mob) - || !_thresholds.TryGetThresholdForState(mob, Shared.Mobs.MobState.Critical, out var threshold)) + || !_thresholds.TryGetThresholdForState(mob, Shared.Mobs.MobState.Critical, Shared.Mobs.MobState.SoftCritical, out var threshold)) return; DamageSpecifier damage = new(); damage.DamageDict.Add("Cellular", (int) threshold + 1 + geneticDamage); diff --git a/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs b/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs index bc61c5d141..815c0ad5d1 100644 --- a/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs +++ b/Content.Server/Damage/ForceSay/DamageForceSaySystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Damage; using Content.Shared.Damage.ForceSay; using Content.Shared.FixedPoint; using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Stunnable; using Robust.Shared.Player; @@ -114,12 +115,14 @@ public sealed class DamageForceSaySystem : EntitySystem private void OnMobStateChanged(EntityUid uid, DamageForceSayComponent component, MobStateChangedEvent args) { - if (args is not { OldMobState: MobState.Alive, NewMobState: MobState.Critical or MobState.Dead }) - return; - - // no suffix for the drama - // LING IN MAI- - TryForceSay(uid, component, false); - AllowNextSpeech(uid); + var mobState = Comp(uid); + if (mobState.CanTalk(args.OldMobState) && // could talk before and + !mobState.CanTalk()) // can't talk now + { + // no suffix for the drama + // LING IN MAI- + TryForceSay(uid, component, false); + AllowNextSpeech(uid); + } } } diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs index a18f3ef725..1136583a1d 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs @@ -197,9 +197,9 @@ namespace Content.Server.Explosion.EntitySystems return; // Sends a message to the radio channel specified by the implant - if (mobstate.CurrentState == MobState.Critical) + if (mobstate.CurrentState.IsCrit()) _radioSystem.SendRadioMessage(uid, critMessage, _prototypeManager.Index(component.RadioChannel), uid); - if (mobstate.CurrentState == MobState.Dead) + if (mobstate.CurrentState.IsDead()) _radioSystem.SendRadioMessage(uid, deathMessage, _prototypeManager.Index(component.RadioChannel), uid); args.Handled = true; diff --git a/Content.Server/Flight/FlightSystem.cs b/Content.Server/Flight/FlightSystem.cs index 5e3ef354f2..0e767368df 100644 --- a/Content.Server/Flight/FlightSystem.cs +++ b/Content.Server/Flight/FlightSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.DoAfter; using Content.Shared.Flight; using Content.Shared.Flight.Events; using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.Standing; using Content.Shared.Stunnable; @@ -19,6 +20,7 @@ public sealed class FlightSystem : SharedFlightSystem [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly StandingStateSystem _standing = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { @@ -119,7 +121,7 @@ public sealed class FlightSystem : SharedFlightSystem private void OnMobStateChangedEvent(EntityUid uid, FlightComponent component, MobStateChangedEvent args) { if (!component.On - || args.NewMobState is MobState.Critical or MobState.Dead) + || _mobState.IsIncapacitated(uid)) return; ToggleActive(args.Target, false, component); diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 4d02e2311d..af1daf5636 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -253,7 +253,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem private void OnMobStateChanged(EntityUid uid, NukeOperativeComponent component, MobStateChangedEvent ev) { - if (ev.NewMobState == MobState.Dead) + if (ev.IsDead()) CheckRoundShouldEnd(); } diff --git a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs index 920668472a..0665b8c3ac 100644 --- a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs @@ -50,7 +50,7 @@ public sealed class RespawnRuleSystem : GameRuleSystem private void OnMobStateChanged(MobStateChangedEvent args) { - if (args.NewMobState == MobState.Alive) + if (args.IsAlive()) return; if (!TryComp(args.Target, out var actor)) diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index c5f88ab6cf..7cacca6b22 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -256,7 +256,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(entity, out var state)) { - if (state.CurrentState == MobState.Dead || state.CurrentState == MobState.Invalid) + if (state.CurrentState.IsDead() || !state.CurrentState.IsValid()) { gone++; } diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 346cb64ea8..f64c62bbda 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -97,6 +97,7 @@ namespace Content.Server.Ghost.Roles RegisterGhostRole((component, ghostRole)); break; } + //case MobState.SoftCritical: // allow ghosts to enter a softcritted vessel and beg for mercy case MobState.Critical: case MobState.Dead: UnregisterGhostRole((component, ghostRole)); diff --git a/Content.Server/Guardian/GuardianSystem.cs b/Content.Server/Guardian/GuardianSystem.cs index 203882ed9e..eb4b8be51d 100644 --- a/Content.Server/Guardian/GuardianSystem.cs +++ b/Content.Server/Guardian/GuardianSystem.cs @@ -238,12 +238,12 @@ namespace Content.Server.Guardian if (component.HostedGuardian == null) return; - if (args.NewMobState == MobState.Critical) + if (args.EnteredCrit()) { _popupSystem.PopupEntity(Loc.GetString("guardian-host-critical-warn"), component.HostedGuardian.Value, component.HostedGuardian.Value); _audio.PlayPvs("/Audio/Effects/guardian_warn.ogg", component.HostedGuardian.Value); } - else if (args.NewMobState == MobState.Dead) + else if (args.IsDead()) { //TODO: Replace WithVariation with datafield _audio.PlayPvs("/Audio/Voice/Human/malescream_guardian.ogg", uid, AudioParams.Default.WithVariation(0.20f)); diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs index 73cce89990..b8f49bfc68 100644 --- a/Content.Server/Medical/HealingSystem.cs +++ b/Content.Server/Medical/HealingSystem.cs @@ -244,7 +244,7 @@ public sealed class HealingSystem : EntitySystem if (!TryComp(uid, out var mobThreshold) || !TryComp(uid, out var damageable)) return output; - if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var amount, mobThreshold)) + if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.SoftCritical, out var amount, mobThreshold)) return 1; var percentDamage = (float) (damageable.TotalDamage / amount); diff --git a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs index aee62a58e5..ae3aa035a5 100644 --- a/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs +++ b/Content.Server/Medical/SuitSensors/SuitSensorSystem.cs @@ -346,7 +346,7 @@ public sealed class SuitSensorSystem : EntitySystem // Get mob total damage crit threshold int? totalDamageThreshold = null; - if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, Shared.Mobs.MobState.Critical, out var critThreshold)) + if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, Shared.Mobs.MobState.SoftCritical, out var critThreshold)) totalDamageThreshold = critThreshold.Value.Int(); // finally, form suit sensor status diff --git a/Content.Server/Mobs/DeathgaspComponent.cs b/Content.Server/Mobs/DeathgaspComponent.cs index 7afcaa1965..c25cbff180 100644 --- a/Content.Server/Mobs/DeathgaspComponent.cs +++ b/Content.Server/Mobs/DeathgaspComponent.cs @@ -21,4 +21,10 @@ public sealed partial class DeathgaspComponent : Component /// [DataField] public bool NeedsCritical = true; + + /// + /// Allows deathgasp to happen in softcrit too. Does nothing if is false. + /// + [DataField] + public bool AllowSoftCritical = true; } diff --git a/Content.Server/Mobs/DeathgaspSystem.cs b/Content.Server/Mobs/DeathgaspSystem.cs index 32be7bfe32..1dcbd41f60 100644 --- a/Content.Server/Mobs/DeathgaspSystem.cs +++ b/Content.Server/Mobs/DeathgaspSystem.cs @@ -21,8 +21,9 @@ public sealed class DeathgaspSystem: EntitySystem private void OnMobStateChanged(EntityUid uid, DeathgaspComponent component, MobStateChangedEvent args) { // don't deathgasp if they arent going straight from crit to dead - if (component.NeedsCritical && args.OldMobState != MobState.Critical - || args.NewMobState != MobState.Dead) + if (component.NeedsCritical && (args.WasCrit(component.AllowSoftCritical)) + || + !args.IsDead() && args.IsValid()) return; Deathgasp(uid, component); diff --git a/Content.Server/Mood/MoodComponent.cs b/Content.Server/Mood/MoodComponent.cs index caa221fe18..a7c6ff8c32 100644 --- a/Content.Server/Mood/MoodComponent.cs +++ b/Content.Server/Mood/MoodComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Alert; +using Content.Shared.Alert; using Content.Shared.FixedPoint; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; @@ -48,8 +48,18 @@ public sealed partial class MoodComponent : Component [DataField] public float DecreaseCritThreshold = 0.9f; + // WD EDIT START + [DataField] + public float IncreaseSoftCritThreshold = 1.1f; + + [DataField] + public float DecreaseSoftCritThreshold = 0.95f; + // WD EDIT END + [ViewVariables(VVAccess.ReadOnly)] public FixedPoint2 CritThresholdBeforeModify; + [ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 SoftCritThresholdBeforeModify; [DataField] public ProtoId MoodCategory = "Mood"; diff --git a/Content.Server/Mood/MoodSystem.cs b/Content.Server/Mood/MoodSystem.cs index 44a93217cf..ac6825b10e 100644 --- a/Content.Server/Mood/MoodSystem.cs +++ b/Content.Server/Mood/MoodSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Chat.Managers; +using Content.Server.Chat.Managers; using Content.Server.Popups; using Content.Shared.Alert; using Content.Shared.Chat; @@ -202,12 +202,12 @@ public sealed class MoodSystem : EntitySystem if (!_config.GetCVar(CCVars.MoodEnabled)) return; - if (args.NewMobState == MobState.Dead && args.OldMobState != MobState.Dead) + if (args.IsDead() && !args.WasDead()) { var ev = new MoodEffectEvent("Dead"); RaiseLocalEvent(uid, ev); } - else if (args.OldMobState == MobState.Dead && args.NewMobState != MobState.Dead) + else if (args.WasDead() && !args.IsDead()) { var ev = new MoodRemoveEffectEvent("Dead"); RaiseLocalEvent(uid, ev); @@ -242,10 +242,13 @@ public sealed class MoodSystem : EntitySystem return; if (_config.GetCVar(CCVars.MoodModifiesThresholds) - && TryComp(uid, out var mobThresholdsComponent) - && _mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, mobThresholdsComponent)) - component.CritThresholdBeforeModify = critThreshold.Value; - + && TryComp(uid, out var mobThresholdsComponent)) + { + if(_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, mobThresholdsComponent)) + component.CritThresholdBeforeModify = critThreshold.Value; + if(_mobThreshold.TryGetThresholdForState(uid, MobState.SoftCritical, out var softCritThreshold, mobThresholdsComponent)) + component.SoftCritThresholdBeforeModify = softCritThreshold.Value; + } EnsureComp(uid); RefreshMood(uid, component); } @@ -335,19 +338,34 @@ public sealed class MoodSystem : EntitySystem private void SetCritThreshold(EntityUid uid, MoodComponent component, int modifier) { if (!_config.GetCVar(CCVars.MoodModifiesThresholds) - || !TryComp(uid, out var mobThresholds) - || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var key)) + || !TryComp(uid, out var mobThresholds)) return; - var newKey = modifier switch + if (_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var key)) { - 1 => FixedPoint2.New(key.Value.Float() * component.IncreaseCritThreshold), - -1 => FixedPoint2.New(key.Value.Float() * component.DecreaseCritThreshold), - _ => component.CritThresholdBeforeModify, - }; + var newKey = modifier switch + { + 1 => FixedPoint2.New(key.Value.Float() * component.IncreaseCritThreshold), + -1 => FixedPoint2.New(key.Value.Float() * component.DecreaseCritThreshold), + _ => component.CritThresholdBeforeModify, + }; - component.CritThresholdBeforeModify = key.Value; - _mobThreshold.SetMobStateThreshold(uid, newKey, MobState.Critical, mobThresholds); + component.CritThresholdBeforeModify = key.Value; + _mobThreshold.SetMobStateThreshold(uid, newKey, MobState.Critical, mobThresholds); + } + + if (_mobThreshold.TryGetThresholdForState(uid, MobState.SoftCritical, out key)) + { + var newKey = modifier switch + { + 1 => FixedPoint2.New(key.Value.Float() * component.IncreaseSoftCritThreshold), + -1 => FixedPoint2.New(key.Value.Float() * component.DecreaseSoftCritThreshold), + _ => component.SoftCritThresholdBeforeModify, + }; + + component.SoftCritThresholdBeforeModify = key.Value; + _mobThreshold.SetMobStateThreshold(uid, newKey, MobState.SoftCritical, mobThresholds); + } } private MoodThreshold GetMoodThreshold(MoodComponent component, float? moodLevel = null) @@ -376,7 +394,7 @@ public sealed class MoodSystem : EntitySystem private void OnDamageChange(EntityUid uid, MoodComponent component, DamageChangedEvent args) { - if (!_mobThreshold.TryGetPercentageForState(uid, MobState.Critical, args.Damageable.TotalDamage, out var damage)) + if (!_mobThreshold.TryGetPercentageForState(uid, MobState.SoftCritical, args.Damageable.TotalDamage, out var damage)) return; var protoId = "HealthNoDamage"; diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs index 32be027ec4..a6ad5f3dae 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Combat/Melee/MeleeOperator.cs @@ -95,7 +95,7 @@ public sealed partial class MeleeOperator : HTNOperator, IHtnConditionalShutdown // Success if (_entManager.TryGetComponent(target, out var mobState) && - mobState.CurrentState > TargetState) + !mobState.IsThreatening()) { status = HTNOperatorStatus.Finished; } diff --git a/Content.Server/NPC/Queries/Considerations/TargetIsThreateningCon.cs b/Content.Server/NPC/Queries/Considerations/TargetIsThreateningCon.cs new file mode 100644 index 0000000000..2f1423a84d --- /dev/null +++ b/Content.Server/NPC/Queries/Considerations/TargetIsThreateningCon.cs @@ -0,0 +1,9 @@ +namespace Content.Server.NPC.Queries.Considerations; + +/// +/// Returns 1f if the target is threatening or 0f if not. +/// +public sealed partial class TargetIsThreateningCon : UtilityConsideration +{ + +} diff --git a/Content.Server/NPC/Systems/NPCSystem.cs b/Content.Server/NPC/Systems/NPCSystem.cs index 24c038a3e7..ff82ad84dd 100644 --- a/Content.Server/NPC/Systems/NPCSystem.cs +++ b/Content.Server/NPC/Systems/NPCSystem.cs @@ -47,7 +47,7 @@ namespace Content.Server.NPC.Systems public void OnPlayerNPCDetach(EntityUid uid, HTNComponent component, PlayerDetachedEvent args) { - if (_mobState.IsIncapacitated(uid) || TerminatingOrDeleted(uid)) + if (TerminatingOrDeleted(uid)) return; // This NPC has an attached mind, so it should not wake up. @@ -150,6 +150,7 @@ namespace Content.Server.NPC.Systems case MobState.Alive: WakeNPC(uid, component); break; + case MobState.SoftCritical: case MobState.Critical: case MobState.Dead: SleepNPC(uid, component); diff --git a/Content.Server/NPC/Systems/NPCUtilitySystem.cs b/Content.Server/NPC/Systems/NPCUtilitySystem.cs index eb111baea8..87956b137c 100644 --- a/Content.Server/NPC/Systems/NPCUtilitySystem.cs +++ b/Content.Server/NPC/Systems/NPCUtilitySystem.cs @@ -324,6 +324,10 @@ public sealed class NPCUtilitySystem : EntitySystem { return _mobState.IsAlive(targetUid) ? 1f : 0f; } + case TargetIsThreateningCon: + { + return _mobState.IsThreatening(targetUid) ? 1f : 0f; + } case TargetIsCritCon: { return _mobState.IsCritical(targetUid) ? 1f : 0f; diff --git a/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs b/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs index 68d8258c6b..4ef30b162f 100644 --- a/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs +++ b/Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs @@ -150,6 +150,9 @@ public sealed class AnimalHusbandrySystem : EntitySystem if (_mobState.IsIncapacitated(uid)) return false; + if (!_mobState.IsConscious(uid)) + return false; + if (TryComp(uid, out var hunger) && _hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay) return false; diff --git a/Content.Server/Objectives/Systems/HijackShuttleConditionSystem.cs b/Content.Server/Objectives/Systems/HijackShuttleConditionSystem.cs index 9e0d2c3d5b..72f98db35e 100644 --- a/Content.Server/Objectives/Systems/HijackShuttleConditionSystem.cs +++ b/Content.Server/Objectives/Systems/HijackShuttleConditionSystem.cs @@ -1,4 +1,4 @@ -using Content.Server.Objectives.Components; +using Content.Server.Objectives.Components; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Systems; using Content.Shared.Cuffs.Components; @@ -87,7 +87,11 @@ public sealed class HijackShuttleConditionSystem : EntitySystem continue; var isPersonIncapacitated = _mobState.IsIncapacitated(player.AttachedEntity.Value); - if (isPersonIncapacitated) // Allow dead and crit + if (isPersonIncapacitated) // allow incapacitated people + continue; + + var isPersonHardcritOrDead = _mobState.IsCriticalOrDead(player.AttachedEntity.Value, false); + if (isPersonIncapacitated) // allow dead and hardcritted people continue; var isPersonCuffed = diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index ba7b9f3c37..e4caa8ffb7 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -100,7 +100,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem if (!TryComp(attached, out var state)) return false; - return state.CurrentState is MobState.Alive or MobState.Critical; + return state.CurrentState.IsCritOrAlive(); } public IEnumerable GetTimedRoles(EntityUid mindId) diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index c9a4ec2d0b..7a9acc0b79 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -85,7 +85,7 @@ public sealed partial class PolymorphSystem : EntitySystem continue; if (comp.Configuration.RevertOnDeath && _mobState.IsDead(uid, mob) || - comp.Configuration.RevertOnCrit && _mobState.IsIncapacitated(uid, mob)) + comp.Configuration.RevertOnCrit && _mobState.IsCritical(uid, mob)) { Revert((uid, comp)); } diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index f8a37c6cd3..fac4f206ee 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -304,7 +304,7 @@ public sealed class PsionicsSystem : EntitySystem private void OnMobstateChanged(EntityUid uid, PsionicComponent component, MobStateChangedEvent args) { if (component.Familiars.Count <= 0 - || args.NewMobState != MobState.Dead) + || !args.IsDead()) return; foreach (var familiar in component.Familiars) diff --git a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs index 2500138a23..7d8ae5c0d2 100644 --- a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs +++ b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Radio.Components; using Content.Server.Speech; using Content.Shared.Chat; using Content.Shared.Inventory.Events; +using Content.Shared.Mobs.Systems; using Content.Shared.Radio; using Content.Shared.Radio.Components; using Content.Shared.Radio.EntitySystems; @@ -18,6 +19,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly RadioSystem _radio = default!; [Dependency] private readonly LanguageSystem _language = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { @@ -54,7 +56,8 @@ public sealed class HeadsetSystem : SharedHeadsetSystem { if (args.Channel != null && TryComp(component.Headset, out EncryptionKeyHolderComponent? keys) - && keys.Channels.Contains(args.Channel.ID)) + && keys.Channels.Contains(args.Channel.ID) + && !_mobState.IsIncapacitated(uid)) { _radio.SendRadioMessage(uid, args.Message, args.Channel, component.Headset); args.Channel = null; // prevent duplicate messages from other listeners. diff --git a/Content.Server/Revenant/EntitySystems/EssenceSystem.cs b/Content.Server/Revenant/EntitySystems/EssenceSystem.cs index f6de79fee5..cebcd97851 100644 --- a/Content.Server/Revenant/EntitySystems/EssenceSystem.cs +++ b/Content.Server/Revenant/EntitySystems/EssenceSystem.cs @@ -72,6 +72,9 @@ public sealed class EssenceSystem : EntitySystem else component.EssenceAmount = _random.NextFloat(45f, 70f); break; + case MobState.SoftCritical: + component.EssenceAmount = _random.NextFloat(40f, 60f); // i dunno? + break; case MobState.Critical: component.EssenceAmount = _random.NextFloat(35f, 50f); break; diff --git a/Content.Server/Shadowkin/ShowEtherealSystem.cs b/Content.Server/Shadowkin/ShowEtherealSystem.cs index e908bd9ce7..f51a2e4f7d 100644 --- a/Content.Server/Shadowkin/ShowEtherealSystem.cs +++ b/Content.Server/Shadowkin/ShowEtherealSystem.cs @@ -70,7 +70,7 @@ public sealed class ShowEtherealSystem : EntitySystem || !HasComp(args.Target)) return; - args.Cancelled = true; + args.Cancel(); if (_gameTiming.InPrediction) return; diff --git a/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs b/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs index 6e516f3011..8be5c40b5d 100644 --- a/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs +++ b/Content.Server/Silicon/DeadStartupButtonSystem/DeadStartupButtonSystem.cs @@ -42,7 +42,7 @@ public sealed class DeadStartupButtonSystem : SharedDeadStartupButtonSystem || !_mobState.IsDead(uid, mobStateComponent) || !TryComp(uid, out var mobThresholdsComponent) || !TryComp(uid, out var damageable) - || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var criticalThreshold, mobThresholdsComponent)) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, MobState.SoftCritical, out var criticalThreshold, mobThresholdsComponent)) return; if (damageable.TotalDamage < criticalThreshold) @@ -70,7 +70,7 @@ public sealed class DeadStartupButtonSystem : SharedDeadStartupButtonSystem private void OnMobStateChanged(EntityUid uid, DeadStartupButtonComponent comp, MobStateChangedEvent args) { - if (args.NewMobState != MobState.Alive) + if (_mobState.IsIncapacitated(uid)) return; _popup.PopupEntity(Loc.GetString("dead-startup-system-reboot-success", ("target", MetaData(uid).EntityName)), uid); diff --git a/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs b/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs index 161a912d06..e91d8afcae 100644 --- a/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs +++ b/Content.Server/Silicon/EmitBuzzWhileDamaged/EmitBuzzWhileDamagedSystem.cs @@ -32,8 +32,8 @@ public sealed class EmitBuzzWhileDamagedSystem : EntitySystem while (query.MoveNext(out var uid, out var emitBuzzOnCritComponent, out var mobStateComponent, out var thresholdsComponent, out var damageableComponent)) { if (_mobState.IsDead(uid, mobStateComponent) - || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var threshold, thresholdsComponent) - || damageableComponent.TotalDamage < threshold / 2) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.SoftCritical, MobState.Critical, out var threshold, thresholdsComponent) // who knows, maybe we'll want softcrittable borgs some day + || damageableComponent.TotalDamage < threshold.Value / 2) continue; // Check update time diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs index c4f840eed1..eeff295b76 100644 --- a/Content.Server/Silicons/Borgs/BorgSystem.cs +++ b/Content.Server/Silicons/Borgs/BorgSystem.cs @@ -172,7 +172,7 @@ public sealed partial class BorgSystem : SharedBorgSystem private void OnMobStateChanged(EntityUid uid, BorgChassisComponent component, MobStateChangedEvent args) { - if (args.NewMobState == MobState.Alive) + if (args.IsAlive()) { if (_mind.TryGetMind(uid, out _, out _)) _powerCell.SetDrawEnabled(uid, true); diff --git a/Content.Server/Traits/Assorted/SingerSystem.cs b/Content.Server/Traits/Assorted/SingerSystem.cs index 95087c0f1e..c1e148a58b 100644 --- a/Content.Server/Traits/Assorted/SingerSystem.cs +++ b/Content.Server/Traits/Assorted/SingerSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Instruments; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; @@ -29,6 +30,7 @@ public sealed class SingerSystem : SharedSingerSystem [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly InstrumentSystem _instrument = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { @@ -71,7 +73,7 @@ public sealed class SingerSystem : SharedSingerSystem private void OnMobStateChangedEvent(EntityUid uid, SharedInstrumentComponent component, MobStateChangedEvent args) { - if (args.NewMobState is MobState.Critical or MobState.Dead) + if (_mobState.IsIncapacitated(uid)) CloseMidiUi(args.Target); } diff --git a/Content.Server/Traits/TraitSystem.Functions.cs b/Content.Server/Traits/TraitSystem.Functions.cs index deb3102d4b..f87e1f1677 100644 --- a/Content.Server/Traits/TraitSystem.Functions.cs +++ b/Content.Server/Traits/TraitSystem.Functions.cs @@ -19,10 +19,14 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Mobs; using Content.Shared.Damage.Components; +using Content.Server.Administration.Commands; using Content.Shared.NPC.Systems; +using Robust.Shared.Utility; +using Robust.Shared.Reflection; using Content.Shared.Weapons.Melee; using Robust.Shared.Audio; + namespace Content.Server.Traits; /// Used for traits that add a Component upon spawning in, overwriting the pre-existing component if it already exists. @@ -415,19 +419,19 @@ public sealed partial class TraitModifyMobThresholds : TraitFunction return; var thresholdSystem = entityManager.System(); - if (CritThresholdModifier != 0) - { - var critThreshold = thresholdSystem.GetThresholdForState(uid, MobState.Critical, threshold); - if (critThreshold != 0) - thresholdSystem.SetMobStateThreshold(uid, critThreshold + CritThresholdModifier, MobState.Critical); - } - if (SoftCritThresholdModifier != 0) { var softCritThreshold = thresholdSystem.GetThresholdForState(uid, MobState.SoftCritical, threshold); if (softCritThreshold != 0) thresholdSystem.SetMobStateThreshold(uid, softCritThreshold + SoftCritThresholdModifier, MobState.SoftCritical); } + + if (CritThresholdModifier != 0) + { + var critThreshold = thresholdSystem.GetThresholdForState(uid, MobState.Critical, threshold); + if (critThreshold != 0) + thresholdSystem.SetMobStateThreshold(uid, critThreshold + CritThresholdModifier, MobState.Critical); + } if (DeadThresholdModifier != 0) { @@ -444,85 +448,30 @@ public sealed partial class TraitModifyMobState : TraitFunction // Three-State Booleans my beloved. // :faridabirb.png: - [DataField, AlwaysPushInheritance] - public bool? AllowMovementWhileCrit; - - [DataField, AlwaysPushInheritance] - public bool? AllowMovementWhileSoftCrit; - - [DataField, AlwaysPushInheritance] - public bool? AllowMovementWhileDead; - - [DataField, AlwaysPushInheritance] - public bool? AllowTalkingWhileCrit; - - [DataField, AlwaysPushInheritance] - public bool? AllowTalkingWhileSoftCrit; - - [DataField, AlwaysPushInheritance] - public bool? AllowTalkingWhileDead; - - [DataField, AlwaysPushInheritance] - public bool? DownWhenCrit; - - [DataField, AlwaysPushInheritance] - public bool? DownWhenSoftCrit; - - [DataField, AlwaysPushInheritance] - public bool? DownWhenDead; - - [DataField, AlwaysPushInheritance] - public bool? AllowHandInteractWhileCrit; - - [DataField, AlwaysPushInheritance] - public bool? AllowHandInteractWhileSoftCrit; - - [DataField, AlwaysPushInheritance] - public bool? AllowHandInteractWhileDead; + [DataField(required: true)] + public Dictionary> Params = default!; public override void OnPlayerSpawn(EntityUid uid, IComponentFactory factory, IEntityManager entityManager, ISerializationManager serializationManager) { - if (!entityManager.TryGetComponent(uid, out var mobStateComponent)) + if (!entityManager.TryGetComponent(uid, out var comp)) return; - if (AllowMovementWhileCrit is not null) - mobStateComponent.AllowMovementWhileCrit = AllowMovementWhileCrit.Value; + var _reflection = IoCManager.Resolve(); + var _proto = IoCManager.Resolve(); - if (AllowMovementWhileSoftCrit is not null) - mobStateComponent.AllowHandInteractWhileSoftCrit = AllowMovementWhileSoftCrit.Value; - - if (AllowMovementWhileDead is not null) - mobStateComponent.AllowMovementWhileDead = AllowMovementWhileDead.Value; - - if (AllowTalkingWhileCrit is not null) - mobStateComponent.AllowTalkingWhileCrit = AllowTalkingWhileCrit.Value; - - if (AllowTalkingWhileSoftCrit is not null) - mobStateComponent.AllowTalkingWhileSoftCrit = AllowTalkingWhileSoftCrit.Value; - - if (AllowTalkingWhileDead is not null) - mobStateComponent.AllowTalkingWhileDead = AllowTalkingWhileDead.Value; - - if (DownWhenCrit is not null) - mobStateComponent.DownWhenCrit = DownWhenCrit.Value; - - if (DownWhenSoftCrit is not null) - mobStateComponent.DownWhenSoftCrit = DownWhenSoftCrit.Value; - - if (DownWhenDead is not null) - mobStateComponent.DownWhenDead = DownWhenDead.Value; - - if (AllowHandInteractWhileCrit is not null) - mobStateComponent.AllowHandInteractWhileCrit = AllowHandInteractWhileCrit.Value; - - if (AllowHandInteractWhileSoftCrit is not null) - mobStateComponent.AllowHandInteractWhileSoftCrit = AllowHandInteractWhileSoftCrit.Value; - - if (AllowHandInteractWhileDead is not null) - mobStateComponent.AllowHandInteractWhileDead = AllowHandInteractWhileDead.Value; + foreach (var pair in Params) { + var succ = _reflection.TryParseEnumReference($"enum.MobState.{pair.Key}", out var rawEnum); + DebugTools.Assert(succ, $"MobState.{pair.Key} does not exist."); + MobState state = (MobState) rawEnum!; + MobStateParametersPrototype current = comp.MobStateParams[state]; + var mod = _proto.Index(pair.Value); + current.MergeWith(mod); + //current.FillDefaults(); + } + comp.Dirty(); // why is this deprecated? it's much better than manually resolving entitymanager with ioc } } diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 42cc357b8d..d6d4f6cfb7 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -112,10 +112,10 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem var target = GetEntity(ev.Target!.Value); - if (_mobState.IsIncapacitated(target)) - { - return false; - } + //if (_mobState.IsIncapacitated(target)) + //{ + // return false; + //} if (!TryComp(target, out var targetHandsComponent)) { diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 2ddff91d40..db6aa7e5b6 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -70,7 +70,7 @@ namespace Content.Server.Zombies /// private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args) { - if (args.NewMobState == MobState.Dead) + if (args.IsDead()) { ZombifyEntity(uid, args.Component); } @@ -141,7 +141,7 @@ namespace Content.Server.Zombies melee.Angle = 0.0f; melee.SoundHit = zombiecomp.BiteSound; - if (mobState.CurrentState == MobState.Alive) + if (mobState.CurrentState.IsAlive()) { // Groaning when damaged EnsureComp(target); diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index ed1f2580f6..c3100a9151 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -167,7 +167,7 @@ namespace Content.Server.Zombies private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args) { - if (args.NewMobState == MobState.Alive) + if (args.IsAlive()) { // Groaning when damaged EnsureComp(uid); @@ -248,12 +248,12 @@ namespace Content.Server.Zombies } } - if (_mobState.IsIncapacitated(entity, mobState) && !HasComp(entity) && !HasComp(entity)) + if (_mobState.IsCriticalOrDead(entity, false, mobState) && !HasComp(entity) && !HasComp(entity)) { ZombifyEntity(entity); args.BonusDamage = -args.BaseDamage; } - else if (mobState.CurrentState == MobState.Alive) //heals when zombies bite live entities + else if (_mobState.IsAlive(entity, mobState)) //heals when zombies bite live entities { _damageable.TryChangeDamage(uid, component.HealingOnBite, true, false); } diff --git a/Content.Server/_Shitmed/Targeting/TargetingSystem.cs b/Content.Server/_Shitmed/Targeting/TargetingSystem.cs index 889d8e7b30..78019a2eb4 100644 --- a/Content.Server/_Shitmed/Targeting/TargetingSystem.cs +++ b/Content.Server/_Shitmed/Targeting/TargetingSystem.cs @@ -40,7 +40,7 @@ public sealed class TargetingSystem : SharedTargetingSystem // I love groin shitcode. component.BodyStatus[TargetBodyPart.Groin] = TargetIntegrity.Dead; } - else if (args.OldMobState == MobState.Dead && (args.NewMobState == MobState.Alive || args.NewMobState == MobState.Critical)) + else if (args.WasDead() && args.IsCritOrAlive()) { component.BodyStatus = _bodySystem.GetBodyPartStatus(uid); changed = true; diff --git a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs index 457e3875d4..bd6ee8cae1 100644 --- a/Content.Shared/ActionBlocker/ActionBlockerSystem.cs +++ b/Content.Shared/ActionBlocker/ActionBlockerSystem.cs @@ -89,7 +89,7 @@ namespace Content.Shared.ActionBlocker return false; var ev = new InteractionAttemptEvent(user, target); - RaiseLocalEvent(user, ref ev); + RaiseLocalEvent(user, ev); if (ev.Cancelled) return false; @@ -98,7 +98,7 @@ namespace Content.Shared.ActionBlocker return true; var targetEv = new GettingInteractedWithAttemptEvent(user, target); - RaiseLocalEvent(target.Value, ref targetEv); + RaiseLocalEvent(target.Value, targetEv); return !targetEv.Cancelled; } @@ -129,7 +129,7 @@ namespace Content.Shared.ActionBlocker public bool CanConsciouslyPerformAction(EntityUid user) { var ev = new ConsciousAttemptEvent(user); - RaiseLocalEvent(user, ref ev); + RaiseLocalEvent(user, ev); return !ev.Cancelled; } diff --git a/Content.Shared/Administration/SharedAdminFrozenSystem.cs b/Content.Shared/Administration/SharedAdminFrozenSystem.cs index 259df2bdf2..b1ae89fdd7 100644 --- a/Content.Shared/Administration/SharedAdminFrozenSystem.cs +++ b/Content.Shared/Administration/SharedAdminFrozenSystem.cs @@ -37,7 +37,7 @@ public abstract class SharedAdminFrozenSystem : EntitySystem private void OnInteractAttempt(Entity ent, ref InteractionAttemptEvent args) { - args.Cancelled = true; + args.Cancel(); } private void OnSpeakAttempt(EntityUid uid, AdminFrozenComponent component, SpeakAttemptEvent args) diff --git a/Content.Shared/Bed/Sleep/SleepingSystem.cs b/Content.Shared/Bed/Sleep/SleepingSystem.cs index 8e6f1193ac..a73e18c82a 100644 --- a/Content.Shared/Bed/Sleep/SleepingSystem.cs +++ b/Content.Shared/Bed/Sleep/SleepingSystem.cs @@ -155,7 +155,7 @@ public sealed partial class SleepingSystem : EntitySystem private void OnConsciousAttempt(Entity ent, ref ConsciousAttemptEvent args) { - args.Cancelled = true; + args.Cancel(); } private void OnExamined(Entity ent, ref ExaminedEvent args) diff --git a/Content.Shared/Buckle/Components/BuckleComponent.cs b/Content.Shared/Buckle/Components/BuckleComponent.cs index 55831515ed..bb30e4f2d5 100644 --- a/Content.Shared/Buckle/Components/BuckleComponent.cs +++ b/Content.Shared/Buckle/Components/BuckleComponent.cs @@ -75,52 +75,61 @@ public sealed partial class BuckleComponent : Component /// Event raised directed at a strap entity before some entity gets buckled to it. /// [ByRefEvent] -public record struct StrapAttemptEvent( - Entity Strap, - Entity Buckle, - EntityUid? User, - bool Popup) +public class StrapAttemptEvent( + Entity strap, + Entity buckle, + EntityUid? user, + bool popup) : CancellableEntityEventArgs { - public bool Cancelled; + public Entity Strap = strap; + public Entity Buckle = buckle; + public EntityUid? User = user; + public bool Popup = popup; } /// /// Event raised directed at a buckle entity before it gets buckled to some strap entity. /// -[ByRefEvent] -public record struct BuckleAttemptEvent( - Entity Strap, - Entity Buckle, - EntityUid? User, - bool Popup) +public class BuckleAttemptEvent( + Entity strap, + Entity buckle, + EntityUid? user, + bool popup) : CancellableEntityEventArgs { - public bool Cancelled; + public Entity Strap = strap; + public Entity Buckle = buckle; + public EntityUid? User = user; + public bool Popup = popup; } /// /// Event raised directed at a strap entity before some entity gets unbuckled from it. /// -[ByRefEvent] -public record struct UnstrapAttemptEvent( - Entity Strap, - Entity Buckle, - EntityUid? User, - bool Popup) +public class UnstrapAttemptEvent( + Entity strap, + Entity buckle, + EntityUid? user, + bool popup) : CancellableEntityEventArgs { - public bool Cancelled; + public Entity Strap = strap; + public Entity Buckle = buckle; + public EntityUid? User = user; + public bool Popup = popup; } /// /// Event raised directed at a buckle entity before it gets unbuckled. /// -[ByRefEvent] -public record struct UnbuckleAttemptEvent( - Entity Strap, - Entity Buckle, - EntityUid? User, - bool Popup) +public class UnbuckleAttemptEvent( + Entity strap, + Entity buckle, + EntityUid? user, + bool popup) : CancellableEntityEventArgs { - public bool Cancelled; + public Entity Strap = strap; + public Entity Buckle = buckle; + public EntityUid? User = user; + public bool Popup = popup; } /// diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index c992f442e2..73adf6d57c 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -303,7 +303,7 @@ public abstract partial class SharedBuckleSystem } var buckleAttempt = new BuckleAttemptEvent((strapUid, strapComp), (buckleUid, buckleComp), user, popup); - RaiseLocalEvent(buckleUid, ref buckleAttempt); + RaiseLocalEvent(buckleUid, buckleAttempt); if (buckleAttempt.Cancelled) return false; @@ -464,7 +464,7 @@ public abstract partial class SharedBuckleSystem Appearance.SetData(strap, StrapVisuals.State, strap.Comp.BuckledEntities.Count != 0); Appearance.SetData(buckle, BuckleVisuals.Buckled, false); - if (HasComp(buckle) || _mobState.IsIncapacitated(buckle)) + if (HasComp(buckle) || _mobState.IsDown(buckle)) _standing.Down(buckle, playSound: false); else _standing.Stand(buckle); @@ -507,12 +507,12 @@ public abstract partial class SharedBuckleSystem return false; var unbuckleAttempt = new UnbuckleAttemptEvent(strap, buckle!, user, popup); - RaiseLocalEvent(buckle, ref unbuckleAttempt); + RaiseLocalEvent(buckle, unbuckleAttempt); if (unbuckleAttempt.Cancelled) return false; var unstrapAttempt = new UnstrapAttemptEvent(strap, buckle!, user, popup); - RaiseLocalEvent(strap, ref unstrapAttempt); + RaiseLocalEvent(strap, unstrapAttempt); return !unstrapAttempt.Cancelled; } diff --git a/Content.Shared/Contests/ContestsSystem.cs b/Content.Shared/Contests/ContestsSystem.cs index 454d17a5d8..254c354e39 100644 --- a/Content.Shared/Contests/ContestsSystem.cs +++ b/Content.Shared/Contests/ContestsSystem.cs @@ -228,7 +228,7 @@ public sealed partial class ContestsSystem : EntitySystem || !_cfg.GetCVar(CCVars.DoHealthContests) || !TryComp(performer, out var damage) || !TryComp(performer, out var thresholdsComponent) - || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var threshold, thresholdsComponent)) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, Mobs.MobState.SoftCritical, out var threshold, thresholdsComponent)) return 1f; return ContestClamp(ContestClampOverride(bypassClamp) @@ -251,8 +251,8 @@ public sealed partial class ContestsSystem : EntitySystem || !TryComp(target, out var targetDamage) || !TryComp(performer, out var perfThresholdComp) || !TryComp(target, out var targetThresholdComp) - || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, out var perfThreshold, perfThresholdComp) - || !_mobThreshold.TryGetThresholdForState(target, Mobs.MobState.Critical, out var targetThreshold, targetThresholdComp)) + || !_mobThreshold.TryGetThresholdForState(performer, Mobs.MobState.Critical, Mobs.MobState.SoftCritical, out var perfThreshold, perfThresholdComp) + || !_mobThreshold.TryGetThresholdForState(target, Mobs.MobState.Critical, Mobs.MobState.SoftCritical, out var targetThreshold, targetThresholdComp)) return 1f; return ContestClamp(ContestClampOverride(bypassClamp) diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 9269f7d179..1f4c21aa12 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -95,7 +95,7 @@ namespace Content.Shared.Cuffs private void CheckInteract(Entity ent, ref InteractionAttemptEvent args) { if (!ent.Comp.CanStillInteract) - args.Cancelled = true; + args.Cancel(); } private void OnUncuffAttempt(ref UncuffAttemptEvent args) @@ -207,15 +207,15 @@ namespace Content.Shared.Cuffs args.Cancel(); } - private void OnBuckleAttempt(Entity ent, EntityUid? user, ref bool cancelled, bool buckling, bool popup) + private void OnBuckleAttempt(EntityUid uid, CuffableComponent comp, EntityUid? user, CancellableEntityEventArgs args, bool buckling, bool popup) { - if (cancelled || user != ent.Owner) + if (args.Cancelled || user != uid) return; - if (!TryComp(ent, out var hands) || ent.Comp.CuffedHandCount < hands.Count) + if (!TryComp(uid, out var hands) || comp.CuffedHandCount < hands.Count) return; - cancelled = true; + args.Cancel(); if (!popup) return; @@ -223,17 +223,17 @@ namespace Content.Shared.Cuffs ? Loc.GetString("handcuff-component-cuff-interrupt-buckled-message") : Loc.GetString("handcuff-component-cuff-interrupt-unbuckled-message"); - _popup.PopupClient(message, ent, user); + _popup.PopupClient(message, uid, user); } - private void OnBuckleAttemptEvent(Entity ent, ref BuckleAttemptEvent args) + private void OnBuckleAttemptEvent(EntityUid uid, CuffableComponent comp, BuckleAttemptEvent args) { - OnBuckleAttempt(ent, args.User, ref args.Cancelled, true, args.Popup); + OnBuckleAttempt(uid, comp, args.User, args, true, args.Popup); } - private void OnUnbuckleAttemptEvent(Entity ent, ref UnbuckleAttemptEvent args) + private void OnUnbuckleAttemptEvent(EntityUid uid, CuffableComponent comp, UnbuckleAttemptEvent args) { - OnBuckleAttempt(ent, args.User, ref args.Cancelled, false, args.Popup); + OnBuckleAttempt(uid, comp, args.User, args, false, args.Popup); } private void OnPull(EntityUid uid, CuffableComponent component, PullMessage args) diff --git a/Content.Shared/Damage/Components/DamageableComponent.cs b/Content.Shared/Damage/Components/DamageableComponent.cs index ed01bd2647..69c4e556f0 100644 --- a/Content.Shared/Damage/Components/DamageableComponent.cs +++ b/Content.Shared/Damage/Components/DamageableComponent.cs @@ -77,6 +77,7 @@ namespace Content.Shared.Damage public Dictionary> HealthIcons = new() { { MobState.Alive, "HealthIconFine" }, + { MobState.SoftCritical, "HealthIconCritical" }, { MobState.Critical, "HealthIconCritical" }, { MobState.Dead, "HealthIconDead" }, }; diff --git a/Content.Shared/Devour/SharedDevourSystem.cs b/Content.Shared/Devour/SharedDevourSystem.cs index 14047fba7d..12ef7c9710 100644 --- a/Content.Shared/Devour/SharedDevourSystem.cs +++ b/Content.Shared/Devour/SharedDevourSystem.cs @@ -54,6 +54,7 @@ public abstract class SharedDevourSystem : EntitySystem { switch (targetState.CurrentState) { + // case MobState.SoftCritical: // they squirm too much case MobState.Critical: case MobState.Dead: diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.cs index 48051e0a30..629efee967 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Damage; using Content.Shared.Hands.Components; using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; using Content.Shared.Tag; using Robust.Shared.GameStates; using Robust.Shared.Serialization; @@ -18,6 +19,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly TagSystem _tag = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; /// /// We'll use an excess time so stuff like finishing effects can show. @@ -48,14 +50,17 @@ public abstract partial class SharedDoAfterSystem : EntitySystem private void OnStateChanged(EntityUid uid, DoAfterComponent component, MobStateChangedEvent args) { - if (args.NewMobState != MobState.Dead || args.NewMobState != MobState.Critical) - return; - - foreach (var doAfter in component.DoAfters.Values) + // Original code + /*if (args.NewMobState != MobState.Dead || args.NewMobState != MobState.Critical) // comment block of shame + return;*/ + if (_mobState.IsIncapacitated(uid)) { - InternalCancel(doAfter, component); + foreach (var doAfter in component.DoAfters.Values) + { + InternalCancel(doAfter, component); + } + Dirty(uid, component); } - Dirty(uid, component); } /// diff --git a/Content.Shared/Examine/ExamineSystemShared.cs b/Content.Shared/Examine/ExamineSystemShared.cs index a1c30a2bd0..f1f0aa8778 100644 --- a/Content.Shared/Examine/ExamineSystemShared.cs +++ b/Content.Shared/Examine/ExamineSystemShared.cs @@ -54,8 +54,8 @@ namespace Content.Shared.Examine if (IsClientSide(entity)) return true; - // check if the mob is in critical or dead - if (MobStateSystem.IsIncapacitated(examiner)) + // check if the mob is conscious to examine something + if (MobStateSystem.IsConscious(examiner)) return false; if (!InRangeUnOccluded(examiner, entity, ExamineDetailsRange)) diff --git a/Content.Shared/Foldable/FoldableSystem.cs b/Content.Shared/Foldable/FoldableSystem.cs index 2a846f4f23..597ddbc310 100644 --- a/Content.Shared/Foldable/FoldableSystem.cs +++ b/Content.Shared/Foldable/FoldableSystem.cs @@ -53,10 +53,10 @@ public sealed class FoldableSystem : EntitySystem args.Cancelled = true; } - public void OnStrapAttempt(EntityUid uid, FoldableComponent comp, ref StrapAttemptEvent args) + public void OnStrapAttempt(EntityUid uid, FoldableComponent comp, StrapAttemptEvent args) { if (comp.IsFolded) - args.Cancelled = true; + args.Cancel(); } /// diff --git a/Content.Shared/Ghost/SharedGhostSystem.cs b/Content.Shared/Ghost/SharedGhostSystem.cs index f10ce162bf..63263fc684 100644 --- a/Content.Shared/Ghost/SharedGhostSystem.cs +++ b/Content.Shared/Ghost/SharedGhostSystem.cs @@ -30,7 +30,7 @@ namespace Content.Shared.Ghost private void OnAttemptInteract(Entity ent, ref InteractionAttemptEvent args) { if (!ent.Comp.CanGhostInteract) - args.Cancelled = true; + args.Cancel(); } private void OnAttempt(EntityUid uid, GhostComponent component, CancellableEntityEventArgs args) diff --git a/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs b/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs index a04c053635..fcc0c478cd 100644 --- a/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs +++ b/Content.Shared/Interaction/Events/InteractionAttemptEvent.cs @@ -1,12 +1,10 @@ -namespace Content.Shared.Interaction.Events +namespace Content.Shared.Interaction.Events { /// /// Event raised directed at a user to see if they can perform a generic interaction. /// - [ByRefEvent] - public struct InteractionAttemptEvent(EntityUid uid, EntityUid? target) + public class InteractionAttemptEvent(EntityUid uid, EntityUid? target) : CancellableEntityEventArgs { - public bool Cancelled; public readonly EntityUid Uid = uid; public readonly EntityUid? Target = target; } @@ -14,10 +12,8 @@ /// /// Raised to determine whether an entity is conscious to perform an action. /// - [ByRefEvent] - public struct ConsciousAttemptEvent(EntityUid uid) + public class ConsciousAttemptEvent(EntityUid uid) : CancellableEntityEventArgs { - public bool Cancelled; public readonly EntityUid Uid = uid; } @@ -25,10 +21,8 @@ /// Event raised directed at the target entity of an interaction to see if the user is allowed to perform some /// generic interaction. /// - [ByRefEvent] - public struct GettingInteractedWithAttemptEvent(EntityUid uid, EntityUid? target) + public class GettingInteractedWithAttemptEvent(EntityUid uid, EntityUid? target) : CancellableEntityEventArgs { - public bool Cancelled; public readonly EntityUid Uid = uid; public readonly EntityUid? Target = target; } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs index 52c40477c9..8a9e1ea077 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.Blocking.cs @@ -29,7 +29,7 @@ public partial class SharedInteractionSystem private void CancelInteractEvent(Entity ent, ref InteractionAttemptEvent args) { if (ent.Comp.BlockInteraction) - args.Cancelled = true; + args.Cancel(); } private void OnMoveAttempt(EntityUid uid, BlockMovementComponent component, UpdateCanMoveEvent args) diff --git a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs index f6ce235ff7..fdd9dd0e25 100644 --- a/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs +++ b/Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs @@ -122,7 +122,7 @@ public abstract partial class SharedCryoPodSystem: EntitySystem // RemComp(contained); // Restore the correct position of the patient. Checking the components manually feels hacky, but I did not find a better way for now. - if (HasComp(contained) || _mobStateSystem.IsIncapacitated(contained)) + if (HasComp(contained) || _mobStateSystem.IsDown(contained)) { _standingStateSystem.Down(contained); } diff --git a/Content.Shared/Mobs/Components/MobStateComponent.cs b/Content.Shared/Mobs/Components/MobStateComponent.cs index d767e77a36..74fefbc560 100644 --- a/Content.Shared/Mobs/Components/MobStateComponent.cs +++ b/Content.Shared/Mobs/Components/MobStateComponent.cs @@ -1,5 +1,18 @@ using Content.Shared.Damage; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Serialization.Markdown.Sequence; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Runtime.CompilerServices; namespace Content.Shared.Mobs.Components; @@ -14,92 +27,173 @@ namespace Content.Shared.Mobs.Components; [AutoGenerateComponentState] public sealed partial class MobStateComponent : Component { - /// - /// Whether this mob will be allowed to issue movement commands when in the Critical MobState. - /// - [DataField] - public bool AllowMovementWhileCrit; + + [DataField("mobStateParams")] + public Dictionary InitMobStateParams = new() + { + {"Alive", "AliveDefault" }, + {"SoftCritical", "SoftCriticalDefault" }, + {"Critical", "CriticalDefault" }, + {"Dead", "DeadDefault" } + }; + + [AutoNetworkedField, ViewVariables] + public Dictionary MobStateParams = new(); /// - /// Whether this mob will be allowed to issue movement commands when in the Soft-Crit MobState. + /// Use this to modify mobstate parameters without actually overwrithing them. /// - [DataField] - public bool AllowMovementWhileSoftCrit; - - /// - /// Whether this mob will be allowed to issue movement commands when in the Dead MobState. - /// This is provided for completeness sake, and *probably* shouldn't be used by default. - /// - [DataField] - public bool AllowMovementWhileDead; - - /// - /// Whether this mob will be allowed to talk while in the Critical MobState. - /// - [DataField] - public bool AllowTalkingWhileCrit = true; - - /// - /// Whether this mob will be allowed to talk while in the SoftCritical MobState. - /// - [DataField] - public bool AllowTalkingWhileSoftCrit = true; - - /// - /// Whether this mob will be allowed to talk while in the Dead MobState. - /// This is provided for completeness sake, and *probably* shouldn't be used by default. - /// - [DataField] - public bool AllowTalkingWhileDead; - - /// - /// Whether this mob is forced to be downed when entering the Critical MobState. - /// - [DataField] - public bool DownWhenCrit = true; - - /// - /// Whether this mob is forced to be downed when entering the SoftCritical MobState. - /// - [DataField] - public bool DownWhenSoftCrit = true; - - /// - /// Whether this mob is forced to be downed when entering the Dead MobState. - /// - [DataField] - public bool DownWhenDead = true; - - /// - /// Whether this mob is allowed to perform hand interactions while in the Critical MobState. - /// - [DataField] - public bool AllowHandInteractWhileCrit; - - /// - /// Whether this mob is allowed to perform hand interactions while in the SoftCritical MobState. - /// - [DataField] - public bool AllowHandInteractWhileSoftCrit; - - /// - /// Whether this mob is allowed to perform hand interactions while in the Dead MobState. - /// This is provided for completeness sake, and *probably* shouldn't be used by default. - /// - [DataField] - public bool AllowHandInteractWhileDead; + [AutoNetworkedField, ViewVariables] + public Dictionary MobStateParamsOverrides = new() + { + { MobState.Alive, new() }, + { MobState.SoftCritical, new() }, + { MobState.Critical, new() }, + { MobState.Dead, new() } + }; + [ViewVariables] + public MobStateParametersPrototype CurrentStateParams => MobStateParams[CurrentState]; + [ViewVariables] + public MobStateParametersOverride CurrentStateOverrides => MobStateParamsOverrides[CurrentState]; //default mobstate is always the lowest state level [AutoNetworkedField, ViewVariables] public MobState CurrentState { get; set; } = MobState.Alive; - [DataField] - [AutoNetworkedField] - public HashSet AllowedStates = new() - { - MobState.Alive, - MobState.Critical, - MobState.SoftCritical, - MobState.Dead - }; + [DataField] + [AutoNetworkedField] + public HashSet AllowedStates = new() + { + MobState.Alive, + MobState.SoftCritical, + MobState.Critical, + MobState.Dead + }; + + + #region terraria wall of getters boss + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MobStateParametersOverride GetOverride(MobState? State = null) { MobStateParamsOverrides.TryGetValue(State ?? CurrentState, out var value); return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private MobStateParametersPrototype? GetParams(MobState? State = null) { MobStateParams.TryGetValue(State ?? CurrentState, out var value); return value; } + + + // the "?." and "?? false" at the end is because at round restard MobStateParams apparently can be wiped, + // but stuff that relies on it will still run, and I don't know why. Cool. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanMove(MobState? State = null) => GetOverride(State).Moving ?? GetParams(State)?.Moving ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanTalk(MobState? State = null) => GetOverride(State).Talking ?? GetParams(State)?.Talking ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanEmote(MobState? State = null) => GetOverride(State).Emoting ?? GetParams(State)?.Emoting ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanThrow(MobState? State = null) => GetOverride(State).Throwing ?? GetParams(State)?.Throwing ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanPickUp(MobState? State = null) => GetOverride(State).PickingUp ?? GetParams(State)?.PickingUp ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanPull(MobState? State = null) => GetOverride(State).Pulling ?? GetParams(State)?.Pulling ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanAttack(MobState? State = null) => GetOverride(State).Attacking ?? GetParams(State)?.Attacking ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanUse(MobState? State = null) => GetOverride(State).Using ?? GetParams(State)?.Using ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanPoint(MobState? State = null) =>GetOverride(State).Pointing ?? GetParams(State)?.Pointing ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsConscious(MobState? State = null) => GetOverride(State).IsConscious ?? GetParams(State)?.IsConscious ?? false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsDowned(MobState? State = null) => GetOverride(State).ForceDown ?? GetParams(State)?.ForceDown ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool ShouldDropItems(MobState? State = null) => GetOverride(State).DropItemsOnEntering ?? GetParams(State)?.DropItemsOnEntering ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsIncapacitated(MobState? State = null) => GetOverride(State).Incapacitated ?? GetParams(State)?.Incapacitated ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsThreatening(MobState? State = null) => GetOverride(State).Threatening ?? GetParams(State)?.Threatening ?? true; // :) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanEquipSelf(MobState? State = null) => GetOverride(State).CanEquipSelf ?? GetParams(State)?.CanEquipSelf ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanUnequipSelf(MobState? State = null) => GetOverride(State).CanUnequipSelf ?? GetParams(State)?.CanUnequipSelf ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanEquipOther(MobState? State = null) => GetOverride(State).CanEquipOther ?? GetParams(State)?.CanEquipOther ?? false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CanUnequipOther(MobState? State = null) => GetOverride(State).CanUnequipOther ?? GetParams(State)?.CanUnequipOther ?? false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float? GetOxyDamageOverlay(MobState? State = null) => GetOverride(State).OxyDamageOverlay ?? GetParams(State)?.OxyDamageOverlay; + /// + /// Not clamped, but you really should, in case someone decides to be funny with the prototypes. + /// [0,1]. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float GetBreathingMultiplier(MobState? State = null) => GetOverride(State).BreathingMultiplier ?? GetParams(State)?.BreathingMultiplier ?? 1f; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float GetStrippingTimeMultiplier(MobState? State = null) => GetOverride(State).StrippingTimeMultiplier ?? GetParams(State)?.StrippingTimeMultiplier ?? 1f; + #endregion +} + +[NetSerializable, Serializable] +public struct MobStateParametersOverride +{ + [DataField] + public bool? Moving, Talking, Emoting, + Throwing, PickingUp, Pulling, Attacking, Using, Pointing, + IsConscious, + CanEquipSelf, CanUnequipSelf, CanEquipOther, CanUnequipOther, + ForceDown, DropItemsOnEntering, Incapacitated, Threatening; + + [DataField] + public float? OxyDamageOverlay, BreathingMultiplier, StrippingTimeMultiplier; +} + +[Prototype("mobStateParams")] +[Serializable] // do i need to put netserializable on it? +public sealed partial class MobStateParametersPrototype : IPrototype, IInheritingPrototype +{ + [IdDataField] + public string ID { get; } = default!; + + [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; private set; } + + [AbstractDataField] + [NeverPushInheritance] + public bool Abstract { get; } + + [DataField] + public bool? Moving, Talking, Emoting, + Throwing, PickingUp, Pulling, Attacking, Using, Pointing, + IsConscious, + CanEquipSelf, CanUnequipSelf, CanEquipOther, CanUnequipOther, + ForceDown, DropItemsOnEntering, Incapacitated, Threatening; + + [DataField] + public float? OxyDamageOverlay, BreathingMultiplier, StrippingTimeMultiplier; + + public void MergeWith(MobStateParametersPrototype other) + { + this.Moving = other.Moving ?? this.Moving; + this.Talking = other.Talking ?? this.Talking; + this.Emoting = other.Emoting ?? this.Emoting; + this.Throwing = other.Throwing ?? this.Throwing; + this.PickingUp = other.PickingUp ?? this.PickingUp; + this.Pulling = other.Pulling ?? this.Pulling; + this.Attacking = other.Attacking ?? this.Attacking; + this.Using = other.Using ?? this.Using; + this.Pointing = other.Pointing ?? this.Pointing; + this.IsConscious = other.IsConscious ?? this.IsConscious; + this.CanEquipSelf = other.CanEquipSelf ?? this.CanEquipSelf; + this.CanEquipOther = other.CanEquipOther ?? this.CanEquipOther; + this.CanUnequipSelf = other.CanUnequipSelf ?? this.CanUnequipSelf; + this.CanUnequipOther = other.CanUnequipOther ?? this.CanUnequipOther; + this.ForceDown = other.ForceDown ?? this.ForceDown; + this.Incapacitated = other.Incapacitated ?? this.Incapacitated; + this.Threatening = other.Threatening ?? this.Threatening; + this.DropItemsOnEntering = other.DropItemsOnEntering ?? this.DropItemsOnEntering; + this.BreathingMultiplier = other.BreathingMultiplier ?? this.BreathingMultiplier; + this.OxyDamageOverlay = other.OxyDamageOverlay ?? this.OxyDamageOverlay; + this.StrippingTimeMultiplier = other.StrippingTimeMultiplier ?? this.StrippingTimeMultiplier; + } + } diff --git a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs index fe435db4d8..5d9ef74776 100644 --- a/Content.Shared/Mobs/Components/MobThresholdsComponent.cs +++ b/Content.Shared/Mobs/Components/MobThresholdsComponent.cs @@ -28,8 +28,8 @@ public sealed partial class MobThresholdsComponent : Component public Dictionary> StateAlertDict = new() { {MobState.Alive, "HumanHealth"}, - {MobState.Critical, "HumanCrit"}, {MobState.SoftCritical, "HumanCrit"}, + {MobState.Critical, "HumanCrit"}, {MobState.Dead, "HumanDead"}, }; diff --git a/Content.Shared/Mobs/MobState.cs b/Content.Shared/Mobs/MobState.cs index 3476651e4f..c0d9f52a51 100644 --- a/Content.Shared/Mobs/MobState.cs +++ b/Content.Shared/Mobs/MobState.cs @@ -1,23 +1,22 @@ -using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Components; using Robust.Shared.Serialization; +using Robust.Shared.Utility; +using System.Runtime.CompilerServices; namespace Content.Shared.Mobs; /// /// Defines what state an is in. -/// /// Ordered from most alive to least alive. -/// To enumerate them in this way see -/// . /// [Serializable, NetSerializable] public enum MobState : byte { Invalid = 0, Alive = 1, - Critical = 2, - SoftCritical = 3, - Dead = 4, + SoftCritical = 2, + Critical = 3, + Dead = 4 } /// @@ -31,9 +30,104 @@ public enum MobState : byte public record struct MobStateChangedEvent(EntityUid Target, MobStateComponent Component, MobState OldMobState, MobState NewMobState, EntityUid? Origin = null); -public static class A + + +/// +/// so i've decided to implement softcrit and now i have to go through +/// all the mobstate comparisons and make sure stuff that uses MobState.Critical +/// knows about MobState.SoftCritical. +/// +/// A better approach to this would probably be look like "IsConscious", "IsDying" etc. +/// which would be more generalised. +/// +/// But for now i just want to wrap all MobState comparisons in these static methods so it's easier to deal with. +/// +public static class MobStateHelpers { - //^.^ + //^.^ // fuck off + + /// + /// Returns true if newmobstate is crit/softcrit and the old one isn't. + /// if ignoreDead is true, will not return true if mobstate changed from dead to crit/softcrit. + /// + public static bool EnteredCrit(this MobStateChangedEvent ev, bool ignoreDead = true) + { + if (ignoreDead) + return ev.IsCrit() && ev.WasCritOrAlive(); + return ev.IsCrit() && (ev.WasCrit() || ev.WasAlive()); + } + /// + /// Returns true if newmobstate is crit/softcrit and the old one isn't. + /// if ignoreDead is true, will not return true if mobstate changed from crit/softcrit to dead. + /// + public static bool ExitedCrit(this MobStateChangedEvent ev, bool ignoreDead = true) + { + if (ignoreDead) + return ev.WasCrit() && ev.IsCritOrAlive(); + return ev.WasCrit() && (ev.IsCrit() || ev.IsAlive()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCritOrDead(this MobStateChangedEvent ev, bool CountSoftCrit = true) => ev.NewMobState.IsCritOrDead(CountSoftCrit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCritOrAlive(this MobStateChangedEvent ev, bool CountSoftCrit = true) => ev.NewMobState.IsCritOrAlive(CountSoftCrit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCrit(this MobStateChangedEvent ev, bool CountSoftCrit = true) => ev.NewMobState.IsCrit(CountSoftCrit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDead(this MobStateChangedEvent ev) => ev.NewMobState.IsDead(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAlive(this MobStateChangedEvent ev) => ev.NewMobState.IsAlive(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsValid(this MobStateChangedEvent ev) => ev.NewMobState.IsValid(); + + public static bool WasCritOrDead(this MobStateChangedEvent ev, bool CountSoftCrit = true) => ev.OldMobState.IsCritOrDead(CountSoftCrit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool WasCritOrAlive(this MobStateChangedEvent ev, bool CountSoftCrit = true) => ev.OldMobState.IsCritOrAlive(CountSoftCrit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool WasCrit(this MobStateChangedEvent ev, bool CountSoftCrit = true) => ev.OldMobState.IsCrit(CountSoftCrit); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool WasDead(this MobStateChangedEvent ev) => ev.OldMobState.IsDead(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool WasAlive(this MobStateChangedEvent ev) => ev.OldMobState.IsAlive(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool WasValid(this MobStateChangedEvent ev) => ev.OldMobState.IsValid(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCritOrDead(this MobState state, bool CountSoftCrit = true) => state.IsCrit(CountSoftCrit) || state.IsDead(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsCritOrAlive(this MobState state, bool CountSoftCrit = true) => state.IsCrit(CountSoftCrit) || state.IsDead(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsDead(this MobState state) => state == MobState.Dead; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAlive(this MobState state) => state == MobState.Alive; + /// + /// i think i am finally losing it. I do not remember writhing this and + /// i cannot fathom what situation would this be warranted in. + /// + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAliveOrDead(this MobState state, bool CountSoftCrit = true) => state.IsDead() || state.IsAlive(); + + public static bool IsCrit(this MobState state, bool CountSoftCrit = true) + { + switch (state) + { + case MobState.SoftCritical: + return CountSoftCrit; + case MobState.Critical: + return true; + default: + return false; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsValid(this MobState state) // fix your shit. mobstate should never be invalid. + { + DebugTools.Assert(state != MobState.Invalid, "MobState is invalid."); + return state != MobState.Invalid; + } } //This is dumb and I hate it but I don't feel like refactoring this garbage diff --git a/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs b/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs index 9419daf348..20625b3464 100644 --- a/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs +++ b/Content.Shared/Mobs/Systems/MobStateActionsSystem.cs @@ -14,7 +14,7 @@ public sealed class MobStateActionsSystem : EntitySystem public override void Initialize() { SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnMobStateComponentInit); + SubscribeLocalEvent(OnMobStateComponentInit); } private void OnMobStateChanged(EntityUid uid, MobStateActionsComponent component, MobStateChangedEvent args) @@ -22,12 +22,12 @@ public sealed class MobStateActionsSystem : EntitySystem ComposeActions(uid, component, args.NewMobState); } - private void OnMobStateComponentInit(EntityUid uid, MobStateComponent component, ComponentInit args) + private void OnMobStateComponentInit(EntityUid uid, MobStateActionsComponent component, ComponentStartup args) { - if (!TryComp(uid, out var mobStateActionsComp)) + if (!TryComp(uid, out var mobState)) return; - ComposeActions(uid, mobStateActionsComp, component.CurrentState); + ComposeActions(uid, component, mobState.CurrentState); } /// diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs index f5acced4db..8e81d87757 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs @@ -1,10 +1,11 @@ -using Content.Shared.Bed.Sleep; +using Content.Shared.Bed.Sleep; using Content.Shared.Buckle.Components; using Content.Shared.CCVar; using Content.Shared.CombatMode.Pacification; using Content.Shared.Damage.ForceSay; using Content.Shared.Emoting; using Content.Shared.Hands; +using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Inventory.Events; @@ -19,49 +20,71 @@ using Content.Shared.Strip.Components; using Content.Shared.Throwing; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Reflection; +using Robust.Shared.Utility; +using System.ComponentModel; namespace Content.Shared.Mobs.Systems; public partial class MobStateSystem { [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IReflectionManager _wehavereflectionathome = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; //General purpose event subscriptions. If you can avoid it register these events inside their own systems private void SubscribeEvents() { + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnGettingStripped); - SubscribeLocalEvent(OnDirectionAttempt); - SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckConcious); - SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(OnSpeakAttempt); SubscribeLocalEvent(OnEquipAttempt); - SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(OnUnequipAttempt); - SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(OnMoveAttempt); - SubscribeLocalEvent(CheckAct); - SubscribeLocalEvent(CheckAct); SubscribeLocalEvent(OnSleepAttempt); + SubscribeLocalEvent(OnDirectionAttempt); + SubscribeLocalEvent(CheckActFactory(c => c.CanUse())); + SubscribeLocalEvent(CheckActFactory(c => c.CanAttack())); + SubscribeLocalEvent(CheckActFactory(c => c.IsConscious())); + SubscribeLocalEvent(CheckActFactory(c => !c.IsIncapacitated())); + SubscribeLocalEvent(CheckActFactory(c => c.CanThrow())); + SubscribeLocalEvent(CheckActFactory(c => c.CanEmote())); + SubscribeLocalEvent(CheckActFactory(c => c.CanPickUp())); + SubscribeLocalEvent(CheckActFactory(c => c.CanPickUp())); + SubscribeLocalEvent(CheckActFactory(c => c.CanPull())); + SubscribeLocalEvent(CheckActFactory(c => !c.IsDowned())); + SubscribeLocalEvent(CheckActFactory(c => c.CanPoint())); SubscribeLocalEvent(OnCombatModeShouldHandInteract); SubscribeLocalEvent(OnAttemptPacifiedAttack); SubscribeLocalEvent(OnUnbuckleAttempt); } + private void OnComponentInit(EntityUid uid, MobStateComponent comp, ComponentInit args) + { + foreach(var entry in comp.InitMobStateParams) + { + Enum? e = null; + DebugTools.Assert(_wehavereflectionathome.TryParseEnumReference($"enum.MobState.{entry.Key}", out e), $"MobState.{entry.Key} does not exist."); + + MobState state = (MobState) e; + + // if this fails, then either the prototype has two parameters specified for one mobstate, + // or we've already received the params from the server before we had the chance to set them + // ourselves. (see TraitModifyMobState) + if (comp.MobStateParams.TryAdd(state, _proto.Index(entry.Value))) + { + //comp.MobStateParams[state].FillDefaults(); + } + } + } + + private void OnDirectionAttempt(Entity ent, ref ChangeDirectionAttemptEvent args) { - if (ent.Comp.CurrentState is MobState.Alive - || ent.Comp.CurrentState is MobState.Critical - && ent.Comp.AllowMovementWhileCrit - && _configurationManager.GetCVar(CCVars.AllowMovementWhileCrit) - || ent.Comp.CurrentState is MobState.SoftCritical - && ent.Comp.AllowMovementWhileSoftCrit - || ent.Comp.CurrentState is MobState.Dead - && ent.Comp.AllowMovementWhileDead) + if (ent.Comp.CanMove()) return; args.Cancel(); @@ -69,37 +92,19 @@ public partial class MobStateSystem private void OnMoveAttempt(Entity ent, ref UpdateCanMoveEvent args) { - if (ent.Comp.CurrentState is MobState.Alive - || ent.Comp.CurrentState is MobState.Critical - && ent.Comp.AllowMovementWhileCrit - && _configurationManager.GetCVar(CCVars.AllowMovementWhileCrit) - || ent.Comp.CurrentState is MobState.SoftCritical - && ent.Comp.AllowMovementWhileSoftCrit - || ent.Comp.CurrentState is MobState.Dead - && ent.Comp.AllowMovementWhileDead) + if (ent.Comp.CanMove()) return; args.Cancel(); } - private void OnUnbuckleAttempt(Entity ent, ref UnbuckleAttemptEvent args) + private void OnUnbuckleAttempt(EntityUid uid, MobStateComponent comp, UnbuckleAttemptEvent args) { // TODO is this necessary? // Shouldn't the interaction have already been blocked by a general interaction check? - if (args.User == ent.Owner && IsIncapacitated(ent)) - args.Cancelled = true; - } - - private void CheckConcious(Entity ent, ref ConsciousAttemptEvent args) - { - switch (ent.Comp.CurrentState) - { - case MobState.Dead: - case MobState.Critical: - args.Cancelled = true; - break; - } + if (args.User.HasValue && IsIncapacitated(args.User.Value)) + args.Cancel(); } private void OnStateExitSubscribers(EntityUid target, MobStateComponent component, MobState state) @@ -109,24 +114,20 @@ public partial class MobStateSystem case MobState.Alive: //unused break; - case MobState.Critical: - if (component.CurrentState is not MobState.Alive) - break; - _standing.Stand(target); - break; + case MobState.SoftCritical: - if (component.CurrentState is not MobState.Alive) - break; - _standing.Stand(target); + case MobState.Critical: + //if (component.CurrentState is not MobState.Alive) + // break; + //_standing.Stand(target); break; case MobState.Dead: RemComp(target); - if (component.CurrentState is MobState.Alive) - _standing.Stand(target); - - if (!_standing.IsDown(target) && TryComp(target, out var physics)) - _physics.SetCanCollide(target, true, body: physics); - + //if (component.CurrentState is MobState.Alive) + // _standing.Stand(target); + // + //if (!_standing.IsDown(target) && TryComp(target, out var physics)) + // _physics.SetCanCollide(target, true, body: physics); break; case MobState.Invalid: //unused @@ -138,6 +139,14 @@ public partial class MobStateSystem private void OnStateEnteredSubscribers(EntityUid target, MobStateComponent component, MobState state) { + if (component.ShouldDropItems() && HasComp(target)) + RaiseLocalEvent(target, new DropHandItemsEvent()); + + if (component.IsDowned()) + _standing.Down(target, dropHeldItems: false); + else + _standing.Stand(target); + // All of the state changes here should already be networked, so we do nothing if we are currently applying a // server state. if (_timing.ApplyingState) @@ -147,26 +156,16 @@ public partial class MobStateSystem switch (state) { case MobState.Alive: - _standing.Stand(target); _appearance.SetData(target, MobStateVisuals.State, MobState.Alive); break; - case MobState.Critical: - if (component.DownWhenCrit) - _standing.Down(target); - _appearance.SetData(target, MobStateVisuals.State, MobState.Critical); - break; case MobState.SoftCritical: - if (component.DownWhenSoftCrit) - _standing.Down(target); + case MobState.Critical: _appearance.SetData(target, MobStateVisuals.State, MobState.Critical); break; case MobState.Dead: EnsureComp(target); - if (component.DownWhenDead) - _standing.Down(target); - - if (_standing.IsDown(target) && TryComp(target, out var physics)) - _physics.SetCanCollide(target, false, body: physics); + //if (_standing.IsDown(target) && TryComp(target, out var physics)) + // _physics.SetCanCollide(target, false, body: physics); _appearance.SetData(target, MobStateVisuals.State, MobState.Dead); break; @@ -190,11 +189,7 @@ public partial class MobStateSystem private void OnGettingStripped(EntityUid target, MobStateComponent component, BeforeGettingStrippedEvent args) { - // Incapacitated or dead targets get stripped two or three times as fast. Makes stripping corpses less tedious. - if (IsDead(target, component)) - args.Multiplier /= 3; - else if (IsCritical(target, component)) - args.Multiplier /= 2; + args.Multiplier *= component.GetStrippingTimeMultiplier(); } private void OnSpeakAttempt(EntityUid uid, MobStateComponent component, SpeakAttemptEvent args) @@ -205,57 +200,55 @@ public partial class MobStateSystem return; } - if (component.CurrentState is MobState.Alive - || component.CurrentState is MobState.Critical - && component.AllowTalkingWhileCrit - && _configurationManager.GetCVar(CCVars.AllowTalkingWhileCrit) - || component.CurrentState is MobState.SoftCritical - && component.AllowTalkingWhileSoftCrit - || component.CurrentState is MobState.Dead - && component.AllowTalkingWhileDead) + if (component.CanTalk()) return; args.Cancel(); } - private void CheckAct(EntityUid target, MobStateComponent component, CancellableEntityEventArgs args) + /// + /// anti-boilerplate. + /// Cancels the event if predicate returns false. + /// + private ComponentEventHandler CheckActFactory(Func predicate) { - switch (component.CurrentState) + return (EntityUid uid, MobStateComponent component, CancellableEntityEventArgs args) => { - case MobState.Dead: - case MobState.SoftCritical: - case MobState.Critical: + if (!predicate(component)) args.Cancel(); - break; - } + }; } - private void OnEquipAttempt(EntityUid target, MobStateComponent component, IsEquippingAttemptEvent args) + //private void CheckAct(EntityUid target, MobStateComponent component, CancellableEntityEventArgs args) + //{ + // if (!CanHandInteract(target, component)) + // args.Cancel(); + //} + + private void OnEquipAttempt(EntityUid uid, MobStateComponent component, IsEquippingAttemptEvent args) { // is this a self-equip, or are they being stripped? - if (args.Equipee == target) - CheckAct(target, component, args); + if (uid == args.Equipee) + if (uid == args.EquipTarget && !component.CanEquipSelf() || + uid != args.EquipTarget && !component.CanEquipOther()) + args.Cancel(); } - private void OnUnequipAttempt(EntityUid target, MobStateComponent component, IsUnequippingAttemptEvent args) + private void OnUnequipAttempt(EntityUid uid, MobStateComponent component, IsUnequippingAttemptEvent args) { // is this a self-equip, or are they being stripped? - if (args.Unequipee == target) - CheckAct(target, component, args); + if (uid == args.Unequipee) + if (uid == args.UnEquipTarget && !component.CanUnequipSelf() || + uid != args.UnEquipTarget && !component.CanUnequipOther()) + args.Cancel(); } private void OnCombatModeShouldHandInteract(EntityUid uid, MobStateComponent component, ref CombatModeShouldHandInteractEvent args) { - if (component.CurrentState is MobState.Alive - || component.CurrentState is MobState.Critical - && component.AllowHandInteractWhileCrit - || component.CurrentState is MobState.SoftCritical - && component.AllowHandInteractWhileSoftCrit - || component.CurrentState is MobState.Dead - && component.AllowHandInteractWhileDead) - return; - - args.Cancelled = true; + // Disallow empty-hand-interacting in combat mode + // for non-dead mobs + if (!IsDead(uid, component)) + args.Cancelled = true; } private void OnAttemptPacifiedAttack(Entity ent, ref AttemptPacifiedAttackEvent args) diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.cs b/Content.Shared/Mobs/Systems/MobStateSystem.cs index 9cdd055c15..61a9993ed9 100644 --- a/Content.Shared/Mobs/Systems/MobStateSystem.cs +++ b/Content.Shared/Mobs/Systems/MobStateSystem.cs @@ -49,12 +49,34 @@ public partial class MobStateSystem : EntitySystem /// The MobState component owned by the target /// If the entity is Critical public bool IsCritical(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CurrentState.IsCrit(); + } + + public bool IsCriticalOrDead(EntityUid target, MobStateComponent? component = null) => IsCriticalOrDead(target, true, component); + public bool IsCriticalOrDead(EntityUid target, bool countSoftCrit = true, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CurrentState.IsCritOrDead(countSoftCrit); + } + + public bool IsHardCritical(EntityUid target, MobStateComponent? component = null) { if (!Resolve(target, ref component, false)) return false; return component.CurrentState == MobState.Critical; } + public bool IsSoftCritical(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CurrentState == MobState.SoftCritical; + } + /// /// Check if a Mob is Dead /// @@ -69,26 +91,8 @@ public partial class MobStateSystem : EntitySystem return component.CurrentState == MobState.Dead; } - public bool IsSoftCritical(EntityUid target, MobStateComponent? component = null) - { - if (!Resolve(target, ref component, false)) - return false; - return component.CurrentState == MobState.SoftCritical; - } - /// - /// Check if a Mob is Critical or Dead or SoftCrit - /// - /// Target Entity - /// The MobState component owned by the target - /// If the entity is Critical or Dead - public bool IsIncapacitated(EntityUid target, MobStateComponent? component = null) - { - if (!Resolve(target, ref component, false)) - return false; - return component.CurrentState is MobState.Critical or MobState.Dead or MobState.SoftCritical; - } /// /// Check if a Mob is in an Invalid state @@ -97,12 +101,158 @@ public partial class MobStateSystem : EntitySystem /// The MobState component owned by the target /// If the entity is in an Invalid State public bool IsInvalidState(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, true)) // i think this one should log it + return false; + return component.CurrentState.IsValid(); + } + + + public bool CanMove(EntityUid target, MobStateComponent? component = null) { if (!Resolve(target, ref component, false)) return false; - return component.CurrentState is MobState.Invalid; + return component.CanMove(); } + public bool CanSpeak(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanTalk(); + } + + public bool CanEmote(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanEmote(); + } + + public bool CanThrow(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanThrow(); + } + + public bool CanPickUp(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanPickUp(); + } + + public bool CanPull(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanPull(); + } + + public bool CanAttack(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanAttack(); + } + + public bool CanUse(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanUse(); + } + + public bool CanPoint(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanPoint(); + } + + public bool ConsciousAttemptAllowed(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.IsConscious(); + } + + public bool IsDown(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.IsDowned(); + } + + public bool IsConscious(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.IsConscious(); + } + + + /// + /// Check if a Mob is incapacitated in its current MobState. + /// + /// Target Entity + /// The MobState component owned by the target + /// If the entity is Critical or Dead + public bool IsIncapacitated(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.IsIncapacitated(); + } + + public bool CanEquipSelf(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanEquipSelf(); + } + + public bool CanUnequipSelf(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanUnequipSelf(); + } + + public bool CanEquipOther(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanEquipOther(); + } + + public bool CanUnequipOther(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.CanUnequipOther(); + } + + + + public bool IsThreatening(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return false; + return component.IsThreatening(); + } + /// + /// Clamped to [0,1]. Use to get unclamped value. + /// + public float BreatheMultiplier(EntityUid target, MobStateComponent? component = null) + { + if (!Resolve(target, ref component, false)) + return 0f; + return Math.Clamp(component.GetBreathingMultiplier(), 0, 1); + } + + #endregion #region Private Implementation diff --git a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs index a5307c4ba5..624dc71122 100644 --- a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs +++ b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs @@ -136,6 +136,20 @@ public sealed class MobThresholdSystem : EntitySystem return false; } + // WD EDIT START + public bool TryGetThresholdForState(EntityUid target, MobState mobState, MobState fallbackMobState, + [NotNullWhen(true)] out FixedPoint2? threshold, + MobThresholdsComponent? thresholdComponent = null) + { + threshold = null; + if (TryGetThresholdForState(target, mobState, out threshold, thresholdComponent)) + return true; + if (TryGetThresholdForState(target, fallbackMobState, out threshold, thresholdComponent)) + return true; + return false; + } + // WD EDIT END + /// /// Try to get the a percentage of the Damage Threshold for the appropriate state if it exists /// @@ -436,11 +450,11 @@ public sealed class MobThresholdSystem : EntitySystem private void OnUpdateMobState(EntityUid target, MobThresholdsComponent component, ref UpdateMobStateEvent args) { - if (!component.AllowRevives && component.CurrentThresholdState == MobState.Dead) + if (!component.AllowRevives && component.CurrentThresholdState.IsDead()) { args.State = MobState.Dead; } - else if (component.CurrentThresholdState != MobState.Invalid) + else if (component.CurrentThresholdState.IsValid()) { args.State = component.CurrentThresholdState; } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index bb6951f2de..fb785c7564 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -311,8 +311,7 @@ namespace Content.Shared.Movement.Systems if (MoverQuery.TryGetComponent(entity, out var mover)) SetMoveInput((entity, mover), MoveButtons.None); - if (_mobState.IsDead(entity) - || _mobState.IsCritical(entity) && !_configManager.GetCVar(CCVars.AllowMovementWhileCrit)) + if (!_mobState.CanMove(entity)) return; HandleDirChange(relayMover.RelayEntity, dir, subTick, state); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 701f32f0c7..6aae9c26dd 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -128,7 +128,7 @@ namespace Content.Shared.Movement.Systems if (_mobState.IsDead(relayTarget.Source) || TryComp(relayTarget.Source, out _) || !MoverQuery.TryGetComponent(relayTarget.Source, out var relayedMover) - || _mobState.IsCritical(relayTarget.Source) && !_configManager.GetCVar(CCVars.AllowMovementWhileCrit)) + || _mobState.CanMove(uid)) { canMove = false; } diff --git a/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs b/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs index 9b7a6954de..0f66dff309 100644 --- a/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs +++ b/Content.Shared/Nyanotrasen/Interaction/NoNormalInteractionSystem.cs @@ -12,7 +12,7 @@ namespace Content.Shared.Interaction private void OnInteractionAttempt(EntityUid uid, NoNormalInteractionComponent component, InteractionAttemptEvent args) { - args.Cancelled = true; + args.Cancel(); } } } diff --git a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs index 448311471f..7dc8578117 100644 --- a/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs +++ b/Content.Shared/Nyanotrasen/Item/PseudoItem/SharedPseudoItemSystem.cs @@ -143,7 +143,7 @@ public abstract partial class SharedPseudoItemSystem : EntitySystem private void OnInteractAttempt(EntityUid uid, PseudoItemComponent component, InteractionAttemptEvent args) { if (args.Uid == args.Target && component.Active) - args.Cancelled = true; + args.Cancel(); } private void OnDoAfter(EntityUid uid, PseudoItemComponent component, DoAfterEvent args) diff --git a/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs b/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs index e3fa21ed37..0ea15f944e 100644 --- a/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs +++ b/Content.Shared/Puppet/SharedVentriloquistPuppetSystem.cs @@ -27,7 +27,7 @@ public abstract class SharedVentriloquistPuppetSystem : EntitySystem private void CancelInteract(Entity ent, ref InteractionAttemptEvent args) { - args.Cancelled = true; + args.Cancel(); } private void OnStartup(EntityUid uid, VentriloquistPuppetComponent component, ComponentStartup args) diff --git a/Content.Shared/Shadowkin/SharedEtherealSystem.cs b/Content.Shared/Shadowkin/SharedEtherealSystem.cs index 6d6dd3daa2..55191f9bbf 100644 --- a/Content.Shared/Shadowkin/SharedEtherealSystem.cs +++ b/Content.Shared/Shadowkin/SharedEtherealSystem.cs @@ -17,6 +17,7 @@ using Content.Shared.Tag; using Content.Shared.Damage.Components; using Content.Shared.Damage.Systems; using Content.Shared.Standing; +using Content.Shared.Mobs.Systems; namespace Content.Shared.Shadowkin; @@ -30,6 +31,7 @@ public abstract class SharedEtherealSystem : EntitySystem [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly StaminaSystem _stamina = default!; [Dependency] private readonly StandingStateSystem _standingState = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; public override void Initialize() { @@ -95,8 +97,7 @@ public abstract class SharedEtherealSystem : EntitySystem private void OnMobStateChanged(EntityUid uid, EtherealComponent component, MobStateChangedEvent args) { - if (args.NewMobState == MobState.Critical - || args.NewMobState == MobState.Dead) + if (_mobState.IsIncapacitated(uid)) { SpawnAtPosition("ShadowkinShadow", Transform(uid).Coordinates); SpawnAtPosition("EffectFlashShadowkinDarkSwapOff", Transform(uid).Coordinates); @@ -137,7 +138,7 @@ public abstract class SharedEtherealSystem : EntitySystem || HasComp(args.Target)) return; - args.Cancelled = true; + args.Cancel(); if (_gameTiming.InPrediction) return; diff --git a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs index ca54b5a853..e8c65a67be 100644 --- a/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs +++ b/Content.Shared/Silicons/StationAi/SharedStationAiSystem.Held.cs @@ -140,11 +140,15 @@ public abstract partial class SharedStationAiSystem private void OnHeldInteraction(Entity ent, ref InteractionAttemptEvent args) { // Cancel if it's not us or something with a whitelist, or whitelist is disabled. - args.Cancelled = (!TryComp(args.Target, out StationAiWhitelistComponent? whitelistComponent) - || !whitelistComponent.Enabled) - && ent.Owner != args.Target - && args.Target != null; - if (whitelistComponent is { Enabled: false }) + if ((!TryComp(args.Target, out var whitelistComponent) + || !whitelistComponent.Enabled) + && ent.Owner != args.Target + && args.Target != null) + { + args.Cancel(); + } + + if (whitelistComponent is {Enabled: false }) { ShowDeviceNotRespondingPopup(ent.Owner); } diff --git a/Content.Shared/Standing/SharedLayingDownSystem.cs b/Content.Shared/Standing/SharedLayingDownSystem.cs index ec5f2a6d66..4e5dde3776 100644 --- a/Content.Shared/Standing/SharedLayingDownSystem.cs +++ b/Content.Shared/Standing/SharedLayingDownSystem.cs @@ -102,8 +102,8 @@ public abstract class SharedLayingDownSystem : EntitySystem RaiseNetworkEvent(new CheckAutoGetUpEvent(GetNetEntity(uid))); - if (HasComp(uid) - || !_mobState.IsAlive(uid)) + if (HasComp(uid)) + //|| _mobState.IsDown(uid)) return; if (_standing.IsDown(uid, standing)) @@ -116,7 +116,7 @@ public abstract class SharedLayingDownSystem : EntitySystem { if (args.Handled || args.Cancelled || HasComp(uid) - || _mobState.IsIncapacitated(uid) + || _mobState.IsDown(uid) || !_standing.Stand(uid)) component.CurrentState = StandingState.Lying; @@ -153,7 +153,7 @@ public abstract class SharedLayingDownSystem : EntitySystem if (!Resolve(uid, ref standingState, false) || !Resolve(uid, ref layingDown, false) || standingState.CurrentState is not StandingState.Lying - || !_mobState.IsAlive(uid) + || _mobState.IsDown(uid) || TerminatingOrDeleted(uid) || !TryComp(uid, out var body) || body.LegEntities.Count < body.RequiredLegs diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index f82a3cc745..217790c410 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -81,7 +81,7 @@ public abstract class SharedStunSystem : EntitySystem private void OnAttemptInteract(Entity ent, ref InteractionAttemptEvent args) { - args.Cancelled = true; + args.Cancel(); } private void OnMobStateChanged(EntityUid uid, MobStateComponent component, MobStateChangedEvent args) @@ -93,6 +93,7 @@ public abstract class SharedStunSystem : EntitySystem { case MobState.Alive: break; + // case MobState.SoftCritical: case MobState.Critical: { _statusEffect.TryRemoveStatusEffect(uid, "Stun"); diff --git a/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs b/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs index cebc84ecb9..1cca016dc7 100644 --- a/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs +++ b/Content.Shared/SubFloor/SharedSubFloorHideSystem.cs @@ -49,7 +49,7 @@ namespace Content.Shared.SubFloor { // No interactions with entities hidden under floor tiles. if (component.BlockInteractions && component.IsUnderCover) - args.Cancelled = true; + args.Cancel(); } private void OnSubFloorStarted(EntityUid uid, SubFloorHideComponent component, ComponentStartup _) diff --git a/Content.Shared/Traits/Assorted/Components/CritModifierComponent.cs b/Content.Shared/Traits/Assorted/Components/CritModifierComponent.cs index 6cf11e3f7f..4cd301e5c0 100644 --- a/Content.Shared/Traits/Assorted/Components/CritModifierComponent.cs +++ b/Content.Shared/Traits/Assorted/Components/CritModifierComponent.cs @@ -13,4 +13,12 @@ public sealed partial class CritModifierComponent : Component /// [DataField] public int CritThresholdModifier { get; private set; } = 0; -} \ No newline at end of file + + // WD EDIT START + /// + /// The amount that an entity's softcrit threshold will be incremented by. + /// + [DataField] + public int SoftCritThresholdModifier { get; private set; } = 0; + // WD EDIT END +} diff --git a/Content.Shared/Traits/Assorted/Systems/TraitStatModifierSystem.cs b/Content.Shared/Traits/Assorted/Systems/TraitStatModifierSystem.cs index 97b88f559e..e0a6133048 100644 --- a/Content.Shared/Traits/Assorted/Systems/TraitStatModifierSystem.cs +++ b/Content.Shared/Traits/Assorted/Systems/TraitStatModifierSystem.cs @@ -29,9 +29,11 @@ public sealed partial class TraitStatModifierSystem : EntitySystem if (!TryComp(uid, out var threshold)) return; - var critThreshold = _threshold.GetThresholdForState(uid, Mobs.MobState.Critical, threshold); - if (critThreshold != 0) - _threshold.SetMobStateThreshold(uid, critThreshold + component.CritThresholdModifier, Mobs.MobState.Critical); + if(_threshold.TryGetThresholdForState(uid, Mobs.MobState.Critical, out var critThreshold)) + _threshold.SetMobStateThreshold(uid, critThreshold.Value + component.CritThresholdModifier, Mobs.MobState.Critical); + + if(_threshold.TryGetThresholdForState(uid, Mobs.MobState.SoftCritical, out var softCritThreshold)) + _threshold.SetMobStateThreshold(uid, softCritThreshold.Value + component.SoftCritThresholdModifier, Mobs.MobState.SoftCritical); } private void OnDeadStartup(EntityUid uid, DeadModifierComponent component, ComponentStartup args) diff --git a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs index 3d7f9df458..ece7731254 100644 --- a/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs +++ b/Content.Shared/Weapons/Misc/SharedTetherGunSystem.cs @@ -69,7 +69,7 @@ public abstract partial class SharedTetherGunSystem : EntitySystem private void OnTetheredBuckleAttempt(EntityUid uid, TetheredComponent component, ref BuckleAttemptEvent args) { - args.Cancelled = true; + args.Cancel(); } private void OnTetheredUpdateCanMove(EntityUid uid, TetheredComponent component, UpdateCanMoveEvent args) diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index a5c3065c4d..cbfb52a0be 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -92,10 +92,16 @@ - type: RadiationReceiver - type: Stamina - type: MobState + mobStateParams: + Alive: AliveDefault + SoftCritical: SoftCriticalDefault + Critical: CriticalDefault + Dead: DeadDefault - type: MobThresholds thresholds: 0: Alive - 100: Critical + 100: SoftCritical + 130: Critical 200: Dead - type: MobStateActions actions: diff --git a/Resources/Prototypes/NPCs/utility_queries.yml b/Resources/Prototypes/NPCs/utility_queries.yml index 3d5385ecbc..9aad83f096 100644 --- a/Resources/Prototypes/NPCs/utility_queries.yml +++ b/Resources/Prototypes/NPCs/utility_queries.yml @@ -86,7 +86,7 @@ query: - !type:NearbyHostilesQuery considerations: - - !type:TargetIsAliveCon + - !type:TargetIsThreateningCon curve: !type:BoolCurve - !type:TargetDistanceCon curve: !type:PresetCurve @@ -104,7 +104,7 @@ query: - !type:NearbyHostilesQuery considerations: - - !type:TargetIsAliveCon + - !type:TargetIsThreateningCon curve: !type:BoolCurve - !type:TargetDistanceCon curve: !type:PresetCurve @@ -164,7 +164,7 @@ query: - !type:NearbyHostilesQuery considerations: - - !type:TargetIsAliveCon + - !type:TargetIsThreateningCon curve: !type:BoolCurve - !type:TargetDistanceCon curve: !type:PresetCurve diff --git a/Resources/Prototypes/mobStateParams/default.yml b/Resources/Prototypes/mobStateParams/default.yml new file mode 100644 index 0000000000..fc0384dd4c --- /dev/null +++ b/Resources/Prototypes/mobStateParams/default.yml @@ -0,0 +1,131 @@ +#moving +#talking +#emoting +#throwing +#pickingUp - Allows picking items up or dropping them. +#pulling +#attacking +#using - Blocks UseAttemptEvent if false. +#pointing +#isConscious - Used to check if a mob in this state is to be considered "conscious". Effects may differ based on how entity systems interpret this value. +#canEquipSelf +#canUnequipSelf +#canEquipOther +#canUnequipOther +#forceDown - Forces mob in this state to be down. (i.e. crawling) +#dropItemsOnEntering - Forces mob to drop held items when entering this state. +#incapacitated - Blocks InteractionAttemptEvents if true. Effects may differ based on how entity systems interpret this value. +#threatening - Used in mob AI. If false, will not be considered as a target by hostile AI. +#breathingMultiplier - Fraction of Atmospherics.BreathVolume this mob will take, with 1 being full, 0.5 being half, 0 being none at all. +# Also modifies the amount of damage the mob will take when suffocating, with the inverse of this value. (1 / breathingMultiplier). +# Clamped to [0,1]. +#oxyDamageOverlay - If set, controls the black vingette that is used for crit blackout. If not, overlay is drawn depending on current oxy damage.. +#strippingTimeMultiplier - How fast this mob can be stripped by other mobs. Less is faster. + + +- type: mobStateParams + id: AliveDefault + moving: true + talking: true + emoting: true + throwing: true + pickingUp: true + pulling: true + attacking: true + using: true + pointing: true + isConscious: true + threatening: true + canEquipSelf: true + canUnequipSelf: true + canEquipOther: true + canUnequipOther: true + #forceDown: false # both default to false if not specified + #incapacitated: false # + #breathingMultiplier: 1 # defaults to 1 if not specified + +- type: mobStateParams + id: SoftCriticalDefault + moving: true + talking: true + emoting: true + throwing: false + pickingUp: false + pulling: false + attacking: false + using: true + pointing: true + isConscious: true + threatening: true + canEquipSelf: false + canUnequipSelf: false + canEquipOther: false + canUnequipOther: false + forceDown: true + dropItemsOnEntering: true + incapacitated: true + breathingMultiplier: 0.25 # This should probably be handled by lungs instead. + oxyDamageOverlay: 0.75 + strippingTimeMultiplier: 0.5 + + +- type: mobStateParams + id: CriticalDefault + moving: false + talking: false + emoting: false + throwing: false + pickingUp: false + pulling: false + attacking: false + using: false + pointing: false + isConscious: false + threatening: false + canEquipSelf: false + canUnequipSelf: false + canEquipOther: false + canUnequipOther: false + forceDown: true + dropItemsOnEntering: true + incapacitated: true + breathingMultiplier: 0 + oxyDamageOverlay: 1 + strippingTimeMultiplier: 0.33 + +- type: mobStateParams + id: DeadDefault + moving: false + talking: false + emoting: false + throwing: false + pickingUp: false + pulling: false + attacking: false + using: false + pointing: false + isConscious: false + threatening: false + canEquipSelf: false + canUnequipSelf: false + canEquipOther: false + canUnequipOther: false + forceDown: true + dropItemsOnEntering: true + incapacitated: true + breathingMultiplier: 0 + oxyDamageOverlay: 0 + strippingTimeMultiplier: 0.33 + +- type: mobStateParams + id: DeadGripMod + dropItemsOnEntering: false + +- type: mobStateParams + id: AuxiliaryDiaphragmSoftCrit + breathingMultiplier: 0.75 + +- type: mobStateParams + id: AuxiliaryDiaphragmHardCrit + breathingMultiplier: 0.25 +