diff --git a/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs
new file mode 100644
index 00000000000000..1700796edea888
--- /dev/null
+++ b/Content.Client/Movement/Systems/ClientSpriteMovementSystem.cs
@@ -0,0 +1,46 @@
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+using Robust.Client.GameObjects;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Movement.Systems;
+
+///
+/// Controls the switching of motion and standing still animation
+///
+public sealed class ClientSpriteMovementSystem : SharedSpriteMovementSystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private EntityQuery _spriteQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _spriteQuery = GetEntityQuery();
+
+ SubscribeLocalEvent(OnAfterAutoHandleState);
+ }
+
+ private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ if (!_spriteQuery.TryGetComponent(ent, out var sprite))
+ return;
+
+ if (ent.Comp.IsMoving)
+ {
+ foreach (var (layer, state) in ent.Comp.MovementLayers)
+ {
+ sprite.LayerSetData(layer, state);
+ }
+ }
+ else
+ {
+ foreach (var (layer, state) in ent.Comp.NoMovementLayers)
+ {
+ sprite.LayerSetData(layer, state);
+ }
+ }
+ }
+}
diff --git a/Content.Client/Movement/Systems/SpriteMovementSystem.cs b/Content.Client/Movement/Systems/SpriteMovementSystem.cs
deleted file mode 100644
index b6203536896dc0..00000000000000
--- a/Content.Client/Movement/Systems/SpriteMovementSystem.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using Content.Shared.Movement.Components;
-using Content.Shared.Movement.Events;
-using Content.Shared.Movement.Systems;
-using Robust.Client.GameObjects;
-using Robust.Shared.Timing;
-
-namespace Content.Client.Movement.Systems;
-
-///
-/// Handles setting sprite states based on whether an entity has movement input.
-///
-public sealed class SpriteMovementSystem : EntitySystem
-{
- [Dependency] private readonly IGameTiming _timing = default!;
-
- private EntityQuery _spriteQuery;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent(OnSpriteMoveInput);
- _spriteQuery = GetEntityQuery();
- }
-
- private void OnSpriteMoveInput(EntityUid uid, SpriteMovementComponent component, ref MoveInputEvent args)
- {
- if (!_timing.IsFirstTimePredicted)
- return;
-
- var oldMoving = (SharedMoverController.GetNormalizedMovement(args.OldMovement) & MoveButtons.AnyDirection) != MoveButtons.None;
- var moving = (SharedMoverController.GetNormalizedMovement(args.Entity.Comp.HeldMoveButtons) & MoveButtons.AnyDirection) != MoveButtons.None;
-
- if (oldMoving == moving || !_spriteQuery.TryGetComponent(uid, out var sprite))
- return;
-
- if (moving)
- {
- foreach (var (layer, state) in component.MovementLayers)
- {
- sprite.LayerSetData(layer, state);
- }
- }
- else
- {
- foreach (var (layer, state) in component.NoMovementLayers)
- {
- sprite.LayerSetData(layer, state);
- }
- }
- }
-}
diff --git a/Content.Server/Movement/Systems/SpriteMovementSystem.cs b/Content.Server/Movement/Systems/SpriteMovementSystem.cs
new file mode 100644
index 00000000000000..9fc4d828598da8
--- /dev/null
+++ b/Content.Server/Movement/Systems/SpriteMovementSystem.cs
@@ -0,0 +1,7 @@
+using Content.Shared.Movement.Systems;
+
+namespace Content.Server.Movement.Systems;
+
+public sealed class SpriteMovementSystem : SharedSpriteMovementSystem
+{
+}
diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs
index fa43b3e7524b59..6a295198c2572e 100644
--- a/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs
+++ b/Content.Server/NPC/Systems/NPCSteeringSystem.Context.cs
@@ -253,7 +253,7 @@ private bool TrySeek(
if (!targetCoordinates.IsValid(EntityManager))
{
- SetDirection(mover, steering, Vector2.Zero);
+ SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath;
return false;
}
@@ -263,7 +263,7 @@ private bool TrySeek(
// Can't make it again.
if (ourMap.MapId != targetMap.MapId)
{
- SetDirection(mover, steering, Vector2.Zero);
+ SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath;
return false;
}
diff --git a/Content.Server/NPC/Systems/NPCSteeringSystem.cs b/Content.Server/NPC/Systems/NPCSteeringSystem.cs
index fc63d1e6156a5d..a8124c02493a5f 100644
--- a/Content.Server/NPC/Systems/NPCSteeringSystem.cs
+++ b/Content.Server/NPC/Systems/NPCSteeringSystem.cs
@@ -11,6 +11,7 @@
using Content.Shared.CombatMode;
using Content.Shared.Interaction;
using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.NPC;
using Content.Shared.NPC.Components;
@@ -207,6 +208,9 @@ public void Unregister(EntityUid uid, NPCSteeringComponent? component = null)
if (EntityManager.TryGetComponent(uid, out InputMoverComponent? controller))
{
controller.CurTickSprintMovement = Vector2.Zero;
+
+ var ev = new SpriteMoveEvent(false);
+ RaiseLocalEvent(uid, ref ev);
}
component.PathfindToken?.Cancel();
@@ -270,7 +274,7 @@ public override void Update(float frameTime)
}
}
- private void SetDirection(InputMoverComponent component, NPCSteeringComponent steering, Vector2 value, bool clear = true)
+ private void SetDirection(EntityUid uid, InputMoverComponent component, NPCSteeringComponent steering, Vector2 value, bool clear = true)
{
if (clear && value.Equals(Vector2.Zero))
{
@@ -282,6 +286,9 @@ private void SetDirection(InputMoverComponent component, NPCSteeringComponent st
component.CurTickSprintMovement = value;
component.LastInputTick = _timing.CurTick;
component.LastInputSubTick = ushort.MaxValue;
+
+ var ev = new SpriteMoveEvent(true);
+ RaiseLocalEvent(uid, ref ev);
}
///
@@ -297,7 +304,7 @@ private void Steer(
{
if (Deleted(steering.Coordinates.EntityId))
{
- SetDirection(mover, steering, Vector2.Zero);
+ SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath;
return;
}
@@ -305,14 +312,14 @@ private void Steer(
// No path set from pathfinding or the likes.
if (steering.Status == SteeringStatus.NoPath)
{
- SetDirection(mover, steering, Vector2.Zero);
+ SetDirection(uid, mover, steering, Vector2.Zero);
return;
}
// Can't move at all, just noop input.
if (!mover.CanMove)
{
- SetDirection(mover, steering, Vector2.Zero);
+ SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath;
return;
}
@@ -341,7 +348,7 @@ private void Steer(
if (steering.CanSeek && !TrySeek(uid, mover, steering, body, xform, offsetRot, moveSpeed, interest, frameTime, ref forceSteer))
{
- SetDirection(mover, steering, Vector2.Zero);
+ SetDirection(uid, mover, steering, Vector2.Zero);
return;
}
@@ -354,7 +361,7 @@ private void Steer(
if (!forceSteer)
{
- SetDirection(mover, steering, steering.LastSteerDirection, false);
+ SetDirection(uid, mover, steering, steering.LastSteerDirection, false);
return;
}
@@ -391,7 +398,7 @@ private void Steer(
steering.LastSteerDirection = resultDirection;
DebugTools.Assert(!float.IsNaN(resultDirection.X));
- SetDirection(mover, steering, resultDirection, false);
+ SetDirection(uid, mover, steering, resultDirection, false);
}
private EntityCoordinates GetCoordinates(PathPoly poly)
diff --git a/Content.Shared/Movement/Components/SpriteMovementComponent.cs b/Content.Shared/Movement/Components/SpriteMovementComponent.cs
index 8dd058f15446a8..b5a9e373521521 100644
--- a/Content.Shared/Movement/Components/SpriteMovementComponent.cs
+++ b/Content.Shared/Movement/Components/SpriteMovementComponent.cs
@@ -5,7 +5,7 @@ namespace Content.Shared.Movement.Components;
///
/// Updates a sprite layer based on whether an entity is moving via input or not.
///
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class SpriteMovementComponent : Component
{
///
@@ -19,4 +19,7 @@ public sealed partial class SpriteMovementComponent : Component
///
[DataField]
public Dictionary NoMovementLayers = new();
+
+ [DataField, AutoNetworkedField]
+ public bool IsMoving;
}
diff --git a/Content.Shared/Movement/Events/SpriteMoveEvent.cs b/Content.Shared/Movement/Events/SpriteMoveEvent.cs
new file mode 100644
index 00000000000000..25ebffe2df7505
--- /dev/null
+++ b/Content.Shared/Movement/Events/SpriteMoveEvent.cs
@@ -0,0 +1,15 @@
+namespace Content.Shared.Movement.Events;
+
+///
+/// Raised on an entity whenever it should change movement sprite
+///
+[ByRefEvent]
+public readonly struct SpriteMoveEvent
+{
+ public readonly bool IsMoving = false;
+
+ public SpriteMoveEvent(bool isMoving)
+ {
+ IsMoving = isMoving;
+ }
+}
diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs
index 1fe38b6cdf1b6c..6f508d9038c0fe 100644
--- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs
+++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs
@@ -96,6 +96,9 @@ protected void SetMoveInput(Entity entity, MoveButtons butt
entity.Comp.HeldMoveButtons = buttons;
RaiseLocalEvent(entity, ref moveEvent);
Dirty(entity, entity.Comp);
+
+ var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None);
+ RaiseLocalEvent(entity, ref ev);
}
private void OnMoverHandleState(Entity entity, ref ComponentHandleState args)
@@ -119,6 +122,9 @@ private void OnMoverHandleState(Entity entity, ref Componen
var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons);
entity.Comp.HeldMoveButtons = state.HeldMoveButtons;
RaiseLocalEvent(entity.Owner, ref moveEvent);
+
+ var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None);
+ RaiseLocalEvent(entity, ref ev);
}
}
diff --git a/Content.Shared/Movement/Systems/SharedSpriteMovementSystem.cs b/Content.Shared/Movement/Systems/SharedSpriteMovementSystem.cs
new file mode 100644
index 00000000000000..eb4bbc1be63563
--- /dev/null
+++ b/Content.Shared/Movement/Systems/SharedSpriteMovementSystem.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Events;
+
+namespace Content.Shared.Movement.Systems;
+
+public abstract class SharedSpriteMovementSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSpriteMoveInput);
+ }
+
+ private void OnSpriteMoveInput(Entity ent, ref SpriteMoveEvent args)
+ {
+ if (ent.Comp.IsMoving == args.IsMoving)
+ return;
+
+ ent.Comp.IsMoving = args.IsMoving;
+ Dirty(ent);
+ }
+}
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index d801530c6bc5c7..b776a156fed5f3 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -2283,24 +2283,10 @@
# Code unique spider prototypes or combine them all into one spider and get a
# random sprite state when you spawn it.
- type: entity
- name: tarantula
parent: [ SimpleMobBase, MobCombat ]
- id: MobGiantSpider
- description: Widely recognized to be the literal worst thing in existence.
+ id: MobSpiderBase
+ abstract: true
components:
- - type: Sprite
- drawdepth: Mobs
- layers:
- - map: ["enum.DamageStateVisualLayers.Base", "movement"]
- state: tarantula
- sprite: Mobs/Animals/spider.rsi
- - type: SpriteMovement
- movementLayers:
- movement:
- state: tarantula-moving
- noMovementLayers:
- movement:
- state: tarantula
- type: Physics
- type: Fixtures
fixtures:
@@ -2313,14 +2299,6 @@
- MobMask
layer:
- MobLayer
- - type: DamageStateVisuals
- states:
- Alive:
- Base: tarantula
- Critical:
- Base: tarantula_dead
- Dead:
- Base: tarantula_dead
- type: Butcherable
spawned:
- id: FoodMeatSpider
@@ -2332,15 +2310,6 @@
thresholds:
0: Alive
90: Dead
- - type: MeleeWeapon
- altDisarm: false
- angle: 0
- animation: WeaponArcBite
- soundHit:
- path: /Audio/Effects/bite.ogg
- damage:
- types:
- Piercing: 6
- type: SolutionContainerManager
solutions:
melee:
@@ -2401,32 +2370,74 @@
Brute: -0.07
Burn: -0.07
+- type: entity
+ parent: MobSpiderBase
+ id: MobSpiderAngryBase
+ abstract: true
+ components:
+ - type: NpcFactionMember
+ factions:
+ - Xeno
+ - type: InputMover
+ - type: MobMover
+ - type: HTN
+ rootTask:
+ task: SimpleHostileCompound
+ - type: GhostRole
+ makeSentient: true
+ name: ghost-role-information-giant-spider-name
+ description: ghost-role-information-giant-spider-description
+ rules: ghost-role-information-giant-spider-rules
+ raffle:
+ settings: short
+ - type: GhostTakeoverAvailable
+
- type: entity
name: tarantula
- parent: MobGiantSpider
- id: MobGiantSpiderAngry
- suffix: Angry
+ parent: MobSpiderBase
+ id: MobGiantSpider
+ description: Widely recognized to be the literal worst thing in existence.
components:
- - type: NpcFactionMember
- factions:
- - Xeno
- - type: InputMover
- - type: MobMover
- - type: HTN
- rootTask:
- task: SimpleHostileCompound
- - type: GhostRole
- makeSentient: true
- name: ghost-role-information-giant-spider-name
- description: ghost-role-information-giant-spider-description
- rules: ghost-role-information-giant-spider-rules
- raffle:
- settings: short
- - type: GhostTakeoverAvailable
+ - type: Sprite
+ drawdepth: Mobs
+ layers:
+ - map: ["enum.DamageStateVisualLayers.Base", "movement"]
+ state: tarantula
+ sprite: Mobs/Animals/spider.rsi
+ - type: SpriteMovement
+ movementLayers:
+ movement:
+ state: tarantula-moving
+ noMovementLayers:
+ movement:
+ state: tarantula
+ - type: DamageStateVisuals
+ states:
+ Alive:
+ Base: tarantula
+ Critical:
+ Base: tarantula_dead
+ Dead:
+ Base: tarantula_dead
+ - type: MeleeWeapon
+ altDisarm: false
+ angle: 0
+ animation: WeaponArcBite
+ soundHit:
+ path: /Audio/Effects/bite.ogg
+ damage:
+ types:
+ Piercing: 6
+
+- type: entity
+ parent:
+ - MobGiantSpider
+ - MobSpiderAngryBase
+ id: MobGiantSpiderAngry
- type: entity
name: clown spider
- parent: MobGiantSpiderAngry
+ parent: MobSpiderAngryBase
id: MobClownSpider
description: Combines the two most terrifying things in existence, spiders and clowns.
components: