Sprite Movement working with AI movement (#33494)

* FINALLY

* Update animals.yml
This commit is contained in:
Ed
2024-12-18 19:15:34 +03:00
committed by GitHub
parent b649517a17
commit 18fe8b9df0
10 changed files with 182 additions and 115 deletions

View File

@@ -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;
/// <summary>
/// Controls the switching of motion and standing still animation
/// </summary>
public sealed class ClientSpriteMovementSystem : SharedSpriteMovementSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<SpriteComponent> _spriteQuery;
public override void Initialize()
{
base.Initialize();
_spriteQuery = GetEntityQuery<SpriteComponent>();
SubscribeLocalEvent<SpriteMovementComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(Entity<SpriteMovementComponent> 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);
}
}
}
}

View File

@@ -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;
/// <summary>
/// Handles setting sprite states based on whether an entity has movement input.
/// </summary>
public sealed class SpriteMovementSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<SpriteComponent> _spriteQuery;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SpriteMovementComponent, MoveInputEvent>(OnSpriteMoveInput);
_spriteQuery = GetEntityQuery<SpriteComponent>();
}
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);
}
}
}
}

View File

@@ -0,0 +1,7 @@
using Content.Shared.Movement.Systems;
namespace Content.Server.Movement.Systems;
public sealed class SpriteMovementSystem : SharedSpriteMovementSystem
{
}

View File

@@ -253,7 +253,7 @@ public sealed partial class NPCSteeringSystem
if (!targetCoordinates.IsValid(EntityManager)) if (!targetCoordinates.IsValid(EntityManager))
{ {
SetDirection(mover, steering, Vector2.Zero); SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath; steering.Status = SteeringStatus.NoPath;
return false; return false;
} }
@@ -263,7 +263,7 @@ public sealed partial class NPCSteeringSystem
// Can't make it again. // Can't make it again.
if (ourMap.MapId != targetMap.MapId) if (ourMap.MapId != targetMap.MapId)
{ {
SetDirection(mover, steering, Vector2.Zero); SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath; steering.Status = SteeringStatus.NoPath;
return false; return false;
} }

View File

@@ -11,6 +11,7 @@ using Content.Shared.Climbing.Systems;
using Content.Shared.CombatMode; using Content.Shared.CombatMode;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Movement.Components; using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems; using Content.Shared.Movement.Systems;
using Content.Shared.NPC; using Content.Shared.NPC;
using Content.Shared.NPC.Components; using Content.Shared.NPC.Components;
@@ -207,6 +208,9 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
if (EntityManager.TryGetComponent(uid, out InputMoverComponent? controller)) if (EntityManager.TryGetComponent(uid, out InputMoverComponent? controller))
{ {
controller.CurTickSprintMovement = Vector2.Zero; controller.CurTickSprintMovement = Vector2.Zero;
var ev = new SpriteMoveEvent(false);
RaiseLocalEvent(uid, ref ev);
} }
component.PathfindToken?.Cancel(); component.PathfindToken?.Cancel();
@@ -270,7 +274,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
} }
} }
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)) if (clear && value.Equals(Vector2.Zero))
{ {
@@ -282,6 +286,9 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
component.CurTickSprintMovement = value; component.CurTickSprintMovement = value;
component.LastInputTick = _timing.CurTick; component.LastInputTick = _timing.CurTick;
component.LastInputSubTick = ushort.MaxValue; component.LastInputSubTick = ushort.MaxValue;
var ev = new SpriteMoveEvent(true);
RaiseLocalEvent(uid, ref ev);
} }
/// <summary> /// <summary>
@@ -297,7 +304,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
{ {
if (Deleted(steering.Coordinates.EntityId)) if (Deleted(steering.Coordinates.EntityId))
{ {
SetDirection(mover, steering, Vector2.Zero); SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath; steering.Status = SteeringStatus.NoPath;
return; return;
} }
@@ -305,14 +312,14 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
// No path set from pathfinding or the likes. // No path set from pathfinding or the likes.
if (steering.Status == SteeringStatus.NoPath) if (steering.Status == SteeringStatus.NoPath)
{ {
SetDirection(mover, steering, Vector2.Zero); SetDirection(uid, mover, steering, Vector2.Zero);
return; return;
} }
// Can't move at all, just noop input. // Can't move at all, just noop input.
if (!mover.CanMove) if (!mover.CanMove)
{ {
SetDirection(mover, steering, Vector2.Zero); SetDirection(uid, mover, steering, Vector2.Zero);
steering.Status = SteeringStatus.NoPath; steering.Status = SteeringStatus.NoPath;
return; return;
} }
@@ -341,7 +348,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
if (steering.CanSeek && !TrySeek(uid, mover, steering, body, xform, offsetRot, moveSpeed, interest, frameTime, ref forceSteer)) 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; return;
} }
@@ -354,7 +361,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
if (!forceSteer) if (!forceSteer)
{ {
SetDirection(mover, steering, steering.LastSteerDirection, false); SetDirection(uid, mover, steering, steering.LastSteerDirection, false);
return; return;
} }
@@ -391,7 +398,7 @@ public sealed partial class NPCSteeringSystem : SharedNPCSteeringSystem
steering.LastSteerDirection = resultDirection; steering.LastSteerDirection = resultDirection;
DebugTools.Assert(!float.IsNaN(resultDirection.X)); DebugTools.Assert(!float.IsNaN(resultDirection.X));
SetDirection(mover, steering, resultDirection, false); SetDirection(uid, mover, steering, resultDirection, false);
} }
private EntityCoordinates GetCoordinates(PathPoly poly) private EntityCoordinates GetCoordinates(PathPoly poly)

View File

@@ -5,7 +5,7 @@ namespace Content.Shared.Movement.Components;
/// <summary> /// <summary>
/// Updates a sprite layer based on whether an entity is moving via input or not. /// Updates a sprite layer based on whether an entity is moving via input or not.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class SpriteMovementComponent : Component public sealed partial class SpriteMovementComponent : Component
{ {
/// <summary> /// <summary>
@@ -19,4 +19,7 @@ public sealed partial class SpriteMovementComponent : Component
/// </summary> /// </summary>
[DataField] [DataField]
public Dictionary<string, PrototypeLayerData> NoMovementLayers = new(); public Dictionary<string, PrototypeLayerData> NoMovementLayers = new();
[DataField, AutoNetworkedField]
public bool IsMoving;
} }

View File

@@ -0,0 +1,15 @@
namespace Content.Shared.Movement.Events;
/// <summary>
/// Raised on an entity whenever it should change movement sprite
/// </summary>
[ByRefEvent]
public readonly struct SpriteMoveEvent
{
public readonly bool IsMoving = false;
public SpriteMoveEvent(bool isMoving)
{
IsMoving = isMoving;
}
}

View File

@@ -96,6 +96,9 @@ namespace Content.Shared.Movement.Systems
entity.Comp.HeldMoveButtons = buttons; entity.Comp.HeldMoveButtons = buttons;
RaiseLocalEvent(entity, ref moveEvent); RaiseLocalEvent(entity, ref moveEvent);
Dirty(entity, entity.Comp); Dirty(entity, entity.Comp);
var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None);
RaiseLocalEvent(entity, ref ev);
} }
private void OnMoverHandleState(Entity<InputMoverComponent> entity, ref ComponentHandleState args) private void OnMoverHandleState(Entity<InputMoverComponent> entity, ref ComponentHandleState args)
@@ -119,6 +122,9 @@ namespace Content.Shared.Movement.Systems
var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons); var moveEvent = new MoveInputEvent(entity, entity.Comp.HeldMoveButtons);
entity.Comp.HeldMoveButtons = state.HeldMoveButtons; entity.Comp.HeldMoveButtons = state.HeldMoveButtons;
RaiseLocalEvent(entity.Owner, ref moveEvent); RaiseLocalEvent(entity.Owner, ref moveEvent);
var ev = new SpriteMoveEvent(entity.Comp.HeldMoveButtons != MoveButtons.None);
RaiseLocalEvent(entity, ref ev);
} }
} }

View File

@@ -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<SpriteMovementComponent, SpriteMoveEvent>(OnSpriteMoveInput);
}
private void OnSpriteMoveInput(Entity<SpriteMovementComponent> ent, ref SpriteMoveEvent args)
{
if (ent.Comp.IsMoving == args.IsMoving)
return;
ent.Comp.IsMoving = args.IsMoving;
Dirty(ent);
}
}

View File

@@ -2283,24 +2283,10 @@
# Code unique spider prototypes or combine them all into one spider and get a # Code unique spider prototypes or combine them all into one spider and get a
# random sprite state when you spawn it. # random sprite state when you spawn it.
- type: entity - type: entity
name: tarantula
parent: [ SimpleMobBase, MobCombat ] parent: [ SimpleMobBase, MobCombat ]
id: MobGiantSpider id: MobSpiderBase
description: Widely recognized to be the literal worst thing in existence. abstract: true
components: 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: Physics
- type: Fixtures - type: Fixtures
fixtures: fixtures:
@@ -2313,14 +2299,6 @@
- MobMask - MobMask
layer: layer:
- MobLayer - MobLayer
- type: DamageStateVisuals
states:
Alive:
Base: tarantula
Critical:
Base: tarantula_dead
Dead:
Base: tarantula_dead
- type: Butcherable - type: Butcherable
spawned: spawned:
- id: FoodMeatSpider - id: FoodMeatSpider
@@ -2332,15 +2310,6 @@
thresholds: thresholds:
0: Alive 0: Alive
90: Dead 90: Dead
- type: MeleeWeapon
altDisarm: false
angle: 0
animation: WeaponArcBite
soundHit:
path: /Audio/Effects/bite.ogg
damage:
types:
Piercing: 6
- type: SolutionContainerManager - type: SolutionContainerManager
solutions: solutions:
melee: melee:
@@ -2402,10 +2371,9 @@
Burn: -0.07 Burn: -0.07
- type: entity - type: entity
name: tarantula parent: MobSpiderBase
parent: MobGiantSpider id: MobSpiderAngryBase
id: MobGiantSpiderAngry abstract: true
suffix: Angry
components: components:
- type: NpcFactionMember - type: NpcFactionMember
factions: factions:
@@ -2424,9 +2392,52 @@
settings: short settings: short
- type: GhostTakeoverAvailable - type: GhostTakeoverAvailable
- type: entity
name: tarantula
parent: MobSpiderBase
id: MobGiantSpider
description: Widely recognized to be the literal worst thing in existence.
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: 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 - type: entity
name: clown spider name: clown spider
parent: MobGiantSpiderAngry parent: MobSpiderAngryBase
id: MobClownSpider id: MobClownSpider
description: Combines the two most terrifying things in existence, spiders and clowns. description: Combines the two most terrifying things in existence, spiders and clowns.
components: components: