From 1951b00875b15968ccc77bdbad4db752ec0c29fd Mon Sep 17 00:00:00 2001
From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com>
Date: Mon, 3 Aug 2026 03:44:01 +0200
Subject: [PATCH] Always raise MobStateChanged on client (#45103)
* lets try this
* sub attribute
---
.../Mobs/Components/MobStateComponent.cs | 52 +++++++++++--------
.../Systems/MobStateSystem.StateMachine.cs | 2 +-
.../Systems/MobStateSystem.Subscribers.cs | 39 ++++++++------
Content.Shared/Mobs/Systems/MobStateSystem.cs | 2 -
4 files changed, 54 insertions(+), 41 deletions(-)
diff --git a/Content.Shared/Mobs/Components/MobStateComponent.cs b/Content.Shared/Mobs/Components/MobStateComponent.cs
index 80a0674c96..29cf0fb5ad 100644
--- a/Content.Shared/Mobs/Components/MobStateComponent.cs
+++ b/Content.Shared/Mobs/Components/MobStateComponent.cs
@@ -2,31 +2,37 @@ using Content.Shared.Damage.Components;
using Content.Shared.Mobs.Systems;
using Robust.Shared.GameStates;
-namespace Content.Shared.Mobs.Components
+namespace Content.Shared.Mobs.Components;
+
+///
+/// When attached to an ,
+/// this component will handle critical and death behaviors for mobs.
+/// Additionally, it handles sending effects to clients
+/// (such as blur effect for unconsciousness) and managing the health HUD.
+///
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState(true)]
+[Access(typeof(MobStateSystem), typeof(MobThresholdSystem))]
+public sealed partial class MobStateComponent : Component
{
///
- /// When attached to an ,
- /// this component will handle critical and death behaviors for mobs.
- /// Additionally, it handles sending effects to clients
- /// (such as blur effect for unconsciousness) and managing the health HUD.
+ /// The current mob state the entity is in.
///
- [RegisterComponent]
- [NetworkedComponent]
- [AutoGenerateComponentState]
- [Access(typeof(MobStateSystem), typeof(MobThresholdSystem))]
- public sealed partial class MobStateComponent : Component
- {
- //default mobstate is always the lowest state level
- [AutoNetworkedField, ViewVariables]
- public MobState CurrentState { get; set; } = MobState.Alive;
+ [DataField, AutoNetworkedField]
+ public MobState CurrentState = MobState.Alive; //default mobstate is always the lowest state level
- [DataField]
- [AutoNetworkedField]
- public HashSet AllowedStates = new()
- {
- MobState.Alive,
- MobState.Critical,
- MobState.Dead
- };
- }
+ ///
+ /// The last state that was received by the client
+ ///
+ [ViewVariables]
+ public MobState LastReceivedState = MobState.Alive;
+
+ [DataField]
+ [AutoNetworkedField]
+ public HashSet AllowedStates = new()
+ {
+ MobState.Alive,
+ MobState.Critical,
+ MobState.Dead
+ };
}
diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs
index b389d67275..31942e57ac 100644
--- a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs
+++ b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs
@@ -33,7 +33,7 @@ public partial class MobStateSystem
if (!_mobStateQuery.Resolve(entity, ref component))
return;
- var ev = new UpdateMobStateEvent {Target = entity, Component = component, Origin = origin};
+ var ev = new UpdateMobStateEvent { Target = entity, Component = component, Origin = origin };
RaiseLocalEvent(entity, ref ev);
ChangeState(entity, component, ev.State, origin: origin);
}
diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs
index dcc9871af4..c876fb30ed 100644
--- a/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs
+++ b/Content.Shared/Mobs/Systems/MobStateSystem.Subscribers.cs
@@ -28,34 +28,20 @@ public partial class MobStateSystem
//General purpose event subscriptions. If you can avoid it register these events inside their own systems
private void SubscribeEvents()
{
- SubscribeLocalEvent(OnGettingStripped);
SubscribeLocalEvent(CheckAct);
SubscribeLocalEvent(CheckAct);
SubscribeLocalEvent(CheckAct);
- SubscribeLocalEvent(CheckConcious);
SubscribeLocalEvent(CheckAct);
- SubscribeLocalEvent(OnSpeakAttempt);
- SubscribeLocalEvent(OnEquipAttempt);
SubscribeLocalEvent(CheckAct);
- SubscribeLocalEvent(OnUnequipAttempt);
SubscribeLocalEvent(CheckAct);
SubscribeLocalEvent(CheckAct);
SubscribeLocalEvent(CheckAct);
SubscribeLocalEvent(CheckAct);
SubscribeLocalEvent(CheckAct);
SubscribeLocalEvent(CheckAct);
- SubscribeLocalEvent(OnSleepAttempt);
- SubscribeLocalEvent(OnCombatModeShouldHandInteract);
- SubscribeLocalEvent(OnAttemptPacifiedAttack);
- SubscribeLocalEvent(OnDamageModify);
- SubscribeLocalEvent(OnAttemptToolRefine);
-
- SubscribeLocalEvent(OnUnbuckleAttempt);
-
- // Actions
- SubscribeLocalEvent(OnMobStateActionAttempt);
}
+ [SubscribeLocalEvent]
private void OnUnbuckleAttempt(Entity ent, ref UnbuckleAttemptEvent args)
{
// TODO is this necessary?
@@ -71,6 +57,7 @@ public partial class MobStateSystem
RaiseLocalEvent(target, ref ev);
}
+ [SubscribeLocalEvent]
private void CheckConcious(Entity ent, ref ConsciousAttemptEvent args)
{
switch (ent.Comp.CurrentState)
@@ -145,6 +132,7 @@ public partial class MobStateSystem
}
}
+ [SubscribeLocalEvent]
private void OnAttemptToolRefine(Entity ent, ref AttemptToolRefineEvent args)
{
if (!IsDead(ent, ent))
@@ -155,12 +143,26 @@ public partial class MobStateSystem
#region Event Subscribers
+ [SubscribeLocalEvent]
+ private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ if (ent.Comp.LastReceivedState == ent.Comp.CurrentState)
+ return;
+
+ var ev = new MobStateChangedEvent(ent, ent.Comp, ent.Comp.LastReceivedState, ent.Comp.CurrentState);
+ OnStateChanged(ent, ent.Comp, ent.Comp.LastReceivedState, ent.Comp.CurrentState);
+ RaiseLocalEvent(ent, ev, true);
+ ent.Comp.LastReceivedState = ent.Comp.CurrentState;
+ }
+
+ [SubscribeLocalEvent]
private void OnSleepAttempt(EntityUid target, MobStateComponent component, ref TryingToSleepEvent args)
{
if (IsDead(target, component))
args.Cancelled = true;
}
+ [SubscribeLocalEvent]
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.
@@ -170,6 +172,7 @@ public partial class MobStateSystem
args.Multiplier /= 2;
}
+ [SubscribeLocalEvent]
private void OnSpeakAttempt(EntityUid uid, MobStateComponent component, SpeakAttemptEvent args)
{
if (HasComp(uid))
@@ -192,6 +195,7 @@ public partial class MobStateSystem
}
}
+ [SubscribeLocalEvent]
private void OnEquipAttempt(EntityUid target, MobStateComponent component, IsEquippingAttemptEvent args)
{
// is this a self-equip, or are they being stripped?
@@ -199,6 +203,7 @@ public partial class MobStateSystem
CheckAct(target, component, args);
}
+ [SubscribeLocalEvent]
private void OnUnequipAttempt(EntityUid target, MobStateComponent component, IsUnequippingAttemptEvent args)
{
// is this a self-equip, or are they being stripped?
@@ -206,6 +211,7 @@ public partial class MobStateSystem
CheckAct(target, component, args);
}
+ [SubscribeLocalEvent]
private void OnCombatModeShouldHandInteract(EntityUid uid, MobStateComponent component, ref CombatModeShouldHandInteractEvent args)
{
// Disallow empty-hand-interacting in combat mode
@@ -214,16 +220,19 @@ public partial class MobStateSystem
args.Cancelled = true;
}
+ [SubscribeLocalEvent]
private void OnAttemptPacifiedAttack(Entity ent, ref AttemptPacifiedAttackEvent args)
{
args.Cancelled = true;
}
+ [SubscribeLocalEvent]
private void OnDamageModify(Entity ent, ref DamageModifyEvent args)
{
args.Damage *= _damageable.UniversalMobDamageModifier;
}
+ [SubscribeLocalEvent]
private void OnMobStateActionAttempt(Entity ent, ref ActionAttemptEvent args)
{
if (_mobStateQuery.TryComp(args.User, out var mobState) &&
diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.cs b/Content.Shared/Mobs/Systems/MobStateSystem.cs
index e5b516b54f..6fb08ba7a0 100644
--- a/Content.Shared/Mobs/Systems/MobStateSystem.cs
+++ b/Content.Shared/Mobs/Systems/MobStateSystem.cs
@@ -18,13 +18,11 @@ public partial class MobStateSystem : EntitySystem
[Dependency] private IGameTiming _timing = default!;
[Dependency] private DamageableSystem _damageable = default!;
[Dependency] private SharedPopupSystem _popup = default!;
- private ISawmill _sawmill = default!;
[Dependency] private EntityQuery _mobStateQuery = default!;
public override void Initialize()
{
- _sawmill = LogManager.GetSawmill("MobState");
base.Initialize();
SubscribeEvents();
}