]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Mouse rotator system (#19267)
authorKara <lunarautomaton6@gmail.com>
Sat, 23 Sep 2023 05:45:13 +0000 (22:45 -0700)
committerGitHub <noreply@github.com>
Sat, 23 Sep 2023 05:45:13 +0000 (15:45 +1000)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Content.Client/MouseRotator/MouseRotatorSystem.cs [new file with mode: 0644]
Content.Server/MouseRotator/MouseRotatorSystem.cs [new file with mode: 0644]
Content.Shared/Interaction/Components/NoRotateOnInteractComponent.cs [new file with mode: 0644]
Content.Shared/Interaction/RotateToFaceSystem.cs
Content.Shared/Interaction/SharedInteractionSystem.cs
Content.Shared/MouseRotator/MouseRotatorComponent.cs [new file with mode: 0644]
Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs [new file with mode: 0644]
Content.Shared/Movement/Components/NoRotateOnMoveComponent.cs [new file with mode: 0644]
Content.Shared/Movement/Systems/SharedMoverController.cs
Resources/Prototypes/Entities/Objects/Weapons/Guns/turrets.yml

diff --git a/Content.Client/MouseRotator/MouseRotatorSystem.cs b/Content.Client/MouseRotator/MouseRotatorSystem.cs
new file mode 100644 (file)
index 0000000..4b7f937
--- /dev/null
@@ -0,0 +1,61 @@
+using Content.Shared.MouseRotator;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.Player;
+using Robust.Shared.Map;
+using Robust.Shared.Timing;
+
+namespace Content.Client.MouseRotator;
+
+/// <inheritdoc/>
+public sealed class MouseRotatorSystem : SharedMouseRotatorSystem
+{
+    [Dependency] private readonly IInputManager _input = default!;
+    [Dependency] private readonly IPlayerManager _player = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly IEyeManager _eye = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        if (!_timing.IsFirstTimePredicted || !_input.MouseScreenPosition.IsValid)
+            return;
+
+        var player = _player.LocalPlayer?.ControlledEntity;
+
+        if (player == null || !TryComp<MouseRotatorComponent>(player, out var rotator))
+            return;
+
+        var xform = Transform(player.Value);
+
+        // Get mouse loc and convert to angle based on player location
+        var coords = _input.MouseScreenPosition;
+        var mapPos = _eye.PixelToMap(coords);
+
+        if (mapPos.MapId == MapId.Nullspace)
+            return;
+
+        var angle = (mapPos.Position - xform.MapPosition.Position).ToWorldAngle();
+
+        var curRot = _transform.GetWorldRotation(xform);
+
+        // Don't raise event if mouse ~hasn't moved (or if too close to goal rotation already)
+        var diff = Angle.ShortestDistance(angle, curRot);
+        if (Math.Abs(diff.Theta) < rotator.AngleTolerance.Theta)
+            return;
+
+        if (rotator.GoalRotation != null)
+        {
+            var goalDiff = Angle.ShortestDistance(angle, rotator.GoalRotation.Value);
+            if (Math.Abs(goalDiff.Theta) < rotator.AngleTolerance.Theta)
+                return;
+        }
+
+        RaisePredictiveEvent(new RequestMouseRotatorRotationEvent
+        {
+            Rotation = angle
+        });
+    }
+}
diff --git a/Content.Server/MouseRotator/MouseRotatorSystem.cs b/Content.Server/MouseRotator/MouseRotatorSystem.cs
new file mode 100644 (file)
index 0000000..10431de
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.MouseRotator;
+
+namespace Content.Server.MouseRotator;
+
+/// <inheritdoc/>
+public sealed class MouseRotatorSystem : SharedMouseRotatorSystem
+{
+}
diff --git a/Content.Shared/Interaction/Components/NoRotateOnInteractComponent.cs b/Content.Shared/Interaction/Components/NoRotateOnInteractComponent.cs
new file mode 100644 (file)
index 0000000..553c034
--- /dev/null
@@ -0,0 +1,11 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Interaction.Components;
+
+/// <summary>
+/// This is used for entities which should not rotate on interactions (for instance those who use <see cref="MouseRotator"/> instead)
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class NoRotateOnInteractComponent : Component
+{
+}
index d5fa01a71bde2d8db51821db545d4b88fcd57e5d..01dc572a73d08ae9ff37e8c0ffcad536e5bc7bde 100644 (file)
@@ -44,7 +44,7 @@ namespace Content.Shared.Interaction
                 if (Math.Abs(rotationDiff) > maxRotate)
                 {
                     var goalTheta = worldRot + Math.Sign(rotationDiff) * maxRotate;
-                    _transform.SetWorldRotation(xform, goalTheta);
+                    TryFaceAngle(uid, goalTheta, xform);
                     rotationDiff = (goalRotation - goalTheta);
 
                     if (Math.Abs(rotationDiff) > tolerance)
@@ -55,11 +55,11 @@ namespace Content.Shared.Interaction
                     return true;
                 }
 
-                _transform.SetWorldRotation(xform, goalRotation);
+                TryFaceAngle(uid, goalRotation, xform);
             }
             else
             {
-                _transform.SetWorldRotation(xform, goalRotation);
+                TryFaceAngle(uid, goalRotation, xform);
             }
 
             return true;
@@ -85,7 +85,7 @@ namespace Content.Shared.Interaction
                 if (!Resolve(user, ref xform))
                     return false;
 
-                xform.WorldRotation = diffAngle;
+                _transform.SetWorldRotation(xform, diffAngle);
                 return true;
             }
 
@@ -101,7 +101,7 @@ namespace Content.Shared.Interaction
                         // (Since the user being buckled to it holds it down with their weight.)
                         // This is logically equivalent to RotateWhileAnchored.
                         // Barstools and office chairs have independent wheels, while regular chairs don't.
-                        Transform(rotatable.Owner).WorldRotation = diffAngle;
+                        _transform.SetWorldRotation(Transform(suid.Value), diffAngle);
                         return true;
                     }
                 }
index 830a3cd936eddc650cbabb16ed04e90373ec6b68..7d55035d5c1736e648178e564f2292822bd9565c 100644 (file)
@@ -433,7 +433,8 @@ namespace Content.Shared.Interaction
             if (coordinates.GetMapId(EntityManager) != Transform(user).MapID)
                 return false;
 
-            _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager));
+            if (!HasComp<NoRotateOnInteractComponent>(user))
+                _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager));
 
             return true;
         }
diff --git a/Content.Shared/MouseRotator/MouseRotatorComponent.cs b/Content.Shared/MouseRotator/MouseRotatorComponent.cs
new file mode 100644 (file)
index 0000000..9b4dac5
--- /dev/null
@@ -0,0 +1,43 @@
+using System.Numerics;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.MouseRotator;
+
+/// <summary>
+/// This component allows overriding an entities local rotation based on the client's mouse movement
+/// </summary>
+/// <see cref="SharedMouseRotatorSystem"/>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class MouseRotatorComponent : Component
+{
+    /// <summary>
+    ///     How much the desired angle needs to change before a predictive event is sent
+    /// </summary>
+    [DataField]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public Angle AngleTolerance = Angle.FromDegrees(5.0);
+
+    /// <summary>
+    ///     The angle that will be lerped to
+    /// </summary>
+    [AutoNetworkedField, DataField]
+    public Angle? GoalRotation;
+
+    /// <summary>
+    ///     Max degrees the entity can rotate per second
+    /// </summary>
+    [DataField]
+    [ViewVariables(VVAccess.ReadWrite)]
+    public double RotationSpeed = float.MaxValue;
+}
+
+/// <summary>
+///     Raised on an entity with <see cref="MouseRotatorComponent"/> as a predictive event on the client
+///     when mouse rotation changes
+/// </summary>
+[Serializable, NetSerializable]
+public sealed class RequestMouseRotatorRotationEvent : EntityEventArgs
+{
+    public Angle Rotation;
+}
diff --git a/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs b/Content.Shared/MouseRotator/SharedMouseRotatorSystem.cs
new file mode 100644 (file)
index 0000000..6c2b1ea
--- /dev/null
@@ -0,0 +1,60 @@
+using Content.Shared.Interaction;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.MouseRotator;
+
+/// <summary>
+/// This handles rotating an entity based on mouse location
+/// </summary>
+/// <see cref="MouseRotatorComponent"/>
+public abstract class SharedMouseRotatorSystem : EntitySystem
+{
+    [Dependency] private readonly RotateToFaceSystem _rotate = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeAllEvent<RequestMouseRotatorRotationEvent>(OnRequestRotation);
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        // TODO maybe `ActiveMouseRotatorComponent` to avoid querying over more entities than we need?
+        // (if this is added to players)
+        // (but arch makes these fast anyway, so)
+        var query = EntityQueryEnumerator<MouseRotatorComponent, TransformComponent>();
+        while (query.MoveNext(out var uid, out var rotator, out var xform))
+        {
+            if (rotator.GoalRotation == null)
+                continue;
+
+            if (_rotate.TryRotateTo(
+                    uid,
+                    rotator.GoalRotation.Value,
+                    frameTime,
+                    rotator.AngleTolerance,
+                    MathHelper.DegreesToRadians(rotator.RotationSpeed),
+                    xform))
+            {
+                // Stop rotating if we finished
+                rotator.GoalRotation = null;
+                Dirty(uid, rotater);
+            }
+        }
+    }
+
+    private void OnRequestRotation(RequestMouseRotatorRotationEvent msg, EntitySessionEventArgs args)
+    {
+        if (args.SenderSession.AttachedEntity is not { } ent || !TryComp<MouseRotatorComponent>(ent, out var rotator))
+        {
+            Log.Error($"User {args.SenderSession.Name} ({args.SenderSession.UserId}) tried setting local rotation without a mouse rotator component attached!");
+            return;
+        }
+
+        rotator.GoalRotation = msg.Rotation;
+        Dirty(ent, rotator);
+    }
+}
diff --git a/Content.Shared/Movement/Components/NoRotateOnMoveComponent.cs b/Content.Shared/Movement/Components/NoRotateOnMoveComponent.cs
new file mode 100644 (file)
index 0000000..c6a2072
--- /dev/null
@@ -0,0 +1,12 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Movement.Components;
+
+/// <summary>
+/// This is used for entities which shouldn't have their local rotation set when moving, e.g. those using
+/// <see cref="MouseRotator"/> instead
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class NoRotateOnMoveComponent : Component
+{
+}
index 2b95b5909f9889069ab6710adaeac145211e3988..a9297b941103c100a409e1266de73b3587f806d3 100644 (file)
@@ -54,6 +54,7 @@ namespace Content.Shared.Movement.Systems
         protected EntityQuery<SharedPullableComponent> PullableQuery;
         protected EntityQuery<TransformComponent> XformQuery;
         protected EntityQuery<CanMoveInAirComponent> CanMoveInAirQuery;
+        protected EntityQuery<NoRotateOnMoveComponent> NoRotateQuery;
 
         private const float StepSoundMoveDistanceRunning = 2;
         private const float StepSoundMoveDistanceWalking = 1.5f;
@@ -84,6 +85,7 @@ namespace Content.Shared.Movement.Systems
             RelayQuery = GetEntityQuery<RelayInputMoverComponent>();
             PullableQuery = GetEntityQuery<SharedPullableComponent>();
             XformQuery = GetEntityQuery<TransformComponent>();
+            NoRotateQuery = GetEntityQuery<NoRotateOnMoveComponent>();
             CanMoveInAirQuery = GetEntityQuery<CanMoveInAirComponent>();
 
             InitializeFootsteps();
@@ -246,10 +248,13 @@ namespace Content.Shared.Movement.Systems
 
             if (worldTotal != Vector2.Zero)
             {
-                var worldRot = _transform.GetWorldRotation(xform);
-                _transform.SetLocalRotation(xform, xform.LocalRotation + worldTotal.ToWorldAngle() - worldRot);
-                // TODO apparently this results in a duplicate move event because "This should have its event run during
-                // island solver"??. So maybe SetRotation needs an argument to avoid raising an event?
+                if (!NoRotateQuery.HasComponent(uid))
+                {
+                    // TODO apparently this results in a duplicate move event because "This should have its event run during
+                    // island solver"??. So maybe SetRotation needs an argument to avoid raising an event?
+                    var worldRot = _transform.GetWorldRotation(xform);
+                    _transform.SetLocalRotation(xform, xform.LocalRotation + worldTotal.ToWorldAngle() - worldRot);
+                }
 
                 if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) &&
                     TryGetSound(weightless, uid, mover, mobMover, xform, out var sound, tileDef: tileDef))
index e0f5357b7e3a7fe44c327d7ffcec320297e91eea..3c7d0dd5d08ecf8714b7fc0f4365d81ec9506bbf 100644 (file)
@@ -66,7 +66,6 @@
       interactSuccessSound:
         path: /Audio/Effects/double_beep.ogg
     - type: CombatMode
-      combatToggleAction: ActionCombatModeToggleOff
     - type: Damageable
       damageContainer: Inorganic
     - type: Destructible
           3.141
         SoundTargetInLOS: !type:SoundPathSpecifier
           path: /Audio/Effects/double_beep.ogg
+    - type: MouseRotator
+      rotationSpeed: 180
+    - type: NoRotateOnInteract
+    - type: NoRotateOnMove
+    - type: Input
+      context: "human"
 
 - type: entity
   parent: BaseWeaponTurret