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: