]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add sprite movement states (#22940)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Mon, 25 Dec 2023 07:34:21 +0000 (18:34 +1100)
committerGitHub <noreply@github.com>
Mon, 25 Dec 2023 07:34:21 +0000 (23:34 -0800)
* Add sprite movement states

Did it for borgs for reference for future implementations.

* Fix

* Fix prediction issue

* review

13 files changed:
Content.Client/Movement/Systems/SpriteMovementSystem.cs [new file with mode: 0644]
Content.Shared/Movement/Components/InputMoverComponent.cs
Content.Shared/Movement/Components/SpriteMovementComponent.cs [new file with mode: 0644]
Content.Shared/Movement/Events/MoveInputEvent.cs
Content.Shared/Movement/Systems/SharedMoverController.Input.cs
Resources/Prototypes/Entities/Mobs/Cyborgs/borg_chassis.yml
Resources/Textures/Mobs/Silicon/chassis.rsi/janitor.png
Resources/Textures/Mobs/Silicon/chassis.rsi/janitor_moving.png [new file with mode: 0644]
Resources/Textures/Mobs/Silicon/chassis.rsi/medical.png
Resources/Textures/Mobs/Silicon/chassis.rsi/medical_moving.png [new file with mode: 0644]
Resources/Textures/Mobs/Silicon/chassis.rsi/meta.json
Resources/Textures/Mobs/Silicon/chassis.rsi/miner.png
Resources/Textures/Mobs/Silicon/chassis.rsi/miner_moving.png [new file with mode: 0644]

diff --git a/Content.Client/Movement/Systems/SpriteMovementSystem.cs b/Content.Client/Movement/Systems/SpriteMovementSystem.cs
new file mode 100644 (file)
index 0000000..37045c5
--- /dev/null
@@ -0,0 +1,51 @@
+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.None;
+        var moving = SharedMoverController.GetNormalizedMovement(args.Component.HeldMoveButtons) != 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);
+            }
+        }
+    }
+}
index 5562bfaee4059f9bb97c52dd5f56024a34bb2f67..f1e34c90df47e5bda1de3faae5c17ab21b900071 100644 (file)
@@ -1,12 +1,13 @@
 using System.Numerics;
 using Content.Shared.Movement.Systems;
 using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Movement.Components
 {
-    [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
+    [RegisterComponent, NetworkedComponent]
     public sealed partial class InputMoverComponent : Component
     {
         // This class has to be able to handle server TPS being lower than client FPS.
@@ -40,32 +41,30 @@ namespace Content.Shared.Movement.Components
         public Vector2 CurTickWalkMovement;
         public Vector2 CurTickSprintMovement;
 
-        [AutoNetworkedField]
         public MoveButtons HeldMoveButtons = MoveButtons.None;
 
         /// <summary>
         /// Entity our movement is relative to.
         /// </summary>
-        [AutoNetworkedField]
         public EntityUid? RelativeEntity;
 
         /// <summary>
         /// Although our movement might be relative to a particular entity we may have an additional relative rotation
         /// e.g. if we've snapped to a different cardinal direction
         /// </summary>
-        [ViewVariables, AutoNetworkedField]
+        [ViewVariables]
         public Angle TargetRelativeRotation = Angle.Zero;
 
         /// <summary>
         /// The current relative rotation. This will lerp towards the <see cref="TargetRelativeRotation"/>.
         /// </summary>
-        [ViewVariables, AutoNetworkedField]
+        [ViewVariables]
         public Angle RelativeRotation;
 
         /// <summary>
         /// If we traverse on / off a grid then set a timer to update our relative inputs.
         /// </summary>
-        [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField]
+        [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
         [ViewVariables(VVAccess.ReadWrite)]
         public TimeSpan LerpTarget;
 
@@ -73,7 +72,18 @@ namespace Content.Shared.Movement.Components
 
         public bool Sprinting => (HeldMoveButtons & MoveButtons.Walk) == 0x0;
 
-        [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
-        public bool CanMove { get; set; } = true;
+        [ViewVariables(VVAccess.ReadWrite)]
+        public bool CanMove = true;
+    }
+
+    [Serializable, NetSerializable]
+    public sealed class InputMoverComponentState : ComponentState
+    {
+        public MoveButtons HeldMoveButtons;
+        public NetEntity? RelativeEntity;
+        public Angle TargetRelativeRotation;
+        public Angle RelativeRotation;
+        public TimeSpan LerpTarget;
+        public bool CanMove;
     }
 }
diff --git a/Content.Shared/Movement/Components/SpriteMovementComponent.cs b/Content.Shared/Movement/Components/SpriteMovementComponent.cs
new file mode 100644 (file)
index 0000000..8dd058f
--- /dev/null
@@ -0,0 +1,22 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Movement.Components;
+
+/// <summary>
+/// Updates a sprite layer based on whether an entity is moving via input or not.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class SpriteMovementComponent : Component
+{
+    /// <summary>
+    /// Layer and sprite state to use when moving.
+    /// </summary>
+    [DataField]
+    public Dictionary<string, PrototypeLayerData> MovementLayers = new();
+
+    /// <summary>
+    /// Layer and sprite state to use when not moving.
+    /// </summary>
+    [DataField]
+    public Dictionary<string, PrototypeLayerData> NoMovementLayers = new();
+}
index 89e5636acd7df5a9d70bfec4d0f72634738e2cd6..2d0f07e6c0237e0850e6fedfc6682294bb1d370e 100644 (file)
@@ -1,15 +1,22 @@
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Systems;
+
 namespace Content.Shared.Movement.Events;
 
 /// <summary>
-/// Raised on an entity whenever it has a movement input.
+/// Raised on an entity whenever it has a movement input change.
 /// </summary>
 [ByRefEvent]
 public readonly struct MoveInputEvent
 {
     public readonly EntityUid Entity;
+    public readonly InputMoverComponent Component;
+    public readonly MoveButtons OldMovement;
 
-    public MoveInputEvent(EntityUid entity)
+    public MoveInputEvent(EntityUid entity, InputMoverComponent component, MoveButtons oldMovement)
     {
         Entity = entity;
+        Component = component;
+        OldMovement = oldMovement;
     }
 }
index 1d323a91876a5e1634cda13066077b96ab59a879..dde4ac063bdbaae86b30450b61268c3f77445d3a 100644 (file)
@@ -4,6 +4,7 @@ using Content.Shared.Follower.Components;
 using Content.Shared.Input;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Events;
+using Robust.Shared.GameStates;
 using Robust.Shared.Input;
 using Robust.Shared.Input.Binding;
 using Robust.Shared.Player;
@@ -48,7 +49,8 @@ namespace Content.Shared.Movement.Systems
                 .Register<SharedMoverController>();
 
             SubscribeLocalEvent<InputMoverComponent, ComponentInit>(OnInputInit);
-            SubscribeLocalEvent<InputMoverComponent, AfterAutoHandleStateEvent>(OnInputHandleState);
+            SubscribeLocalEvent<InputMoverComponent, ComponentGetState>(OnMoverGetState);
+            SubscribeLocalEvent<InputMoverComponent, ComponentHandleState>(OnMoverHandleState);
             SubscribeLocalEvent<InputMoverComponent, EntParentChangedMessage>(OnInputParentChange);
 
             SubscribeLocalEvent<AutoOrientComponent, EntParentChangedMessage>(OnAutoParentChange);
@@ -64,19 +66,76 @@ namespace Content.Shared.Movement.Systems
             CameraRotationLocked = obj;
         }
 
+        /// <summary>
+        /// Gets the buttons held with opposites cancelled out.
+        /// </summary>
+        public static MoveButtons GetNormalizedMovement(MoveButtons buttons)
+        {
+            var oldMovement = buttons;
+
+            if ((oldMovement & (MoveButtons.Left | MoveButtons.Right)) == (MoveButtons.Left | MoveButtons.Right))
+            {
+                oldMovement &= ~MoveButtons.Left;
+                oldMovement &= ~MoveButtons.Right;
+            }
+
+            if ((oldMovement & (MoveButtons.Up | MoveButtons.Down)) == (MoveButtons.Up | MoveButtons.Down))
+            {
+                oldMovement &= ~MoveButtons.Up;
+                oldMovement &= ~MoveButtons.Down;
+            }
+
+            return oldMovement;
+        }
+
         protected void SetMoveInput(InputMoverComponent component, MoveButtons buttons)
         {
             if (component.HeldMoveButtons == buttons)
                 return;
 
+            // Relay the fact we had any movement event.
+            // TODO: Ideally we'd do these in a tick instead of out of sim.
+            var moveEvent = new MoveInputEvent(component.Owner, component, component.HeldMoveButtons);
             component.HeldMoveButtons = buttons;
-            Dirty(component);
+            RaiseLocalEvent(component.Owner, ref moveEvent);
+            Dirty(component.Owner, component);
         }
 
-        private void OnInputHandleState(EntityUid uid, InputMoverComponent component, ref AfterAutoHandleStateEvent args)
+        private void OnMoverHandleState(EntityUid uid, InputMoverComponent component, ComponentHandleState args)
         {
+            if (args.Current is not InputMoverComponentState state)
+                return;
+
+            // Handle state
+            component.LerpTarget = state.LerpTarget;
+            component.RelativeRotation = state.RelativeRotation;
+            component.TargetRelativeRotation = state.TargetRelativeRotation;
+            component.CanMove = state.CanMove;
+            component.RelativeEntity = EnsureEntity<InputMoverComponent>(state.RelativeEntity, uid);
+
+            // Reset
             component.LastInputTick = GameTick.Zero;
             component.LastInputSubTick = 0;
+
+            if (component.HeldMoveButtons != state.HeldMoveButtons)
+            {
+                var moveEvent = new MoveInputEvent(uid, component, component.HeldMoveButtons);
+                component.HeldMoveButtons = state.HeldMoveButtons;
+                RaiseLocalEvent(uid, ref moveEvent);
+            }
+        }
+
+        private void OnMoverGetState(EntityUid uid, InputMoverComponent component, ref ComponentGetState args)
+        {
+            args.State = new InputMoverComponentState()
+            {
+                CanMove = component.CanMove,
+                RelativeEntity = GetNetEntity(component.RelativeEntity),
+                LerpTarget = component.LerpTarget,
+                HeldMoveButtons = component.HeldMoveButtons,
+                RelativeRotation = component.RelativeRotation,
+                TargetRelativeRotation = component.TargetRelativeRotation,
+            };
         }
 
         private void ShutdownInput()
@@ -260,11 +319,6 @@ namespace Content.Shared.Movement.Systems
             if (!MoverQuery.TryGetComponent(entity, out var moverComp))
                 return;
 
-            // Relay the fact we had any movement event.
-            // TODO: Ideally we'd do these in a tick instead of out of sim.
-            var moveEvent = new MoveInputEvent(entity);
-            RaiseLocalEvent(entity, ref moveEvent);
-
             // For stuff like "Moving out of locker" or the likes
             // We'll relay a movement input to the parent.
             if (_container.IsEntityInContainer(entity) &&
@@ -276,7 +330,7 @@ namespace Content.Shared.Movement.Systems
                 RaiseLocalEvent(xform.ParentUid, ref relayMoveEvent);
             }
 
-            SetVelocityDirection(moverComp, dir, subTick, state);
+            SetVelocityDirection(entity, moverComp, dir, subTick, state);
         }
 
         private void OnInputInit(EntityUid uid, InputMoverComponent component, ComponentInit args)
@@ -308,7 +362,7 @@ namespace Content.Shared.Movement.Systems
 
             if (moverComp == null) return;
 
-            SetSprinting(moverComp, subTick, walking);
+            SetSprinting(uid, moverComp, subTick, walking);
         }
 
         public (Vector2 Walking, Vector2 Sprinting) GetVelocityInput(InputMoverComponent mover)
@@ -359,7 +413,7 @@ namespace Content.Shared.Movement.Systems
         ///     composed into a single direction vector, <see cref="VelocityDir"/>. Enabling
         ///     opposite directions will cancel each other out, resulting in no direction.
         /// </summary>
-        public void SetVelocityDirection(InputMoverComponent component, Direction direction, ushort subTick, bool enabled)
+        public void SetVelocityDirection(EntityUid entity, InputMoverComponent component, Direction direction, ushort subTick, bool enabled)
         {
             // Logger.Info($"[{_gameTiming.CurTick}/{subTick}] {direction}: {enabled}");
 
@@ -372,10 +426,10 @@ namespace Content.Shared.Movement.Systems
                 _ => throw new ArgumentException(nameof(direction))
             };
 
-            SetMoveInput(component, subTick, enabled, bit);
+            SetMoveInput(entity, component, subTick, enabled, bit);
         }
 
-        private void SetMoveInput(InputMoverComponent component, ushort subTick, bool enabled, MoveButtons bit)
+        private void SetMoveInput(EntityUid entity, InputMoverComponent component, ushort subTick, bool enabled, MoveButtons bit)
         {
             // Modifies held state of a movement button at a certain sub tick and updates current tick movement vectors.
             ResetSubtick(component);
@@ -415,11 +469,11 @@ namespace Content.Shared.Movement.Systems
             component.LastInputSubTick = 0;
         }
 
-        public void SetSprinting(InputMoverComponent component, ushort subTick, bool walking)
+        public void SetSprinting(EntityUid entity, InputMoverComponent component, ushort subTick, bool walking)
         {
             // Logger.Info($"[{_gameTiming.CurTick}/{subTick}] Sprint: {enabled}");
 
-            SetMoveInput(component, subTick, walking, MoveButtons.Walk);
+            SetMoveInput(entity, component, subTick, walking, MoveButtons.Walk);
         }
 
         /// <summary>
index eb6673fea2d0f832f6e0b124ffa0a358dfd11a9c..4a45717b79115b4ad55d6323c735f37f2dcdc879 100644 (file)
@@ -33,6 +33,7 @@
   - type: Sprite
     layers:
     - state: miner
+      map: ["movement"]
     - state: miner_e_r
       map: ["enum.BorgVisualLayers.Light"]
       shader: unshaded
       shader: unshaded
       map: ["light"]
       visible: false
+  - type: SpriteMovement
+    movementLayers:
+      movement:
+        state: miner_moving
+    noMovementLayers:
+      movement:
+        state: miner
   - type: BorgChassis
     maxModules: 4
     moduleWhitelist:
   - type: Sprite
     layers:
     - state: janitor
+      map: ["movement"]
     - state: janitor_e_r
       map: ["enum.BorgVisualLayers.Light"]
       shader: unshaded
       shader: unshaded
       map: ["light"]
       visible: false
+  - type: SpriteMovement
+    movementLayers:
+      movement:
+        state: janitor_moving
+    noMovementLayers:
+      movement:
+        state: janitor
   - type: BorgChassis
     maxModules: 4
     moduleWhitelist:
   - type: Sprite
     layers:
     - state: medical
+      map: ["movement"]
     - state: medical_e_r
       map: ["enum.BorgVisualLayers.Light"]
       shader: unshaded
       shader: unshaded
       map: ["light"]
       visible: false
+  - type: SpriteMovement
+    movementLayers:
+      movement:
+        state: medical_moving
+    noMovementLayers:
+      movement:
+        state: medical
   - type: BorgChassis
     maxModules: 4
     moduleWhitelist:
index ff7d4eb483ab447cc836098870e3e609af530e1b..83275f949bfca7c12002f3848f8af20f2888fbdc 100644 (file)
Binary files a/Resources/Textures/Mobs/Silicon/chassis.rsi/janitor.png and b/Resources/Textures/Mobs/Silicon/chassis.rsi/janitor.png differ
diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/janitor_moving.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/janitor_moving.png
new file mode 100644 (file)
index 0000000..ff7d4eb
Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/chassis.rsi/janitor_moving.png differ
index 4be26cce2f447e6aeced62eb58c3edba4422f7f4..86f26f52cf2c3795fdf03eb78ad343e1a31060b4 100644 (file)
Binary files a/Resources/Textures/Mobs/Silicon/chassis.rsi/medical.png and b/Resources/Textures/Mobs/Silicon/chassis.rsi/medical.png differ
diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/medical_moving.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/medical_moving.png
new file mode 100644 (file)
index 0000000..4be26cc
Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/chassis.rsi/medical_moving.png differ
index 33219de10e5cf97b3a98c17adeb94c8630a087ac..6f31a4f5d51aa53d7413daac29503bfdd9444277 100644 (file)
     },
     {
       "name": "janitor",
+      "directions": 4
+    },
+    {
+      "name": "janitor_moving",
       "directions": 4,
       "delays": [
         [
         ]
       ]
     },
+    {
+      "name": "medical_moving",
+      "directions": 4,
+      "delays": [
+        [
+          0.1,
+          0.2,
+          0.1
+        ],
+        [
+          0.1,
+          0.2,
+          0.1
+        ],
+        [
+          0.1,
+          0.2,
+          0.1
+        ],
+        [
+          0.1,
+          0.2,
+          0.1
+        ]
+      ]
+    },
     {
       "name": "medical_e",
       "directions": 4
     },
     {
       "name": "miner",
+      "directions": 4
+    },
+    {
+      "name": "miner_moving",
       "directions": 4,
       "delays": [
         [
index f4a6ca49a3f0adc329ee9fa43e1e993e6a9526df..93da33c7b4ca2bafe2e0d6fe091d24cfdf971b92 100644 (file)
Binary files a/Resources/Textures/Mobs/Silicon/chassis.rsi/miner.png and b/Resources/Textures/Mobs/Silicon/chassis.rsi/miner.png differ
diff --git a/Resources/Textures/Mobs/Silicon/chassis.rsi/miner_moving.png b/Resources/Textures/Mobs/Silicon/chassis.rsi/miner_moving.png
new file mode 100644 (file)
index 0000000..f4a6ca4
Binary files /dev/null and b/Resources/Textures/Mobs/Silicon/chassis.rsi/miner_moving.png differ