diff --git a/Content.Client/GameObjects/Components/Rotatable/RotatableComponent.cs b/Content.Client/GameObjects/Components/Rotatable/RotatableComponent.cs
new file mode 100644
index 0000000000..462d360709
--- /dev/null
+++ b/Content.Client/GameObjects/Components/Rotatable/RotatableComponent.cs
@@ -0,0 +1,12 @@
+#nullable enable
+using Content.Shared.GameObjects.Components.Rotatable;
+using Robust.Shared.GameObjects;
+
+namespace Content.Client.GameObjects.Components.Rotatable
+{
+ [RegisterComponent]
+ [ComponentReference(typeof(SharedRotatableComponent))]
+ public class RotatableComponent : SharedRotatableComponent
+ {
+ }
+}
diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs
index cee1503a1f..f1ef10650e 100644
--- a/Content.Client/IgnoredComponents.cs
+++ b/Content.Client/IgnoredComponents.cs
@@ -56,7 +56,6 @@ namespace Content.Client
"Drink",
"Food",
"FoodContainer",
- "Rotatable",
"MagicMirror",
"FloorTile",
"ShuttleController",
diff --git a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs
index 380b7935a8..33aa7243e3 100644
--- a/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs
+++ b/Content.Server/GameObjects/Components/Rotatable/RotatableComponent.cs
@@ -1,27 +1,19 @@
-using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
+#nullable enable
+using Content.Shared.GameObjects.Components.Rotatable;
+using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
-using Robust.Shared.Serialization.Manager.Attributes;
-using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Rotatable
{
[RegisterComponent]
- public class RotatableComponent : Component
+ [ComponentReference(typeof(SharedRotatableComponent))]
+ public class RotatableComponent : SharedRotatableComponent
{
- public override string Name => "Rotatable";
-
- ///
- /// If true, this entity can be rotated even while anchored.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("rotateWhileAnchored")]
- public bool RotateWhileAnchored { get; private set; }
-
private void TryRotate(IEntity user, Angle angle)
{
if (!RotateWhileAnchored && Owner.TryGetComponent(out IPhysBody? physics))
diff --git a/Content.Shared/GameObjects/Components/Rotatable/SharedRotatableComponent.cs b/Content.Shared/GameObjects/Components/Rotatable/SharedRotatableComponent.cs
new file mode 100644
index 0000000000..cad3c89985
--- /dev/null
+++ b/Content.Shared/GameObjects/Components/Rotatable/SharedRotatableComponent.cs
@@ -0,0 +1,26 @@
+#nullable enable
+using Robust.Shared.GameObjects;
+using Robust.Shared.Serialization.Manager.Attributes;
+using Robust.Shared.ViewVariables;
+
+namespace Content.Shared.GameObjects.Components.Rotatable
+{
+ public abstract class SharedRotatableComponent : Component
+ {
+ public override string Name => "Rotatable";
+
+ ///
+ /// If true, this entity can be rotated even while anchored.
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("rotateWhileAnchored")]
+ public bool RotateWhileAnchored { get; protected set; }
+
+ ///
+ /// If true, will rotate entity in players direction when pulled
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ [DataField("rotateWhilePulling")]
+ public bool RotateWhilePulling { get; protected set; } = true;
+ }
+}
diff --git a/Content.Shared/GameObjects/EntitySystems/SharedPullingSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedPullingSystem.cs
index c89d510046..1e4aa4328c 100644
--- a/Content.Shared/GameObjects/EntitySystems/SharedPullingSystem.cs
+++ b/Content.Shared/GameObjects/EntitySystems/SharedPullingSystem.cs
@@ -1,7 +1,9 @@
#nullable enable
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.GameObjects.Components.Pulling;
+using Content.Shared.GameObjects.Components.Rotatable;
using Content.Shared.GameObjects.EntitySystemMessages.Pulling;
using Content.Shared.GameTicking;
using Content.Shared.Input;
@@ -11,6 +13,7 @@ using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Input.Binding;
using Robust.Shared.Map;
+using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Players;
@@ -29,6 +32,20 @@ namespace Content.Shared.GameObjects.EntitySystems
private readonly HashSet _moving = new();
private readonly HashSet _stoppedMoving = new();
+ ///
+ /// If distance between puller and pulled entity lower that this threshold,
+ /// pulled entity will not change its rotation.
+ /// Helps with small distance jittering
+ ///
+ private const float ThresholdRotDistance = 1;
+
+ ///
+ /// If difference between puller and pulled angle lower that this threshold,
+ /// pulled entity will not change its rotation.
+ /// Helps with diagonal movement jittering
+ ///
+ private const float ThresholdRotAngle = 30;
+
public IReadOnlySet Moving => _moving;
public override void Initialize()
@@ -89,6 +106,7 @@ namespace Content.Shared.GameObjects.EntitySystems
private void PullerMoved(MoveEvent ev)
{
+ var puller = ev.Sender;
if (!TryGetPulled(ev.Sender, out var pulled))
{
return;
@@ -99,6 +117,8 @@ namespace Content.Shared.GameObjects.EntitySystems
return;
}
+ UpdatePulledRotation(puller, pulled);
+
physics.WakeBody();
if (pulled.TryGetComponent(out SharedPullableComponent? pullable))
@@ -198,5 +218,26 @@ namespace Content.Shared.GameObjects.EntitySystems
{
return _pullers.ContainsKey(puller);
}
+
+ private void UpdatePulledRotation(IEntity puller, IEntity pulled)
+ {
+ // TODO: update once ComponentReference works with directed event bus.
+ if (!pulled.TryGetComponent(out SharedRotatableComponent? rotatable))
+ return;
+
+ if (!rotatable.RotateWhilePulling)
+ return;
+
+ var dir = puller.Transform.WorldPosition - pulled.Transform.WorldPosition;
+ if (dir.LengthSquared > ThresholdRotDistance * ThresholdRotDistance)
+ {
+ var oldAngle = pulled.Transform.WorldRotation;
+ var newAngle = Angle.FromWorldVec(dir);
+
+ var diff = newAngle - oldAngle;
+ if (Math.Abs(diff.Degrees) > ThresholdRotAngle)
+ pulled.Transform.WorldRotation = newAngle;
+ }
+ }
}
}