From c15b0691ec452e543a3cc6894a28bb409dbc65f8 Mon Sep 17 00:00:00 2001 From: Jezithyr Date: Sat, 3 Feb 2024 10:32:30 -0800 Subject: [PATCH] Emergency revert for pulling (#24923) Revert "Pulling rework (#20906)" This reverts commit 0d8254b2a2891f8d5623c9878bd0e567d0c7fe3c. --- Content.Client/Alerts/ClientAlertsSystem.cs | 29 +- .../Physics/Controllers/MoverController.cs | 7 +- Content.Client/Pulling/PullingSystem.cs | 21 + .../ReplaySpectatorSystem.Blockers.cs | 2 +- .../Throwing/ThrownItemVisualizerSystem.cs | 2 +- .../Tests/Puller/PullerTest.cs | 4 +- Content.Server/Alert/Click/StopBeingPulled.cs | 8 +- Content.Server/Alert/Click/StopPulling.cs | 12 +- .../Electrocution/ElectrocutionSystem.cs | 7 +- Content.Server/Hands/Systems/HandsSystem.cs | 23 +- .../HTN/Preconditions/PulledPrecondition.cs | 5 +- .../Systems/StealConditionSystem.cs | 15 +- .../Physics/Controllers/PullController.cs | 207 ++++++++ Content.Server/Pulling/PullingSystem.cs | 48 ++ .../ArtifactInteractionTriggerSystem.cs | 4 +- .../Zombies/ZombieSystem.Transform.cs | 7 +- .../Administration/AdminFrozenSystem.cs | 15 +- .../Buckle/SharedBuckleSystem.Buckle.cs | 10 +- Content.Shared/Buckle/SharedBuckleSystem.cs | 3 +- .../EntitySystems/AnchorableSystem.cs | 15 +- Content.Shared/Cuffs/SharedCuffableSystem.cs | 14 +- Content.Shared/Follower/FollowerSystem.cs | 2 +- .../Friction/TileFrictionController.cs | 40 +- .../Interaction/SharedInteractionSystem.cs | 10 +- .../Pulling/Components/PullableComponent.cs | 39 -- .../Pulling/Components/PullerComponent.cs | 41 -- .../Pulling/Events/AttemptPullEvent.cs | 13 - .../Pulling/Events/AttemptStopPullingEvent.cs | 10 - .../Movement/Pulling/Events/PullMessage.cs | 13 - .../Pulling/Events/PullStartedMessage.cs | 9 - .../Pulling/Events/PullStoppedMessage.cs | 13 - .../Movement/Pulling/Systems/PullingSystem.cs | 455 ------------------ .../Movement/Systems/SharedMoverController.cs | 8 +- .../Pulling/Components/PullableComponent.cs | 57 +++ .../Components/SharedPullerComponent.cs | 23 + .../Pulling/Events/BeingPulledAttemptEvent.cs | 0 .../Pulling/Events/PullAttemptEvent.cs | 11 + Content.Shared/Pulling/Events/PullMessage.cs | 16 + .../Pulling/Events/PullStartedMessage.cs | 12 + .../Pulling/Events/PullStoppedMessage.cs | 11 + .../Pulling/Events/StartPullAttemptEvent.cs | 0 Content.Shared/Pulling/PullableMoveMessage.cs | 6 + .../Pulling/PullableStopMovingMessage.cs | 6 + .../Pulling/Systems/SharedPullableSystem.cs | 28 ++ .../Pulling/Systems/SharedPullerSystem.cs | 90 ++++ .../SharedPullingStateManagementSystem.cs | 196 ++++++++ .../Systems/SharedPullingSystem.Actions.cs | 239 +++++++++ .../Pulling/Systems/SharedPullingSystem.cs | 243 ++++++++++ .../Systems/DeployableBarrierSystem.cs | 10 +- .../Systems/SharedPortalSystem.cs | 16 +- Content.Shared/Throwing/ThrowingSystem.cs | 33 +- .../Throwing/ThrownItemComponent.cs | 6 - Content.Shared/Throwing/ThrownItemSystem.cs | 7 +- 53 files changed, 1358 insertions(+), 763 deletions(-) create mode 100644 Content.Client/Pulling/PullingSystem.cs create mode 100644 Content.Server/Physics/Controllers/PullController.cs create mode 100644 Content.Server/Pulling/PullingSystem.cs delete mode 100644 Content.Shared/Movement/Pulling/Components/PullableComponent.cs delete mode 100644 Content.Shared/Movement/Pulling/Components/PullerComponent.cs delete mode 100644 Content.Shared/Movement/Pulling/Events/AttemptPullEvent.cs delete mode 100644 Content.Shared/Movement/Pulling/Events/AttemptStopPullingEvent.cs delete mode 100644 Content.Shared/Movement/Pulling/Events/PullMessage.cs delete mode 100644 Content.Shared/Movement/Pulling/Events/PullStartedMessage.cs delete mode 100644 Content.Shared/Movement/Pulling/Events/PullStoppedMessage.cs delete mode 100644 Content.Shared/Movement/Pulling/Systems/PullingSystem.cs create mode 100644 Content.Shared/Pulling/Components/PullableComponent.cs create mode 100644 Content.Shared/Pulling/Components/SharedPullerComponent.cs rename Content.Shared/{Movement => }/Pulling/Events/BeingPulledAttemptEvent.cs (100%) create mode 100644 Content.Shared/Pulling/Events/PullAttemptEvent.cs create mode 100644 Content.Shared/Pulling/Events/PullMessage.cs create mode 100644 Content.Shared/Pulling/Events/PullStartedMessage.cs create mode 100644 Content.Shared/Pulling/Events/PullStoppedMessage.cs rename Content.Shared/{Movement => }/Pulling/Events/StartPullAttemptEvent.cs (100%) create mode 100644 Content.Shared/Pulling/PullableMoveMessage.cs create mode 100644 Content.Shared/Pulling/PullableStopMovingMessage.cs create mode 100644 Content.Shared/Pulling/Systems/SharedPullableSystem.cs create mode 100644 Content.Shared/Pulling/Systems/SharedPullerSystem.cs create mode 100644 Content.Shared/Pulling/Systems/SharedPullingStateManagementSystem.cs create mode 100644 Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs create mode 100644 Content.Shared/Pulling/Systems/SharedPullingSystem.cs diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs index d1ea1c32bc09df..83327ad77b5af1 100644 --- a/Content.Client/Alerts/ClientAlertsSystem.cs +++ b/Content.Client/Alerts/ClientAlertsSystem.cs @@ -4,7 +4,6 @@ using Robust.Client.Player; using Robust.Shared.Player; using Robust.Shared.Prototypes; -using Robust.Shared.Timing; namespace Content.Client.Alerts; @@ -13,7 +12,6 @@ public sealed class ClientAlertsSystem : AlertsSystem { public AlertOrderPrototype? AlertOrder { get; set; } - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -42,7 +40,7 @@ public IReadOnlyDictionary? ActiveAlerts { get { - var ent = _playerManager.LocalEntity; + var ent = _playerManager.LocalPlayer?.ControlledEntity; return ent is not null ? GetActiveAlerts(ent.Value) : null; @@ -51,28 +49,29 @@ public IReadOnlyDictionary? ActiveAlerts protected override void AfterShowAlert(Entity alerts) { - UpdateHud(alerts); - } + if (_playerManager.LocalPlayer?.ControlledEntity != alerts.Owner) + return; - protected override void AfterClearAlert(Entity alerts) - { - UpdateHud(alerts); + SyncAlerts?.Invoke(this, alerts.Comp.Alerts); } - private void ClientAlertsHandleState(Entity alerts, ref AfterAutoHandleStateEvent args) + protected override void AfterClearAlert(Entity alertsComponent) { - UpdateHud(alerts); + if (_playerManager.LocalPlayer?.ControlledEntity != alertsComponent.Owner) + return; + + SyncAlerts?.Invoke(this, alertsComponent.Comp.Alerts); } - private void UpdateHud(Entity entity) + private void ClientAlertsHandleState(EntityUid uid, AlertsComponent component, ref AfterAutoHandleStateEvent args) { - if (_playerManager.LocalEntity == entity.Owner) - SyncAlerts?.Invoke(this, entity.Comp.Alerts); + if (_playerManager.LocalPlayer?.ControlledEntity == uid) + SyncAlerts?.Invoke(this, component.Alerts); } private void OnPlayerAttached(EntityUid uid, AlertsComponent component, LocalPlayerAttachedEvent args) { - if (_playerManager.LocalEntity != uid) + if (_playerManager.LocalPlayer?.ControlledEntity != uid) return; SyncAlerts?.Invoke(this, component.Alerts); @@ -82,7 +81,7 @@ protected override void HandleComponentShutdown(EntityUid uid, AlertsComponent c { base.HandleComponentShutdown(uid, component, args); - if (_playerManager.LocalEntity != uid) + if (_playerManager.LocalPlayer?.ControlledEntity != uid) return; ClearAlerts?.Invoke(this, EventArgs.Empty); diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 9e308c8e88c3e7..763f7b01145b82 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -1,7 +1,6 @@ using Content.Shared.Movement.Components; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; -using Robust.Client.GameObjects; +using Content.Shared.Pulling.Components; using Robust.Client.Physics; using Robust.Client.Player; using Robust.Shared.Physics.Components; @@ -25,7 +24,7 @@ public override void Initialize() SubscribeLocalEvent(OnUpdatePredicted); SubscribeLocalEvent(OnUpdateRelayTargetPredicted); - SubscribeLocalEvent(OnUpdatePullablePredicted); + SubscribeLocalEvent(OnUpdatePullablePredicted); } private void OnUpdatePredicted(EntityUid uid, InputMoverComponent component, ref UpdateIsPredictedEvent args) @@ -41,7 +40,7 @@ private void OnUpdateRelayTargetPredicted(EntityUid uid, MovementRelayTargetComp args.IsPredicted = true; } - private void OnUpdatePullablePredicted(EntityUid uid, PullableComponent component, ref UpdateIsPredictedEvent args) + private void OnUpdatePullablePredicted(EntityUid uid, SharedPullableComponent component, ref UpdateIsPredictedEvent args) { // Enable prediction if an entity is being pulled by the player. // Disable prediction if an entity is being pulled by some non-player entity. diff --git a/Content.Client/Pulling/PullingSystem.cs b/Content.Client/Pulling/PullingSystem.cs new file mode 100644 index 00000000000000..556dadd00dad7d --- /dev/null +++ b/Content.Client/Pulling/PullingSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; +using JetBrains.Annotations; +using Robust.Client.Physics; + +namespace Content.Client.Pulling +{ + [UsedImplicitly] + public sealed class PullingSystem : SharedPullingSystem + { + public override void Initialize() + { + base.Initialize(); + + UpdatesAfter.Add(typeof(PhysicsSystem)); + + SubscribeLocalEvent(OnPullableMove); + SubscribeLocalEvent(OnPullableStopMove); + } + } +} diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs index 2fa862f3df7f19..86d113defb1e52 100644 --- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs +++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs @@ -3,7 +3,7 @@ using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Movement.Events; -using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Physics.Pull; using Content.Shared.Throwing; namespace Content.Client.Replay.Spectator; diff --git a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs index b25b4fbb7deffc..bbd36731104bac 100644 --- a/Content.Client/Throwing/ThrownItemVisualizerSystem.cs +++ b/Content.Client/Throwing/ThrownItemVisualizerSystem.cs @@ -24,7 +24,7 @@ public override void Initialize() private void OnAutoHandleState(EntityUid uid, ThrownItemComponent component, ref AfterAutoHandleStateEvent args) { - if (!TryComp(uid, out var sprite) || !component.Animate) + if (!TryComp(uid, out var sprite)) return; var animationPlayer = EnsureComp(uid); diff --git a/Content.IntegrationTests/Tests/Puller/PullerTest.cs b/Content.IntegrationTests/Tests/Puller/PullerTest.cs index 87d174f7272272..ba91f54ff77512 100644 --- a/Content.IntegrationTests/Tests/Puller/PullerTest.cs +++ b/Content.IntegrationTests/Tests/Puller/PullerTest.cs @@ -1,6 +1,6 @@ using Content.Shared.Hands.Components; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Prototypes; +using Content.Shared.Pulling.Components; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -29,7 +29,7 @@ await server.WaitAssertion(() => { foreach (var proto in protoManager.EnumeratePrototypes()) { - if (!proto.TryGetComponent(out PullerComponent? puller)) + if (!proto.TryGetComponent(out SharedPullerComponent? puller)) continue; if (!puller.NeedsHands) diff --git a/Content.Server/Alert/Click/StopBeingPulled.cs b/Content.Server/Alert/Click/StopBeingPulled.cs index b02da38ecfa718..2cf076fbeed236 100644 --- a/Content.Server/Alert/Click/StopBeingPulled.cs +++ b/Content.Server/Alert/Click/StopBeingPulled.cs @@ -1,7 +1,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Alert; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Pulling.Components; +using Content.Shared.Pulling; using JetBrains.Annotations; namespace Content.Server.Alert.Click @@ -20,9 +20,9 @@ public void AlertClicked(EntityUid player) if (!entityManager.System().CanInteract(player, null)) return; - if (entityManager.TryGetComponent(player, out PullableComponent? playerPullable)) + if (entityManager.TryGetComponent(player, out SharedPullableComponent? playerPullable)) { - entityManager.System().TryStopPull(player, playerPullable, user: player); + entityManager.System().TryStopPull(playerPullable); } } } diff --git a/Content.Server/Alert/Click/StopPulling.cs b/Content.Server/Alert/Click/StopPulling.cs index 76f9569429f987..00a4149598517e 100644 --- a/Content.Server/Alert/Click/StopPulling.cs +++ b/Content.Server/Alert/Click/StopPulling.cs @@ -1,6 +1,6 @@ using Content.Shared.Alert; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; using JetBrains.Annotations; namespace Content.Server.Alert.Click @@ -15,12 +15,12 @@ public sealed partial class StopPulling : IAlertClick public void AlertClicked(EntityUid player) { var entManager = IoCManager.Resolve(); - var ps = entManager.System(); - if (entManager.TryGetComponent(player, out PullerComponent? puller) && - entManager.TryGetComponent(puller.Pulling, out PullableComponent? pullableComp)) + var ps = entManager.System(); + var playerTarget = ps.GetPulled(player); + if (playerTarget != default && entManager.TryGetComponent(playerTarget, out SharedPullableComponent? playerPullable)) { - ps.TryStopPull(puller.Pulling.Value, pullableComp, user: player); + ps.TryStopPull(playerPullable); } } } diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 591fd825b48d6b..aac500f34f8b4e 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -19,6 +19,7 @@ using Content.Shared.Maps; using Content.Shared.Mobs; using Content.Shared.Popups; +using Content.Shared.Pulling.Components; using Content.Shared.Speech.EntitySystems; using Content.Shared.StatusEffect; using Content.Shared.Stunnable; @@ -31,8 +32,6 @@ using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent; -using PullerComponent = Content.Shared.Movement.Pulling.Components.PullerComponent; namespace Content.Server.Electrocution; @@ -466,14 +465,14 @@ private void GetChainedElectrocutionTargetsRecurse( all.Add((entity, depth)); visited.Add(entity); - if (TryComp(entity, out var pullable) && + if (TryComp(entity, out var pullable) && pullable.Puller is { Valid: true } pullerId && !visited.Contains(pullerId)) { GetChainedElectrocutionTargetsRecurse(pullerId, depth + 1, visited, all); } - if (TryComp(entity, out var puller) && + if (TryComp(entity, out var puller) && puller.Pulling is { Valid: true } pullingId && !visited.Contains(pullingId)) { diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index 7c48ee089eeac5..9d664d0f9a0290 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Server.Inventory; +using Content.Server.Pulling; using Content.Server.Stack; using Content.Server.Stunnable; using Content.Shared.ActionBlocker; @@ -10,9 +11,8 @@ using Content.Shared.Hands.EntitySystems; using Content.Shared.Input; using Content.Shared.Inventory.VirtualItem; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Events; -using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling.Components; using Content.Shared.Stacks; using Content.Shared.Throwing; using Robust.Shared.GameStates; @@ -88,8 +88,9 @@ private void OnDisarmed(EntityUid uid, HandsComponent component, DisarmedEvent a return; // Break any pulls - if (TryComp(uid, out PullerComponent? puller) && TryComp(puller.Pulling, out PullableComponent? pullable)) - _pullingSystem.TryStopPull(puller.Pulling.Value, pullable); + if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is EntityUid pulled && + TryComp(pulled, out SharedPullableComponent? pullable)) + _pullingSystem.TryStopPull(pullable); if (!_handsSystem.TryDrop(uid, component.ActiveHand!, null, checkActionBlocker: false)) return; @@ -127,13 +128,13 @@ private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) { - if (args.PullerUid != uid) + if (args.Puller.Owner != uid) return; - if (TryComp(args.PullerUid, out var pullerComp) && !pullerComp.NeedsHands) + if (TryComp(args.Puller.Owner, out var pullerComp) && !pullerComp.NeedsHands) return; - if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.PulledUid, uid)) + if (!_virtualItemSystem.TrySpawnVirtualItemInHand(args.Pulled.Owner, uid)) { DebugTools.Assert("Unable to find available hand when starting pulling??"); } @@ -141,7 +142,7 @@ private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStar private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args) { - if (args.PullerUid != uid) + if (args.Puller.Owner != uid) return; // Try find hand that is doing this pull. @@ -150,10 +151,8 @@ private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStop { if (hand.HeldEntity == null || !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem) - || virtualItem.BlockingEntity != args.PulledUid) - { + || virtualItem.BlockingEntity != args.Pulled.Owner) continue; - } QueueDel(hand.HeldEntity.Value); break; diff --git a/Content.Server/NPC/HTN/Preconditions/PulledPrecondition.cs b/Content.Server/NPC/HTN/Preconditions/PulledPrecondition.cs index d276be721870f4..64a72b13cfae75 100644 --- a/Content.Server/NPC/HTN/Preconditions/PulledPrecondition.cs +++ b/Content.Server/NPC/HTN/Preconditions/PulledPrecondition.cs @@ -1,5 +1,4 @@ using Content.Shared.Pulling; -using PullingSystem = Content.Shared.Movement.Pulling.Systems.PullingSystem; namespace Content.Server.NPC.HTN.Preconditions; @@ -8,14 +7,14 @@ namespace Content.Server.NPC.HTN.Preconditions; /// public sealed partial class PulledPrecondition : HTNPrecondition { - private PullingSystem _pulling = default!; + private SharedPullingSystem _pulling = default!; [ViewVariables(VVAccess.ReadWrite)] [DataField("isPulled")] public bool IsPulled = true; public override void Initialize(IEntitySystemManager sysManager) { base.Initialize(sysManager); - _pulling = sysManager.GetEntitySystem(); + _pulling = sysManager.GetEntitySystem(); } public override bool IsMet(NPCBlackboard blackboard) diff --git a/Content.Server/Objectives/Systems/StealConditionSystem.cs b/Content.Server/Objectives/Systems/StealConditionSystem.cs index 0fe6f0947c876b..02d4ee010b5883 100644 --- a/Content.Server/Objectives/Systems/StealConditionSystem.cs +++ b/Content.Server/Objectives/Systems/StealConditionSystem.cs @@ -6,10 +6,11 @@ using Robust.Shared.Containers; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Content.Shared.Pulling.Components; +using Content.Shared.Objectives; using Content.Shared.Mind.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Components; -using Content.Shared.Movement.Pulling.Components; namespace Content.Server.Objectives.Systems; @@ -99,19 +100,19 @@ private float GetProgress(MindComponent mind, StealConditionComponent condition) var count = 0; //check pulling object - if (TryComp(mind.OwnedEntity, out var pull)) //TO DO: to make the code prettier? don't like the repetition + if (TryComp(mind.OwnedEntity, out var pull)) //TO DO: to make the code prettier? don't like the repetition { - var pulledEntity = pull.Pulling; - if (pulledEntity != null) + var pullid = pull.Pulling; + if (pullid != null) { // check if this is the item - if (CheckStealTarget(pulledEntity.Value, condition)) count++; + if (CheckStealTarget(pullid.Value, condition)) count++; //we don't check the inventories of sentient entity - if (!HasComp(pulledEntity)) + if (!TryComp(pullid, out var pullMind)) { // if it is a container check its contents - if (_containerQuery.TryGetComponent(pulledEntity, out var containerManager)) + if (_containerQuery.TryGetComponent(pullid, out var containerManager)) stack.Push(containerManager); } } diff --git a/Content.Server/Physics/Controllers/PullController.cs b/Content.Server/Physics/Controllers/PullController.cs new file mode 100644 index 00000000000000..8f58f807aaee53 --- /dev/null +++ b/Content.Server/Physics/Controllers/PullController.cs @@ -0,0 +1,207 @@ +using System.Numerics; +using Content.Shared.ActionBlocker; +using Content.Shared.Gravity; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; +using Content.Shared.Rotatable; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Controllers; + +namespace Content.Server.Physics.Controllers +{ + public sealed class PullController : VirtualController + { + // Parameterization for pulling: + // Speeds. Note that the speed is mass-independent (multiplied by mass). + // Instead, tuning to mass is done via the mass values below. + // Note that setting the speed too high results in overshoots (stabilized by drag, but bad) + private const float AccelModifierHigh = 15f; + private const float AccelModifierLow = 60.0f; + // High/low-mass marks. Curve is constant-lerp-constant, i.e. if you can even pull an item, + // you'll always get at least AccelModifierLow and no more than AccelModifierHigh. + private const float AccelModifierHighMass = 70.0f; // roundstart saltern emergency closet + private const float AccelModifierLowMass = 5.0f; // roundstart saltern emergency crowbar + // Used to control settling (turns off pulling). + private const float MaximumSettleVelocity = 0.1f; + private const float MaximumSettleDistance = 0.1f; + // Settle shutdown control. + // Mustn't be too massive, as that causes severe mispredicts *and can prevent it ever resolving*. + // Exists to bleed off "I pulled my crowbar" overshoots. + // Minimum velocity for shutdown to be necessary. This prevents stuff getting stuck b/c too much shutdown. + private const float SettleMinimumShutdownVelocity = 0.25f; + // Distance in which settle shutdown multiplier is at 0. It then scales upwards linearly with closer distances. + private const float SettleShutdownDistance = 1.0f; + // Velocity change of -LinearVelocity * frameTime * this + private const float SettleShutdownMultiplier = 20.0f; + + // How much you must move for the puller movement check to actually hit. + private const float MinimumMovementDistance = 0.005f; + + [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly SharedPullingSystem _pullableSystem = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + // TODO: Move this stuff to pullingsystem + /// + /// 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 + /// As of further adjustments, should divide cleanly into 90 degrees + /// + private const float ThresholdRotAngle = 22.5f; + + public override void Initialize() + { + UpdatesAfter.Add(typeof(MoverController)); + SubscribeLocalEvent(OnPullerMove); + + base.Initialize(); + } + + private void OnPullerMove(EntityUid uid, SharedPullerComponent component, ref MoveEvent args) + { + if (component.Pulling is not { } pullable || !TryComp(pullable, out var pullableComponent)) + return; + + UpdatePulledRotation(uid, pullable); + + if (args.NewPosition.EntityId == args.OldPosition.EntityId && + (args.NewPosition.Position - args.OldPosition.Position).LengthSquared() < MinimumMovementDistance * MinimumMovementDistance) + return; + + if (TryComp(pullable, out var physics)) + PhysicsSystem.WakeBody(pullable, body: physics); + + _pullableSystem.StopMoveTo(pullableComponent); + } + + private void UpdatePulledRotation(EntityUid puller, EntityUid pulled) + { + // TODO: update once ComponentReference works with directed event bus. + if (!TryComp(pulled, out RotatableComponent? rotatable)) + return; + + if (!rotatable.RotateWhilePulling) + return; + + var xforms = GetEntityQuery(); + var pulledXform = xforms.GetComponent(pulled); + var pullerXform = xforms.GetComponent(puller); + + var pullerData = TransformSystem.GetWorldPositionRotation(pullerXform, xforms); + var pulledData = TransformSystem.GetWorldPositionRotation(pulledXform, xforms); + + var dir = pullerData.WorldPosition - pulledData.WorldPosition; + if (dir.LengthSquared() > ThresholdRotDistance * ThresholdRotDistance) + { + var oldAngle = pulledData.WorldRotation; + var newAngle = Angle.FromWorldVec(dir); + + var diff = newAngle - oldAngle; + if (Math.Abs(diff.Degrees) > ThresholdRotAngle / 2f) + { + // Ok, so this bit is difficult because ideally it would look like it's snapping to sane angles. + // Otherwise PIANO DOOR STUCK! happens. + // But it also needs to work with station rotation / align to the local parent. + // So... + var baseRotation = pulledData.WorldRotation - pulledXform.LocalRotation; + var localRotation = newAngle - baseRotation; + var localRotationSnapped = Angle.FromDegrees(Math.Floor((localRotation.Degrees / ThresholdRotAngle) + 0.5f) * ThresholdRotAngle); + TransformSystem.SetLocalRotation(pulledXform, localRotationSnapped); + } + } + } + + public override void UpdateBeforeSolve(bool prediction, float frameTime) + { + base.UpdateBeforeSolve(prediction, frameTime); + + foreach (var pullable in _pullableSystem.Moving) + { + // There's a 1-frame delay between stopping moving something and it leaving the Moving set. + // This can include if leaving the Moving set due to not being pulled anymore, + // or due to being deleted. + + if (pullable.Deleted) + continue; + + if (pullable.MovingTo == null) + continue; + + if (pullable.Puller is not {Valid: true} puller) + continue; + + var pullableEnt = pullable.Owner; + var pullableXform = Transform(pullableEnt); + var pullerXform = Transform(puller); + + // Now that's over with... + + var pullerPosition = pullerXform.MapPosition; + var movingTo = pullable.MovingTo.Value.ToMap(EntityManager, _transform); + if (movingTo.MapId != pullerPosition.MapId) + { + _pullableSystem.StopMoveTo(pullable); + continue; + } + + if (!TryComp(pullableEnt, out var physics) || + physics.BodyType == BodyType.Static || + movingTo.MapId != pullableXform.MapID) + { + _pullableSystem.StopMoveTo(pullable); + continue; + } + + var movingPosition = movingTo.Position; + var ownerPosition = pullableXform.MapPosition.Position; + + var diff = movingPosition - ownerPosition; + var diffLength = diff.Length(); + + if (diffLength < MaximumSettleDistance && physics.LinearVelocity.Length() < MaximumSettleVelocity) + { + PhysicsSystem.SetLinearVelocity(pullableEnt, Vector2.Zero, body: physics); + _pullableSystem.StopMoveTo(pullable); + continue; + } + + var impulseModifierLerp = Math.Min(1.0f, Math.Max(0.0f, (physics.Mass - AccelModifierLowMass) / (AccelModifierHighMass - AccelModifierLowMass))); + var impulseModifier = MathHelper.Lerp(AccelModifierLow, AccelModifierHigh, impulseModifierLerp); + var multiplier = diffLength < 1 ? impulseModifier * diffLength : impulseModifier; + // Note the implication that the real rules of physics don't apply to pulling control. + var accel = diff.Normalized() * multiplier; + // Now for the part where velocity gets shutdown... + if (diffLength < SettleShutdownDistance && physics.LinearVelocity.Length() >= SettleMinimumShutdownVelocity) + { + // Shutdown velocity increases as we get closer to centre + var scaling = (SettleShutdownDistance - diffLength) / SettleShutdownDistance; + accel -= physics.LinearVelocity * SettleShutdownMultiplier * scaling; + } + + PhysicsSystem.WakeBody(pullableEnt, body: physics); + + var impulse = accel * physics.Mass * frameTime; + PhysicsSystem.ApplyLinearImpulse(pullableEnt, impulse, body: physics); + + // if the puller is weightless or can't move, then we apply the inverse impulse (Newton's third law). + // doing it under gravity produces an unsatisfying wiggling when pulling. + // If player can't move, assume they are on a chair and we need to prevent pull-moving. + if ((_gravity.IsWeightless(puller) && pullerXform.GridUid == null) || !_actionBlockerSystem.CanMove(puller)) + { + PhysicsSystem.WakeBody(puller); + PhysicsSystem.ApplyLinearImpulse(puller, -impulse); + } + } + } + } +} diff --git a/Content.Server/Pulling/PullingSystem.cs b/Content.Server/Pulling/PullingSystem.cs new file mode 100644 index 00000000000000..69bb7c93704ede --- /dev/null +++ b/Content.Server/Pulling/PullingSystem.cs @@ -0,0 +1,48 @@ +using Content.Shared.Input; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Shared.Input.Binding; +using Robust.Shared.Player; + +namespace Content.Server.Pulling +{ + [UsedImplicitly] + public sealed class PullingSystem : SharedPullingSystem + { + public override void Initialize() + { + base.Initialize(); + + UpdatesAfter.Add(typeof(PhysicsSystem)); + + SubscribeLocalEvent(OnPullableMove); + SubscribeLocalEvent(OnPullableStopMove); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject)) + .Register(); + } + + private void HandleReleasePulledObject(ICommonSession? session) + { + if (session?.AttachedEntity is not {Valid: true} player) + { + return; + } + + if (!TryGetPulled(player, out var pulled)) + { + return; + } + + if (!EntityManager.TryGetComponent(pulled.Value, out SharedPullableComponent? pullable)) + { + return; + } + + TryStopPull(pullable); + } + } +} diff --git a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs index 9976d56da0b294..239b6741608171 100644 --- a/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs +++ b/Content.Server/Xenoarchaeology/XenoArtifacts/Triggers/Systems/ArtifactInteractionTriggerSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components; using Content.Shared.Interaction; -using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Physics.Pull; using Content.Shared.Weapons.Melee.Events; namespace Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Systems; @@ -22,7 +22,7 @@ private void OnPull(EntityUid uid, ArtifactInteractionTriggerComponent component if (!component.PullActivation) return; - _artifactSystem.TryActivateArtifact(uid, args.PullerUid); + _artifactSystem.TryActivateArtifact(uid, args.Puller.Owner); } private void OnAttack(EntityUid uid, ArtifactInteractionTriggerComponent component, AttackedEvent args) diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index eadeb463a52b70..386a7c6419b9ad 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -24,12 +24,12 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; using Content.Shared.Nutrition.AnimalHusbandry; using Content.Shared.Nutrition.Components; using Content.Shared.Popups; using Content.Shared.Roles; +using Content.Shared.Pulling.Components; using Content.Shared.Weapons.Melee; using Content.Shared.Zombies; using Content.Shared.Prying.Components; @@ -57,6 +57,7 @@ public sealed partial class ZombieSystem [Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; /// @@ -262,9 +263,7 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) RemComp(target, handsComp); } - // Sloth: What the fuck? - // How long until compregistry lmao. - RemComp(target); + RemComp(target); // No longer waiting to become a zombie: // Requires deferral because this is (probably) the event which called ZombifyEntity in the first place. diff --git a/Content.Shared/Administration/AdminFrozenSystem.cs b/Content.Shared/Administration/AdminFrozenSystem.cs index 4ec9600b0bdce2..14438cc59120cc 100644 --- a/Content.Shared/Administration/AdminFrozenSystem.cs +++ b/Content.Shared/Administration/AdminFrozenSystem.cs @@ -1,10 +1,13 @@ using Content.Shared.ActionBlocker; using Content.Shared.Interaction.Events; using Content.Shared.Item; +using Content.Shared.Movement; using Content.Shared.Movement.Events; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Events; -using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; +using Content.Shared.Pulling.Events; +using Content.Shared.Stunnable; using Content.Shared.Throwing; namespace Content.Shared.Administration; @@ -12,7 +15,7 @@ namespace Content.Shared.Administration; public sealed class AdminFrozenSystem : EntitySystem { [Dependency] private readonly ActionBlockerSystem _blocker = default!; - [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedPullingSystem _pulling = default!; public override void Initialize() { @@ -42,9 +45,9 @@ private void OnPullAttempt(EntityUid uid, AdminFrozenComponent component, PullAt private void OnStartup(EntityUid uid, AdminFrozenComponent component, ComponentStartup args) { - if (TryComp(uid, out var pullable)) + if (TryComp(uid, out var pullable)) { - _pulling.TryStopPull(uid, pullable); + _pulling.TryStopPull(pullable); } UpdateCanMove(uid, component, args); diff --git a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs index 7c85c5a63117fa..731b2892aa87bc 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs @@ -11,6 +11,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Movement.Events; using Content.Shared.Popups; +using Content.Shared.Pulling.Components; using Content.Shared.Standing; using Content.Shared.Storage.Components; using Content.Shared.Stunnable; @@ -19,7 +20,6 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Utility; -using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent; namespace Content.Shared.Buckle; @@ -356,11 +356,11 @@ public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid RaiseLocalEvent(ev.BuckledEntity, ref ev); RaiseLocalEvent(ev.StrapEntity, ref ev); - if (TryComp(buckleUid, out var ownerPullable)) + if (TryComp(buckleUid, out var ownerPullable)) { if (ownerPullable.Puller != null) { - _pulling.TryStopPull(buckleUid, ownerPullable); + _pulling.TryStopPull(ownerPullable); } } @@ -369,12 +369,12 @@ public bool TryBuckle(EntityUid buckleUid, EntityUid userUid, EntityUid strapUid _physics.ResetDynamics(physics); } - if (!buckleComp.PullStrap && TryComp(strapUid, out var toPullable)) + if (!buckleComp.PullStrap && TryComp(strapUid, out var toPullable)) { if (toPullable.Puller == buckleUid) { // can't pull it and buckle to it at the same time - _pulling.TryStopPull(strapUid, toPullable); + _pulling.TryStopPull(toPullable); } } diff --git a/Content.Shared/Buckle/SharedBuckleSystem.cs b/Content.Shared/Buckle/SharedBuckleSystem.cs index 67218657e52e99..8f6833566374da 100644 --- a/Content.Shared/Buckle/SharedBuckleSystem.cs +++ b/Content.Shared/Buckle/SharedBuckleSystem.cs @@ -15,7 +15,6 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Player; using Robust.Shared.Timing; -using PullingSystem = Content.Shared.Movement.Pulling.Systems.PullingSystem; namespace Content.Shared.Buckle; @@ -36,7 +35,7 @@ public abstract partial class SharedBuckleSystem : EntitySystem [Dependency] private readonly SharedInteractionSystem _interaction = default!; [Dependency] private readonly SharedJointSystem _joints = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedPullingSystem _pulling = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly StandingStateSystem _standing = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index cc21507461f4e1..b40c0495622821 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -6,15 +6,16 @@ using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.Interaction; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Popups; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; using Content.Shared.Tools; using Content.Shared.Tools.Components; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics.Components; using Content.Shared.Tag; +using Robust.Shared.Player; using Robust.Shared.Serialization; using Robust.Shared.Utility; using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem; @@ -26,7 +27,7 @@ public sealed partial class AnchorableSystem : EntitySystem [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; - [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedPullingSystem _pulling = default!; [Dependency] private readonly SharedToolSystem _tool = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; @@ -128,9 +129,9 @@ private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryA var rot = xform.LocalRotation; xform.LocalRotation = Math.Round(rot / (Math.PI / 2)) * (Math.PI / 2); - if (TryComp(uid, out var pullable) && pullable.Puller != null) + if (TryComp(uid, out var pullable) && pullable.Puller != null) { - _pulling.TryStopPull(uid, pullable); + _pulling.TryStopPull(pullable); } // TODO: Anchoring snaps rn anyway! @@ -171,7 +172,7 @@ private void OnAnchorComplete(EntityUid uid, AnchorableComponent component, TryA public void TryToggleAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, AnchorableComponent? anchorable = null, TransformComponent? transform = null, - PullableComponent? pullable = null, + SharedPullableComponent? pullable = null, ToolComponent? usingTool = null) { if (!Resolve(uid, ref transform)) @@ -200,7 +201,7 @@ public void TryToggleAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid private void TryAnchor(EntityUid uid, EntityUid userUid, EntityUid usingUid, AnchorableComponent? anchorable = null, TransformComponent? transform = null, - PullableComponent? pullable = null, + SharedPullableComponent? pullable = null, ToolComponent? usingTool = null) { if (!Resolve(uid, ref anchorable, ref transform)) diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index 7118b8e5cbcfeb..99657c87aabe50 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -22,8 +22,9 @@ using Content.Shared.Item; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Events; -using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Physics.Pull; using Content.Shared.Popups; +using Content.Shared.Pulling.Components; using Content.Shared.Pulling.Events; using Content.Shared.Rejuvenate; using Content.Shared.Stunnable; @@ -35,7 +36,6 @@ using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Serialization; -using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent; namespace Content.Shared.Cuffs { @@ -70,7 +70,7 @@ public override void Initialize() SubscribeLocalEvent(OnCuffsInsertedIntoContainer); SubscribeLocalEvent(OnRejuvenate); SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(HandleStopPull); + SubscribeLocalEvent(HandleStopPull); SubscribeLocalEvent(HandleMoveAttempt); SubscribeLocalEvent(OnEquipAttempt); SubscribeLocalEvent(OnUnequipAttempt); @@ -182,7 +182,7 @@ public void UpdateCuffState(EntityUid uid, CuffableComponent component) private void OnBeingPulledAttempt(EntityUid uid, CuffableComponent component, BeingPulledAttemptEvent args) { - if (!TryComp(uid, out var pullable)) + if (!TryComp(uid, out var pullable)) return; if (pullable.Puller != null && !component.CanStillInteract) // If we are being pulled already and cuffed, we can't get pulled again. @@ -214,19 +214,19 @@ private void OnPull(EntityUid uid, CuffableComponent component, PullMessage args private void HandleMoveAttempt(EntityUid uid, CuffableComponent component, UpdateCanMoveEvent args) { - if (component.CanStillInteract || !EntityManager.TryGetComponent(uid, out PullableComponent? pullable) || !pullable.BeingPulled) + if (component.CanStillInteract || !EntityManager.TryGetComponent(uid, out SharedPullableComponent? pullable) || !pullable.BeingPulled) return; args.Cancel(); } - private void HandleStopPull(EntityUid uid, CuffableComponent component, AttemptStopPullingEvent args) + private void HandleStopPull(EntityUid uid, CuffableComponent component, StopPullingEvent args) { if (args.User == null || !Exists(args.User.Value)) return; if (args.User.Value == uid && !component.CanStillInteract) - args.Cancelled = true; + args.Cancel(); } private void AddUncuffVerb(EntityUid uid, CuffableComponent component, GetVerbsEvent args) diff --git a/Content.Shared/Follower/FollowerSystem.cs b/Content.Shared/Follower/FollowerSystem.cs index fc7cccf9bd6ade..5656778a3f9448 100644 --- a/Content.Shared/Follower/FollowerSystem.cs +++ b/Content.Shared/Follower/FollowerSystem.cs @@ -4,8 +4,8 @@ using Content.Shared.Ghost; using Content.Shared.Hands; using Content.Shared.Movement.Events; -using Content.Shared.Movement.Pulling.Events; using Content.Shared.Movement.Systems; +using Content.Shared.Physics.Pull; using Content.Shared.Tag; using Content.Shared.Verbs; using Robust.Shared.Containers; diff --git a/Content.Shared/Friction/TileFrictionController.cs b/Content.Shared/Friction/TileFrictionController.cs index ae9f32819a5402..472f5b574b2a52 100644 --- a/Content.Shared/Friction/TileFrictionController.cs +++ b/Content.Shared/Friction/TileFrictionController.cs @@ -2,8 +2,8 @@ using Content.Shared.CCVar; using Content.Shared.Gravity; using Content.Shared.Movement.Events; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Systems; +using Content.Shared.Pulling.Components; using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.Map; @@ -23,12 +23,6 @@ public sealed class TileFrictionController : VirtualController [Dependency] private readonly SharedMoverController _mover = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; - private EntityQuery _frictionQuery; - private EntityQuery _xformQuery; - private EntityQuery _pullerQuery; - private EntityQuery _pullableQuery; - private EntityQuery _gridQuery; - private float _stopSpeed; private float _frictionModifier; public const float DefaultFriction = 0.3f; @@ -37,12 +31,6 @@ public override void Initialize() { base.Initialize(); - _frictionQuery = GetEntityQuery(); - _xformQuery = GetEntityQuery(); - _pullerQuery = GetEntityQuery(); - _pullableQuery = GetEntityQuery(); - _gridQuery = GetEntityQuery(); - _configManager.OnValueChanged(CCVars.TileFrictionModifier, SetFrictionModifier, true); _configManager.OnValueChanged(CCVars.StopSpeed, SetStopSpeed, true); } @@ -63,6 +51,12 @@ public override void UpdateBeforeMapSolve(bool prediction, PhysicsMapComponent m { base.UpdateBeforeMapSolve(prediction, mapComponent, frameTime); + var frictionQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + var pullerQuery = GetEntityQuery(); + var pullableQuery = GetEntityQuery(); + var gridQuery = GetEntityQuery(); + foreach (var body in mapComponent.AwakeBodies) { var uid = body.Owner; @@ -78,16 +72,16 @@ public override void UpdateBeforeMapSolve(bool prediction, PhysicsMapComponent m if (body.LinearVelocity.Equals(Vector2.Zero) && body.AngularVelocity.Equals(0f)) continue; - if (!_xformQuery.TryGetComponent(uid, out var xform)) + if (!xformQuery.TryGetComponent(uid, out var xform)) { Log.Error($"Unable to get transform for {ToPrettyString(uid)} in tilefrictioncontroller"); continue; } - var surfaceFriction = GetTileFriction(uid, body, xform); + var surfaceFriction = GetTileFriction(uid, body, xform, gridQuery, frictionQuery); var bodyModifier = 1f; - if (_frictionQuery.TryGetComponent(uid, out var frictionComp)) + if (frictionQuery.TryGetComponent(uid, out var frictionComp)) { bodyModifier = frictionComp.Modifier; } @@ -100,8 +94,8 @@ public override void UpdateBeforeMapSolve(bool prediction, PhysicsMapComponent m // If we're sandwiched between 2 pullers reduce friction // Might be better to make this dynamic and check how many are in the pull chain? // Either way should be much faster for now. - if (_pullerQuery.TryGetComponent(uid, out var puller) && puller.Pulling != null && - _pullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled) + if (pullerQuery.TryGetComponent(uid, out var puller) && puller.Pulling != null && + pullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled) { bodyModifier *= 0.2f; } @@ -181,7 +175,9 @@ private void ReduceAngularVelocity(EntityUid uid, bool prediction, PhysicsCompon private float GetTileFriction( EntityUid uid, PhysicsComponent body, - TransformComponent xform) + TransformComponent xform, + EntityQuery gridQuery, + EntityQuery frictionQuery) { // TODO: Make IsWeightless event-based; we already have grid traversals tracked so just raise events if (_gravity.IsWeightless(uid, body, xform)) @@ -191,9 +187,9 @@ private float GetTileFriction( return 0.0f; // If not on a grid then return the map's friction. - if (!_gridQuery.TryGetComponent(xform.GridUid, out var grid)) + if (!gridQuery.TryGetComponent(xform.GridUid, out var grid)) { - return _frictionQuery.TryGetComponent(xform.MapUid, out var friction) + return frictionQuery.TryGetComponent(xform.MapUid, out var friction) ? friction.Modifier : DefaultFriction; } @@ -213,7 +209,7 @@ private float GetTileFriction( while (anc.MoveNext(out var tileEnt)) { - if (_frictionQuery.TryGetComponent(tileEnt, out var friction)) + if (frictionQuery.TryGetComponent(tileEnt, out var friction)) return friction.Modifier; } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 29cf8d6d12f238..75063c55503fcd 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -15,10 +15,10 @@ using Content.Shared.Inventory.Events; using Content.Shared.Item; using Content.Shared.Movement.Components; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Physics; using Content.Shared.Popups; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; using Content.Shared.Tag; using Content.Shared.Timing; using Content.Shared.Verbs; @@ -60,7 +60,7 @@ public abstract partial class SharedInteractionSystem : EntitySystem [Dependency] private readonly SharedVerbSystem _verbSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly UseDelaySystem _useDelay = default!; - [Dependency] private readonly PullingSystem _pullSystem = default!; + [Dependency] private readonly SharedPullingSystem _pullSystem = default!; [Dependency] private readonly InventorySystem _inventory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly TagSystem _tagSystem = default!; @@ -185,10 +185,10 @@ private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coor if (!InRangeUnobstructed(userEntity.Value, uid, popup: true)) return false; - if (!TryComp(uid, out PullableComponent? pull)) + if (!TryComp(uid, out SharedPullableComponent? pull)) return false; - _pullSystem.TogglePull(uid, userEntity.Value, pull); + _pullSystem.TogglePull(userEntity.Value, pull); return false; } diff --git a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs b/Content.Shared/Movement/Pulling/Components/PullableComponent.cs deleted file mode 100644 index db889e7e3bd9e4..00000000000000 --- a/Content.Shared/Movement/Pulling/Components/PullableComponent.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Movement.Pulling.Components; - -/// -/// Specifies an entity as being pullable by an entity with -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -[Access(typeof(Systems.PullingSystem))] -public sealed partial class PullableComponent : Component -{ - /// - /// The current entity pulling this component. - /// - [AutoNetworkedField, DataField] - public EntityUid? Puller; - - /// - /// The pull joint. - /// - [AutoNetworkedField, DataField] - public string? PullJointId; - - public bool BeingPulled => Puller != null; - - /// - /// If the physics component has FixedRotation should we keep it upon being pulled - /// - [Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)] - [ViewVariables(VVAccess.ReadWrite), DataField("fixedRotation")] - public bool FixedRotationOnPull; - - /// - /// What the pullable's fixedrotation was set to before being pulled. - /// - [Access(typeof(Systems.PullingSystem), Other = AccessPermissions.ReadExecute)] - [AutoNetworkedField, DataField] - public bool PrevFixedRotation; -} diff --git a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs b/Content.Shared/Movement/Pulling/Components/PullerComponent.cs deleted file mode 100644 index 1fc9b731bd5ff8..00000000000000 --- a/Content.Shared/Movement/Pulling/Components/PullerComponent.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Content.Shared.Movement.Pulling.Systems; -using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; - -namespace Content.Shared.Movement.Pulling.Components; - -/// -/// Specifies an entity as being able to pull another entity with -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -[Access(typeof(PullingSystem))] -public sealed partial class PullerComponent : Component -{ - // My raiding guild - /// - /// Next time the puller can throw what is being pulled. - /// Used to avoid spamming it for infinite spin + velocity. - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField] - public TimeSpan NextThrow; - - [DataField] - public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(1); - - // Before changing how this is updated, please see SharedPullerSystem.RefreshMovementSpeed - public float WalkSpeedModifier => Pulling == default ? 1.0f : 0.95f; - - public float SprintSpeedModifier => Pulling == default ? 1.0f : 0.95f; - - /// - /// Entity currently being pulled if applicable. - /// - [AutoNetworkedField, DataField] - public EntityUid? Pulling; - - /// - /// Does this entity need hands to be able to pull something? - /// - [DataField] - public bool NeedsHands = true; -} diff --git a/Content.Shared/Movement/Pulling/Events/AttemptPullEvent.cs b/Content.Shared/Movement/Pulling/Events/AttemptPullEvent.cs deleted file mode 100644 index b0101c46996f18..00000000000000 --- a/Content.Shared/Movement/Pulling/Events/AttemptPullEvent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Physics.Components; - -namespace Content.Shared.Movement.Pulling.Events; - -/// -/// Raised directed on puller and pullable to determine if it can be pulled. -/// -public sealed class PullAttemptEvent : PullMessage -{ - public PullAttemptEvent(EntityUid pullerUid, EntityUid pullableUid) : base(pullerUid, pullableUid) { } - - public bool Cancelled { get; set; } -} diff --git a/Content.Shared/Movement/Pulling/Events/AttemptStopPullingEvent.cs b/Content.Shared/Movement/Pulling/Events/AttemptStopPullingEvent.cs deleted file mode 100644 index cd7edc5f623fe4..00000000000000 --- a/Content.Shared/Movement/Pulling/Events/AttemptStopPullingEvent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared.Pulling.Events; - -/// -/// Raised when a request is made to stop pulling an entity. -/// -public record struct AttemptStopPullingEvent(EntityUid? User = null) -{ - public readonly EntityUid? User = User; - public bool Cancelled; -} \ No newline at end of file diff --git a/Content.Shared/Movement/Pulling/Events/PullMessage.cs b/Content.Shared/Movement/Pulling/Events/PullMessage.cs deleted file mode 100644 index a427e448d5c319..00000000000000 --- a/Content.Shared/Movement/Pulling/Events/PullMessage.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Content.Shared.Movement.Pulling.Events; - -public abstract class PullMessage : EntityEventArgs -{ - public readonly EntityUid PullerUid; - public readonly EntityUid PulledUid; - - protected PullMessage(EntityUid pullerUid, EntityUid pulledUid) - { - PullerUid = pullerUid; - PulledUid = pulledUid; - } -} diff --git a/Content.Shared/Movement/Pulling/Events/PullStartedMessage.cs b/Content.Shared/Movement/Pulling/Events/PullStartedMessage.cs deleted file mode 100644 index 29460e1dfc1db9..00000000000000 --- a/Content.Shared/Movement/Pulling/Events/PullStartedMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Shared.Movement.Pulling.Events; - -public sealed class PullStartedMessage : PullMessage -{ - public PullStartedMessage(EntityUid pullerUid, EntityUid pullableUid) : - base(pullerUid, pullableUid) - { - } -} diff --git a/Content.Shared/Movement/Pulling/Events/PullStoppedMessage.cs b/Content.Shared/Movement/Pulling/Events/PullStoppedMessage.cs deleted file mode 100644 index 47aa34562fbad3..00000000000000 --- a/Content.Shared/Movement/Pulling/Events/PullStoppedMessage.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Robust.Shared.Physics.Components; - -namespace Content.Shared.Movement.Pulling.Events; - -/// -/// Raised directed on both puller and pullable. -/// -public sealed class PullStoppedMessage : PullMessage -{ - public PullStoppedMessage(EntityUid pullerUid, EntityUid pulledUid) : base(pullerUid, pulledUid) - { - } -} diff --git a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs b/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs deleted file mode 100644 index 8ed06ca8c98916..00000000000000 --- a/Content.Shared/Movement/Pulling/Systems/PullingSystem.cs +++ /dev/null @@ -1,455 +0,0 @@ -using System.Numerics; -using Content.Shared.ActionBlocker; -using Content.Shared.Administration.Logs; -using Content.Shared.Alert; -using Content.Shared.Buckle.Components; -using Content.Shared.Database; -using Content.Shared.Hands; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Input; -using Content.Shared.Interaction; -using Content.Shared.Movement.Events; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Events; -using Content.Shared.Movement.Systems; -using Content.Shared.Pulling.Events; -using Content.Shared.Throwing; -using Content.Shared.Verbs; -using Robust.Shared.Containers; -using Robust.Shared.Input.Binding; -using Robust.Shared.Map; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Components; -using Robust.Shared.Physics.Events; -using Robust.Shared.Physics.Systems; -using Robust.Shared.Player; -using Robust.Shared.Timing; - -namespace Content.Shared.Movement.Pulling.Systems; - -/// -/// Allows one entity to pull another behind them via a physics distance joint. -/// -public sealed class PullingSystem : EntitySystem -{ - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly ActionBlockerSystem _blocker = default!; - [Dependency] private readonly AlertsSystem _alertsSystem = default!; - [Dependency] private readonly MovementSpeedModifierSystem _modifierSystem = default!; - [Dependency] private readonly SharedJointSystem _joints = default!; - [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly SharedHandsSystem _handsSystem = default!; - [Dependency] private readonly SharedInteractionSystem _interaction = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly SharedTransformSystem _xformSys = default!; - [Dependency] private readonly ThrowingSystem _throwing = default!; - - public override void Initialize() - { - base.Initialize(); - - UpdatesAfter.Add(typeof(SharedPhysicsSystem)); - UpdatesOutsidePrediction = true; - - SubscribeLocalEvent(OnPullableMoveInput); - SubscribeLocalEvent(OnPullableCollisionChange); - SubscribeLocalEvent(OnJointRemoved); - SubscribeLocalEvent>(AddPullVerbs); - - SubscribeLocalEvent(OnPullerUnpaused); - SubscribeLocalEvent(OnVirtualItemDeleted); - SubscribeLocalEvent(OnRefreshMovespeed); - - CommandBinds.Builder - .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(OnRequestMovePulledObject)) - .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(OnReleasePulledObject)) - .Register(); - } - - public override void Shutdown() - { - base.Shutdown(); - CommandBinds.Unregister(); - } - - private void OnPullerUnpaused(EntityUid uid, PullerComponent component, ref EntityUnpausedEvent args) - { - component.NextThrow += args.PausedTime; - } - - private void OnVirtualItemDeleted(EntityUid uid, PullerComponent component, VirtualItemDeletedEvent args) - { - // If client deletes the virtual hand then stop the pull. - if (component.Pulling == null) - return; - - if (component.Pulling != args.BlockingEntity) - return; - - if (EntityManager.TryGetComponent(args.BlockingEntity, out PullableComponent? comp)) - { - TryStopPull(args.BlockingEntity, comp, uid); - } - } - - private void AddPullVerbs(EntityUid uid, PullableComponent component, GetVerbsEvent args) - { - if (!args.CanAccess || !args.CanInteract) - return; - - // Are they trying to pull themselves up by their bootstraps? - if (args.User == args.Target) - return; - - //TODO VERB ICONS add pulling icon - if (component.Puller == args.User) - { - Verb verb = new() - { - Text = Loc.GetString("pulling-verb-get-data-text-stop-pulling"), - Act = () => TryStopPull(uid, component, user: args.User), - DoContactInteraction = false // pulling handle its own contact interaction. - }; - args.Verbs.Add(verb); - } - else if (CanPull(args.User, args.Target)) - { - Verb verb = new() - { - Text = Loc.GetString("pulling-verb-get-data-text"), - Act = () => TryStartPull(args.User, args.Target), - DoContactInteraction = false // pulling handle its own contact interaction. - }; - args.Verbs.Add(verb); - } - } - - private void OnRefreshMovespeed(EntityUid uid, PullerComponent component, RefreshMovementSpeedModifiersEvent args) - { - args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier); - } - - private void OnPullableMoveInput(EntityUid uid, PullableComponent component, ref MoveInputEvent args) - { - // If someone moves then break their pulling. - if (!component.BeingPulled) - return; - - var entity = args.Entity; - - if (!_blocker.CanMove(entity)) - return; - - TryStopPull(uid, component, user: uid); - } - - private void OnPullableCollisionChange(EntityUid uid, PullableComponent component, ref CollisionChangeEvent args) - { - // IDK what this is supposed to be. - if (!_timing.ApplyingState && component.PullJointId != null && !args.CanCollide) - { - _joints.RemoveJoint(uid, component.PullJointId); - } - } - - private void OnJointRemoved(EntityUid uid, PullableComponent component, JointRemovedEvent args) - { - // Not relevant / pullable state handle it. - if (component.Puller != args.OtherEntity || - args.Joint.ID != component.PullJointId || - _timing.ApplyingState) - { - return; - } - - if (args.Joint.ID != component.PullJointId) - return; - - CleanupPulling(uid, component); - } - - private void CleanupPulling(EntityUid pullableUid, PullableComponent pullableComp) - { - // No more joints with puller -> force stop pull. - if (TryComp(pullableComp.Puller, out var pullerComp)) - { - pullerComp.Pulling = null; - Dirty(pullableComp.Puller.Value, pullerComp); - } - - pullableComp.PullJointId = null; - pullableComp.Puller = null; - Dirty(pullableUid, pullableComp); - } - - public bool IsPulled(EntityUid uid, PullableComponent? component = null) - { - return Resolve(uid, ref component, false) && component.BeingPulled; - } - - private bool OnRequestMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid) - { - if (session?.AttachedEntity is not { } player || - !player.IsValid()) - { - return false; - } - - if (!TryComp(player, out var pullerComp)) - return false; - - var pulled = pullerComp.Pulling; - - if (!HasComp(pulled)) - return false; - - if (_containerSystem.IsEntityInContainer(player)) - return false; - - // Cooldown buddy - if (_timing.CurTime < pullerComp.NextThrow) - return false; - - pullerComp.NextThrow = _timing.CurTime + pullerComp.ThrowCooldown; - - // Cap the distance - const float range = 2f; - var fromUserCoords = coords.WithEntityId(player, EntityManager); - var userCoords = new EntityCoordinates(player, Vector2.Zero); - - if (!userCoords.InRange(EntityManager, _xformSys, fromUserCoords, range)) - { - var userDirection = fromUserCoords.Position - userCoords.Position; - fromUserCoords = userCoords.Offset(userDirection.Normalized() * range); - } - - Dirty(player, pullerComp); - _throwing.TryThrow(pulled.Value, fromUserCoords, user: player, strength: 4f, animated: false, recoil: false, playSound: false); - return false; - } - - public bool IsPulling(EntityUid puller, PullerComponent? component = null) - { - return Resolve(puller, ref component, false) && component.Pulling != null; - } - - private void OnReleasePulledObject(ICommonSession? session) - { - if (session?.AttachedEntity is not {Valid: true} player) - { - return; - } - - if (!TryComp(player, out PullerComponent? pullerComp) || - !TryComp(pullerComp.Pulling, out PullableComponent? pullableComp)) - { - return; - } - - TryStopPull(pullerComp.Pulling.Value, pullableComp, user: player); - } - - public bool CanPull(EntityUid puller, EntityUid pullableUid, PullerComponent? pullerComp = null) - { - if (!Resolve(puller, ref pullerComp, false)) - { - return false; - } - - if (pullerComp.NeedsHands && !_handsSystem.TryGetEmptyHand(puller, out _)) - { - return false; - } - - if (!_blocker.CanInteract(puller, pullableUid)) - { - return false; - } - - if (!EntityManager.TryGetComponent(pullableUid, out var physics)) - { - return false; - } - - if (physics.BodyType == BodyType.Static) - { - return false; - } - - if (puller == pullableUid) - { - return false; - } - - if (!_containerSystem.IsInSameOrNoContainer(puller, pullableUid)) - { - return false; - } - - if (EntityManager.TryGetComponent(puller, out BuckleComponent? buckle)) - { - // Prevent people pulling the chair they're on, etc. - if (buckle is { PullStrap: false, Buckled: true } && (buckle.LastEntityBuckledTo == pullableUid)) - { - return false; - } - } - - var getPulled = new BeingPulledAttemptEvent(puller, pullableUid); - RaiseLocalEvent(pullableUid, getPulled, true); - var startPull = new StartPullAttemptEvent(puller, pullableUid); - RaiseLocalEvent(puller, startPull, true); - return !startPull.Cancelled && !getPulled.Cancelled; - } - - public bool TogglePull(EntityUid pullableUid, EntityUid pullerUid, PullableComponent pullable) - { - if (pullable.Puller == pullerUid) - { - return TryStopPull(pullableUid, pullable); - } - - return TryStartPull(pullerUid, pullableUid, pullableComp: pullable); - } - - public bool TogglePull(EntityUid pullerUid, PullerComponent puller) - { - if (!TryComp(puller.Pulling, out var pullable)) - return false; - - return TogglePull(puller.Pulling.Value, pullerUid, pullable); - } - - public bool TryStartPull(EntityUid pullerUid, EntityUid pullableUid, EntityUid? user = null, - PullerComponent? pullerComp = null, PullableComponent? pullableComp = null) - { - if (!Resolve(pullerUid, ref pullerComp, false) || - !Resolve(pullableUid, ref pullableComp, false)) - { - return false; - } - - if (pullerComp.Pulling == pullableUid) - return true; - - if (!CanPull(pullerUid, pullableUid)) - return false; - - if (!EntityManager.TryGetComponent(pullerUid, out var pullerPhysics) || - !EntityManager.TryGetComponent(pullableUid, out var pullablePhysics)) - { - return false; - } - - // Ensure that the puller is not currently pulling anything. - var oldPullable = pullerComp.Pulling; - - if (oldPullable != null) - { - // Well couldn't stop the old one. - if (!TryStopPull(oldPullable.Value, pullableComp, user)) - return false; - } - - var pullAttempt = new PullAttemptEvent(pullerUid, pullableUid); - RaiseLocalEvent(pullerUid, pullAttempt); - - if (pullAttempt.Cancelled) - return false; - - RaiseLocalEvent(pullableUid, pullAttempt); - - if (pullAttempt.Cancelled) - return false; - - // Pulling confirmed - - _interaction.DoContactInteraction(pullableUid, pullerUid); - - // Use net entity so it's consistent across client and server. - pullableComp.PullJointId = $"pull-joint-{GetNetEntity(pullableUid)}"; - - pullerComp.Pulling = pullableUid; - pullableComp.Puller = pullerUid; - - // joint state handling will manage its own state - if (!_timing.ApplyingState) - { - // Joint startup - var union = _physics.GetHardAABB(pullerUid).Union(_physics.GetHardAABB(pullableUid, body: pullablePhysics)); - var length = Math.Max((float) union.Size.X, (float) union.Size.Y) * 0.75f; - - var joint = _joints.CreateDistanceJoint(pullableUid, pullerUid, id: pullableComp.PullJointId); - joint.CollideConnected = false; - // This maximum has to be there because if the object is constrained too closely, the clamping goes backwards and asserts. - joint.MaxLength = Math.Max(1.0f, length); - joint.Length = length * 0.75f; - joint.MinLength = 0f; - joint.Stiffness = 1f; - - _physics.SetFixedRotation(pullableUid, pullableComp.FixedRotationOnPull, body: pullablePhysics); - } - - pullableComp.PrevFixedRotation = pullablePhysics.FixedRotation; - - // Messaging - var message = new PullStartedMessage(pullerUid, pullableUid); - _alertsSystem.ShowAlert(pullerUid, AlertType.Pulling); - _alertsSystem.ShowAlert(pullableUid, AlertType.Pulled); - - RaiseLocalEvent(pullerUid, message); - RaiseLocalEvent(pullableUid, message); - - Dirty(pullerUid, pullerComp); - Dirty(pullableUid, pullableComp); - - _adminLogger.Add(LogType.Action, LogImpact.Low, - $"{ToPrettyString(pullerUid):user} started pulling {ToPrettyString(pullableUid):target}"); - return true; - } - - public bool TryStopPull(EntityUid pullableUid, PullableComponent pullable, EntityUid? user = null) - { - var pullerUidNull = pullable.Puller; - - if (pullerUidNull == null) - return false; - - var pullerUid = pullerUidNull.Value; - var msg = new AttemptStopPullingEvent(user); - RaiseLocalEvent(pullableUid, msg, true); - - if (msg.Cancelled) - return false; - - // Stop pulling confirmed! - if (!_timing.ApplyingState) - { - if (TryComp(pullableUid, out var pullablePhysics)) - { - _physics.SetFixedRotation(pullableUid, pullable.PrevFixedRotation, body: pullablePhysics); - } - - // Joint shutdown - if (pullable.PullJointId != null) - { - _joints.RemoveJoint(pullableUid, pullable.PullJointId); - pullable.PullJointId = null; - } - } - - CleanupPulling(pullableUid, pullable); - - // Messaging - var message = new PullStoppedMessage(pullerUid, pullableUid); - _alertsSystem.ClearAlert(pullerUid, AlertType.Pulling); - _alertsSystem.ClearAlert(pullableUid, AlertType.Pulled); - _modifierSystem.RefreshMovementSpeedModifiers(pullerUid); - _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(pullerUid):user} stopped pulling {ToPrettyString(pullableUid):target}"); - - RaiseLocalEvent(pullerUid, message); - RaiseLocalEvent(pullableUid, message); - return true; - } -} diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index 6a3dbb2864ecbd..7d5e24a15b1873 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -9,6 +9,7 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; +using Content.Shared.Pulling.Components; using Content.Shared.Tag; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -22,7 +23,6 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; using Robust.Shared.Utility; -using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent; namespace Content.Shared.Movement.Systems { @@ -53,7 +53,7 @@ public abstract partial class SharedMoverController : VirtualController protected EntityQuery ModifierQuery; protected EntityQuery PhysicsQuery; protected EntityQuery RelayQuery; - protected EntityQuery PullableQuery; + protected EntityQuery PullableQuery; protected EntityQuery XformQuery; protected EntityQuery CanMoveInAirQuery; protected EntityQuery NoRotateQuery; @@ -85,7 +85,7 @@ public override void Initialize() RelayTargetQuery = GetEntityQuery(); PhysicsQuery = GetEntityQuery(); RelayQuery = GetEntityQuery(); - PullableQuery = GetEntityQuery(); + PullableQuery = GetEntityQuery(); XformQuery = GetEntityQuery(); NoRotateQuery = GetEntityQuery(); CanMoveInAirQuery = GetEntityQuery(); @@ -381,7 +381,7 @@ private bool IsAroundCollider(SharedPhysicsSystem broadPhaseSystem, TransformCom !otherCollider.CanCollide || ((collider.CollisionMask & otherCollider.CollisionLayer) == 0 && (otherCollider.CollisionMask & collider.CollisionLayer) == 0) || - (TryComp(otherCollider.Owner, out PullableComponent? pullable) && pullable.BeingPulled)) + (TryComp(otherCollider.Owner, out SharedPullableComponent? pullable) && pullable.BeingPulled)) { continue; } diff --git a/Content.Shared/Pulling/Components/PullableComponent.cs b/Content.Shared/Pulling/Components/PullableComponent.cs new file mode 100644 index 00000000000000..c5c30688699464 --- /dev/null +++ b/Content.Shared/Pulling/Components/PullableComponent.cs @@ -0,0 +1,57 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Map; +using Robust.Shared.Serialization; + +namespace Content.Shared.Pulling.Components +{ + // Before you try to add another type than SharedPullingStateManagementSystem, consider the can of worms you may be opening! + [NetworkedComponent, AutoGenerateComponentState] + [Access(typeof(SharedPullingStateManagementSystem))] + [RegisterComponent] + public sealed partial class SharedPullableComponent : Component + { + /// + /// The current entity pulling this component. + /// + [DataField, AutoNetworkedField] + public EntityUid? Puller { get; set; } + + /// + /// The pull joint. + /// + [DataField, AutoNetworkedField] + public string? PullJointId { get; set; } + + public bool BeingPulled => Puller != null; + + [Access(typeof(SharedPullingStateManagementSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends + public EntityCoordinates? MovingTo { get; set; } + + /// + /// If the physics component has FixedRotation should we keep it upon being pulled + /// + [Access(typeof(SharedPullingSystem), Other = AccessPermissions.ReadExecute)] + [ViewVariables(VVAccess.ReadWrite), DataField("fixedRotation")] + public bool FixedRotationOnPull { get; set; } + + /// + /// What the pullable's fixedrotation was set to before being pulled. + /// + [Access(typeof(SharedPullingSystem), Other = AccessPermissions.ReadExecute)] + [ViewVariables] + public bool PrevFixedRotation; + } + + /// + /// Raised when a request is made to stop pulling an entity. + /// + public sealed class StopPullingEvent : CancellableEntityEventArgs + { + public EntityUid? User { get; } + + public StopPullingEvent(EntityUid? uid = null) + { + User = uid; + } + } +} diff --git a/Content.Shared/Pulling/Components/SharedPullerComponent.cs b/Content.Shared/Pulling/Components/SharedPullerComponent.cs new file mode 100644 index 00000000000000..57a86e7f7a842f --- /dev/null +++ b/Content.Shared/Pulling/Components/SharedPullerComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Pulling.Components +{ + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] + [Access(typeof(SharedPullingStateManagementSystem))] + public sealed partial class SharedPullerComponent : Component + { + // Before changing how this is updated, please see SharedPullerSystem.RefreshMovementSpeed + public float WalkSpeedModifier => Pulling == default ? 1.0f : 0.95f; + + public float SprintSpeedModifier => Pulling == default ? 1.0f : 0.95f; + + [DataField, AutoNetworkedField] + public EntityUid? Pulling { get; set; } + + /// + /// Does this entity need hands to be able to pull something? + /// + [DataField("needsHands")] + public bool NeedsHands = true; + } +} diff --git a/Content.Shared/Movement/Pulling/Events/BeingPulledAttemptEvent.cs b/Content.Shared/Pulling/Events/BeingPulledAttemptEvent.cs similarity index 100% rename from Content.Shared/Movement/Pulling/Events/BeingPulledAttemptEvent.cs rename to Content.Shared/Pulling/Events/BeingPulledAttemptEvent.cs diff --git a/Content.Shared/Pulling/Events/PullAttemptEvent.cs b/Content.Shared/Pulling/Events/PullAttemptEvent.cs new file mode 100644 index 00000000000000..6296dc2f14f68c --- /dev/null +++ b/Content.Shared/Pulling/Events/PullAttemptEvent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Physics.Components; + +namespace Content.Shared.Physics.Pull +{ + public sealed class PullAttemptEvent : PullMessage + { + public PullAttemptEvent(PhysicsComponent puller, PhysicsComponent pulled) : base(puller, pulled) { } + + public bool Cancelled { get; set; } + } +} diff --git a/Content.Shared/Pulling/Events/PullMessage.cs b/Content.Shared/Pulling/Events/PullMessage.cs new file mode 100644 index 00000000000000..b11de7c1702610 --- /dev/null +++ b/Content.Shared/Pulling/Events/PullMessage.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Physics.Components; + +namespace Content.Shared.Physics.Pull +{ + public abstract class PullMessage : EntityEventArgs + { + public readonly PhysicsComponent Puller; + public readonly PhysicsComponent Pulled; + + protected PullMessage(PhysicsComponent puller, PhysicsComponent pulled) + { + Puller = puller; + Pulled = pulled; + } + } +} diff --git a/Content.Shared/Pulling/Events/PullStartedMessage.cs b/Content.Shared/Pulling/Events/PullStartedMessage.cs new file mode 100644 index 00000000000000..0ede284bb0cc3b --- /dev/null +++ b/Content.Shared/Pulling/Events/PullStartedMessage.cs @@ -0,0 +1,12 @@ +using Robust.Shared.Physics.Components; + +namespace Content.Shared.Physics.Pull +{ + public sealed class PullStartedMessage : PullMessage + { + public PullStartedMessage(PhysicsComponent puller, PhysicsComponent pulled) : + base(puller, pulled) + { + } + } +} diff --git a/Content.Shared/Pulling/Events/PullStoppedMessage.cs b/Content.Shared/Pulling/Events/PullStoppedMessage.cs new file mode 100644 index 00000000000000..afcbcb7074019b --- /dev/null +++ b/Content.Shared/Pulling/Events/PullStoppedMessage.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Physics.Components; + +namespace Content.Shared.Physics.Pull +{ + public sealed class PullStoppedMessage : PullMessage + { + public PullStoppedMessage(PhysicsComponent puller, PhysicsComponent pulled) : base(puller, pulled) + { + } + } +} diff --git a/Content.Shared/Movement/Pulling/Events/StartPullAttemptEvent.cs b/Content.Shared/Pulling/Events/StartPullAttemptEvent.cs similarity index 100% rename from Content.Shared/Movement/Pulling/Events/StartPullAttemptEvent.cs rename to Content.Shared/Pulling/Events/StartPullAttemptEvent.cs diff --git a/Content.Shared/Pulling/PullableMoveMessage.cs b/Content.Shared/Pulling/PullableMoveMessage.cs new file mode 100644 index 00000000000000..46c6b1291d6ed1 --- /dev/null +++ b/Content.Shared/Pulling/PullableMoveMessage.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Pulling +{ + public sealed class PullableMoveMessage : EntityEventArgs + { + } +} diff --git a/Content.Shared/Pulling/PullableStopMovingMessage.cs b/Content.Shared/Pulling/PullableStopMovingMessage.cs new file mode 100644 index 00000000000000..0233e32f2b4694 --- /dev/null +++ b/Content.Shared/Pulling/PullableStopMovingMessage.cs @@ -0,0 +1,6 @@ +namespace Content.Shared.Pulling +{ + public sealed class PullableStopMovingMessage : EntityEventArgs + { + } +} diff --git a/Content.Shared/Pulling/Systems/SharedPullableSystem.cs b/Content.Shared/Pulling/Systems/SharedPullableSystem.cs new file mode 100644 index 00000000000000..3dab476337b966 --- /dev/null +++ b/Content.Shared/Pulling/Systems/SharedPullableSystem.cs @@ -0,0 +1,28 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Mobs.Systems; +using Content.Shared.Pulling.Components; +using Content.Shared.Movement.Events; + +namespace Content.Shared.Pulling.Systems +{ + public sealed class SharedPullableSystem : EntitySystem + { + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedPullingSystem _pullSystem = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRelayMoveInput); + } + + private void OnRelayMoveInput(EntityUid uid, SharedPullableComponent component, ref MoveInputEvent args) + { + var entity = args.Entity; + if (_mobState.IsIncapacitated(entity) || !_blocker.CanMove(entity)) return; + + _pullSystem.TryStopPull(component); + } + } +} diff --git a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs new file mode 100644 index 00000000000000..e388d7a57c6eb0 --- /dev/null +++ b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs @@ -0,0 +1,90 @@ +using Content.Shared.Administration.Logs; +using Content.Shared.Alert; +using Content.Shared.Database; +using Content.Shared.Hands; +using Content.Shared.Movement.Systems; +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling.Components; +using JetBrains.Annotations; + +namespace Content.Shared.Pulling.Systems +{ + [UsedImplicitly] + public sealed class SharedPullerSystem : EntitySystem + { + [Dependency] private readonly SharedPullingStateManagementSystem _why = default!; + [Dependency] private readonly SharedPullingSystem _pullSystem = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(PullerHandlePullStarted); + SubscribeLocalEvent(PullerHandlePullStopped); + SubscribeLocalEvent(OnVirtualItemDeleted); + SubscribeLocalEvent(OnRefreshMovespeed); + SubscribeLocalEvent(OnPullerShutdown); + } + + private void OnPullerShutdown(EntityUid uid, SharedPullerComponent component, ComponentShutdown args) + { + _why.ForceDisconnectPuller(component); + } + + private void OnVirtualItemDeleted(EntityUid uid, SharedPullerComponent component, VirtualItemDeletedEvent args) + { + if (component.Pulling == null) + return; + + if (component.Pulling == args.BlockingEntity) + { + if (EntityManager.TryGetComponent(args.BlockingEntity, out var comp)) + { + _pullSystem.TryStopPull(comp, uid); + } + } + } + + private void PullerHandlePullStarted( + EntityUid uid, + SharedPullerComponent component, + PullStartedMessage args) + { + if (args.Puller.Owner != uid) + return; + + _alertsSystem.ShowAlert(component.Owner, AlertType.Pulling); + + RefreshMovementSpeed(component); + } + + private void PullerHandlePullStopped( + EntityUid uid, + SharedPullerComponent component, + PullStoppedMessage args) + { + if (args.Puller.Owner != uid) + return; + + var euid = component.Owner; + if (_alertsSystem.IsShowingAlert(euid, AlertType.Pulling)) + _adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(euid):user} stopped pulling {ToPrettyString(args.Pulled.Owner):target}"); + _alertsSystem.ClearAlert(euid, AlertType.Pulling); + + RefreshMovementSpeed(component); + } + + private void OnRefreshMovespeed(EntityUid uid, SharedPullerComponent component, RefreshMovementSpeedModifiersEvent args) + { + args.ModifySpeed(component.WalkSpeedModifier, component.SprintSpeedModifier); + } + + private void RefreshMovementSpeed(SharedPullerComponent component) + { + _movementSpeedModifierSystem.RefreshMovementSpeedModifiers(component.Owner); + } + } +} diff --git a/Content.Shared/Pulling/Systems/SharedPullingStateManagementSystem.cs b/Content.Shared/Pulling/Systems/SharedPullingStateManagementSystem.cs new file mode 100644 index 00000000000000..38ed8998898965 --- /dev/null +++ b/Content.Shared/Pulling/Systems/SharedPullingStateManagementSystem.cs @@ -0,0 +1,196 @@ +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling.Components; +using JetBrains.Annotations; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; + +namespace Content.Shared.Pulling +{ + /// + /// This is the core of pulling state management. + /// Because pulling state is such a mess to get right, all writes to pulling state must go through this class. + /// + [UsedImplicitly] + public sealed class SharedPullingStateManagementSystem : EntitySystem + { + [Dependency] private readonly SharedJointSystem _jointSystem = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnShutdown); + } + + private void OnShutdown(EntityUid uid, SharedPullableComponent component, ComponentShutdown args) + { + if (component.Puller != null) + ForceRelationship(null, component); + } + + // A WARNING: + // The following 2 functions are the most internal part of the pulling system's relationship management. + // They do not expect to be cancellable. + private void ForceDisconnect(SharedPullerComponent puller, SharedPullableComponent pullable) + { + var pullerPhysics = EntityManager.GetComponent(puller.Owner); + var pullablePhysics = EntityManager.GetComponent(pullable.Owner); + + // MovingTo shutdown + ForceSetMovingTo(pullable, null); + + // Joint shutdown + if (!_timing.ApplyingState && // During state-handling, joint component will handle its own state. + pullable.PullJointId != null && + TryComp(puller.Owner, out JointComponent? jointComp)) + { + if (jointComp.GetJoints.TryGetValue(pullable.PullJointId, out var j)) + _jointSystem.RemoveJoint(j); + } + pullable.PullJointId = null; + + // State shutdown + puller.Pulling = null; + pullable.Puller = null; + + // Messaging + var message = new PullStoppedMessage(pullerPhysics, pullablePhysics); + + RaiseLocalEvent(puller.Owner, message, broadcast: false); + + if (Initialized(pullable.Owner)) + RaiseLocalEvent(pullable.Owner, message, true); + + // Networking + Dirty(puller); + Dirty(pullable); + } + + public void ForceRelationship(SharedPullerComponent? puller, SharedPullableComponent? pullable) + { + if (_timing.ApplyingState) + return; + ; + if (pullable != null && puller != null && (puller.Pulling == pullable.Owner)) + { + // Already done + return; + } + + // Start by disconnecting the pullable from whatever it is currently connected to. + var pullableOldPullerE = pullable?.Puller; + if (pullableOldPullerE != null) + { + ForceDisconnect(EntityManager.GetComponent(pullableOldPullerE.Value), pullable!); + } + + // Continue with the puller. + var pullerOldPullableE = puller?.Pulling; + if (pullerOldPullableE != null) + { + ForceDisconnect(puller!, EntityManager.GetComponent(pullerOldPullableE.Value)); + } + + // And now for the actual connection (if any). + + if (puller != null && pullable != null) + { + var pullerPhysics = EntityManager.GetComponent(puller.Owner); + var pullablePhysics = EntityManager.GetComponent(pullable.Owner); + pullable.PullJointId = $"pull-joint-{pullable.Owner}"; + + // State startup + puller.Pulling = pullable.Owner; + pullable.Puller = puller.Owner; + + // joint state handling will manage its own state + if (!_timing.ApplyingState) + { + // Joint startup + var union = _physics.GetHardAABB(puller.Owner).Union(_physics.GetHardAABB(pullable.Owner, body: pullablePhysics)); + var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f; + + var joint = _jointSystem.CreateDistanceJoint(pullablePhysics.Owner, pullerPhysics.Owner, id: pullable.PullJointId); + joint.CollideConnected = false; + // This maximum has to be there because if the object is constrained too closely, the clamping goes backwards and asserts. + joint.MaxLength = Math.Max(1.0f, length); + joint.Length = length * 0.75f; + joint.MinLength = 0f; + joint.Stiffness = 1f; + } + + // Messaging + var message = new PullStartedMessage(pullerPhysics, pullablePhysics); + + RaiseLocalEvent(puller.Owner, message, broadcast: false); + RaiseLocalEvent(pullable.Owner, message, true); + + // Networking + Dirty(puller); + Dirty(pullable); + } + } + + // For OnRemove use only. + public void ForceDisconnectPuller(SharedPullerComponent puller) + { + // DO NOT ADD ADDITIONAL LOGIC IN THIS FUNCTION. Do it in ForceRelationship. + ForceRelationship(puller, null); + } + + // For OnRemove use only. + public void ForceDisconnectPullable(SharedPullableComponent pullable) + { + // DO NOT ADD ADDITIONAL LOGIC IN THIS FUNCTION. Do it in ForceRelationship. + ForceRelationship(null, pullable); + } + + public void ForceSetMovingTo(SharedPullableComponent pullable, EntityCoordinates? movingTo) + { + if (_timing.ApplyingState) + return; + + if (pullable.MovingTo == movingTo) + { + return; + } + + // Don't allow setting a MovingTo if there's no puller. + // The other half of this guarantee (shutting down a MovingTo if the puller goes away) is enforced in ForceRelationship. + if (pullable.Puller == null && movingTo != null) + { + return; + } + + pullable.MovingTo = movingTo; + Dirty(pullable); + + if (movingTo == null) + { + RaiseLocalEvent(pullable.Owner, new PullableStopMovingMessage(), true); + } + else + { + RaiseLocalEvent(pullable.Owner, new PullableMoveMessage(), true); + } + } + + /// + /// Changes if the entity needs a hand in order to be able to pull objects. + /// + public void ChangeHandRequirement(EntityUid uid, bool needsHands, SharedPullerComponent? comp) + { + if (!Resolve(uid, ref comp, false)) + return; + + comp.NeedsHands = needsHands; + + Dirty(uid, comp); + } + } +} diff --git a/Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs new file mode 100644 index 00000000000000..1e2bb90c61eb34 --- /dev/null +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.Actions.cs @@ -0,0 +1,239 @@ +using Content.Shared.ActionBlocker; +using Content.Shared.Administration.Logs; +using Content.Shared.Buckle.Components; +using Content.Shared.Database; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Interaction; +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling.Components; +using Content.Shared.Pulling.Events; +using Robust.Shared.Containers; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared.Pulling +{ + public abstract partial class SharedPullingSystem + { + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + public bool CanPull(EntityUid puller, EntityUid pulled) + { + if (!EntityManager.TryGetComponent(puller, out var comp)) + { + return false; + } + + if (comp.NeedsHands && !_handsSystem.TryGetEmptyHand(puller, out _)) + { + return false; + } + + if (!_blocker.CanInteract(puller, pulled)) + { + return false; + } + + if (!EntityManager.TryGetComponent(pulled, out var physics)) + { + return false; + } + + if (physics.BodyType == BodyType.Static) + { + return false; + } + + if (puller == pulled) + { + return false; + } + + if(_containerSystem.IsEntityInContainer(puller) || _containerSystem.IsEntityInContainer(pulled)) + { + return false; + } + + if (EntityManager.TryGetComponent(puller, out BuckleComponent? buckle)) + { + // Prevent people pulling the chair they're on, etc. + if (buckle is { PullStrap: false, Buckled: true } && (buckle.LastEntityBuckledTo == pulled)) + { + return false; + } + } + + var getPulled = new BeingPulledAttemptEvent(puller, pulled); + RaiseLocalEvent(pulled, getPulled, true); + var startPull = new StartPullAttemptEvent(puller, pulled); + RaiseLocalEvent(puller, startPull, true); + return (!startPull.Cancelled && !getPulled.Cancelled); + } + + public bool TogglePull(EntityUid puller, SharedPullableComponent pullable) + { + if (pullable.Puller == puller) + { + return TryStopPull(pullable); + } + return TryStartPull(puller, pullable.Owner); + } + + // -- Core attempted actions -- + + public bool TryStopPull(SharedPullableComponent pullable, EntityUid? user = null) + { + if (_timing.ApplyingState) + return false; + + if (!pullable.BeingPulled) + { + return false; + } + + var msg = new StopPullingEvent(user); + RaiseLocalEvent(pullable.Owner, msg, true); + + if (msg.Cancelled) return false; + + // Stop pulling confirmed! + + if (TryComp(pullable.Owner, out var pullablePhysics)) + { + _physics.SetFixedRotation(pullable.Owner, pullable.PrevFixedRotation, body: pullablePhysics); + } + + _pullSm.ForceRelationship(null, pullable); + return true; + } + + public bool TryStartPull(EntityUid puller, EntityUid pullable) + { + if (!EntityManager.TryGetComponent(puller, out SharedPullerComponent? pullerComp)) + { + return false; + } + if (!EntityManager.TryGetComponent(pullable, out SharedPullableComponent? pullableComp)) + { + return false; + } + return TryStartPull(pullerComp, pullableComp); + } + + // The main "start pulling" function. + public bool TryStartPull(SharedPullerComponent puller, SharedPullableComponent pullable) + { + if (_timing.ApplyingState) + return false; + + if (puller.Pulling == pullable.Owner) + return true; + + // Pulling a new object : Perform sanity checks. + + if (!CanPull(puller.Owner, pullable.Owner)) + { + return false; + } + + if (!EntityManager.TryGetComponent(puller.Owner, out var pullerPhysics)) + { + return false; + } + + if (!EntityManager.TryGetComponent(pullable.Owner, out var pullablePhysics)) + { + return false; + } + + // Ensure that the puller is not currently pulling anything. + // If this isn't done, then it happens too late, and the start/stop messages go out of order, + // and next thing you know it thinks it's not pulling anything even though it is! + + var oldPullable = puller.Pulling; + if (oldPullable != null) + { + if (EntityManager.TryGetComponent(oldPullable.Value, out SharedPullableComponent? oldPullableComp)) + { + if (!TryStopPull(oldPullableComp)) + { + return false; + } + } + else + { + Log.Warning("Well now you've done it, haven't you? Someone transferred pulling (onto {0}) while presently pulling something that has no Pullable component (on {1})!", pullable.Owner, oldPullable); + return false; + } + } + + // Ensure that the pullable is not currently being pulled. + // Same sort of reasons as before. + + var oldPuller = pullable.Puller; + if (oldPuller != null) + { + if (!TryStopPull(pullable)) + { + return false; + } + } + + // Continue with pulling process. + + var pullAttempt = new PullAttemptEvent(pullerPhysics, pullablePhysics); + + RaiseLocalEvent(puller.Owner, pullAttempt, broadcast: false); + + if (pullAttempt.Cancelled) + { + return false; + } + + RaiseLocalEvent(pullable.Owner, pullAttempt, true); + + if (pullAttempt.Cancelled) + return false; + + _interaction.DoContactInteraction(pullable.Owner, puller.Owner); + + _pullSm.ForceRelationship(puller, pullable); + pullable.PrevFixedRotation = pullablePhysics.FixedRotation; + _physics.SetFixedRotation(pullable.Owner, pullable.FixedRotationOnPull, body: pullablePhysics); + _adminLogger.Add(LogType.Action, LogImpact.Low, + $"{ToPrettyString(puller.Owner):user} started pulling {ToPrettyString(pullable.Owner):target}"); + return true; + } + + public bool TryMoveTo(SharedPullableComponent pullable, EntityCoordinates to) + { + if (pullable.Puller == null) + { + return false; + } + + if (!EntityManager.HasComponent(pullable.Owner)) + { + return false; + } + + _pullSm.ForceSetMovingTo(pullable, to); + return true; + } + + public void StopMoveTo(SharedPullableComponent pullable) + { + _pullSm.ForceSetMovingTo(pullable, null); + } + } +} diff --git a/Content.Shared/Pulling/Systems/SharedPullingSystem.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs new file mode 100644 index 00000000000000..0c139ee9e35010 --- /dev/null +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs @@ -0,0 +1,243 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Alert; +using Content.Shared.GameTicking; +using Content.Shared.Input; +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling.Components; +using Content.Shared.Verbs; +using JetBrains.Annotations; +using Robust.Shared.Containers; +using Robust.Shared.Input.Binding; +using Robust.Shared.Map; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Player; + +namespace Content.Shared.Pulling +{ + [UsedImplicitly] + public abstract partial class SharedPullingSystem : EntitySystem + { + [Dependency] private readonly SharedPullingStateManagementSystem _pullSm = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly SharedJointSystem _joints = default!; + + /// + /// A mapping of pullers to the entity that they are pulling. + /// + private readonly Dictionary _pullers = + new(); + + private readonly HashSet _moving = new(); + private readonly HashSet _stoppedMoving = new(); + + public IReadOnlySet Moving => _moving; + + public override void Initialize() + { + base.Initialize(); + + UpdatesOutsidePrediction = true; + + SubscribeLocalEvent(Reset); + SubscribeLocalEvent(OnPullStarted); + SubscribeLocalEvent(OnPullStopped); + SubscribeLocalEvent(HandleContainerInsert); + SubscribeLocalEvent(OnJointRemoved); + SubscribeLocalEvent(OnPullableCollisionChange); + + SubscribeLocalEvent(PullableHandlePullStarted); + SubscribeLocalEvent(PullableHandlePullStopped); + + SubscribeLocalEvent>(AddPullVerbs); + + CommandBinds.Builder + .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) + .Register(); + } + + private void OnPullableCollisionChange(EntityUid uid, SharedPullableComponent component, ref CollisionChangeEvent args) + { + if (component.PullJointId != null && !args.CanCollide) + { + _joints.RemoveJoint(uid, component.PullJointId); + } + } + + private void OnJointRemoved(EntityUid uid, SharedPullableComponent component, JointRemovedEvent args) + { + if (component.Puller != args.OtherEntity) + return; + + // Do we have some other join with our Puller? + // or alternatively: + // TODO track the relevant joint. + + if (TryComp(uid, out JointComponent? joints)) + { + foreach (var jt in joints.GetJoints.Values) + { + if (jt.BodyAUid == component.Puller || jt.BodyBUid == component.Puller) + return; + } + } + + // No more joints with puller -> force stop pull. + _pullSm.ForceDisconnectPullable(component); + } + + private void AddPullVerbs(EntityUid uid, SharedPullableComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract) + return; + + // Are they trying to pull themselves up by their bootstraps? + if (args.User == args.Target) + return; + + //TODO VERB ICONS add pulling icon + if (component.Puller == args.User) + { + Verb verb = new() + { + Text = Loc.GetString("pulling-verb-get-data-text-stop-pulling"), + Act = () => TryStopPull(component, args.User), + DoContactInteraction = false // pulling handle its own contact interaction. + }; + args.Verbs.Add(verb); + } + else if (CanPull(args.User, args.Target)) + { + Verb verb = new() + { + Text = Loc.GetString("pulling-verb-get-data-text"), + Act = () => TryStartPull(args.User, args.Target), + DoContactInteraction = false // pulling handle its own contact interaction. + }; + args.Verbs.Add(verb); + } + } + + // Raise a "you are being pulled" alert if the pulled entity has alerts. + private void PullableHandlePullStarted(EntityUid uid, SharedPullableComponent component, PullStartedMessage args) + { + if (args.Pulled.Owner != uid) + return; + + _alertsSystem.ShowAlert(uid, AlertType.Pulled); + } + + private void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args) + { + if (args.Pulled.Owner != uid) + return; + + _alertsSystem.ClearAlert(uid, AlertType.Pulled); + } + + public bool IsPulled(EntityUid uid, SharedPullableComponent? component = null) + { + return Resolve(uid, ref component, false) && component.BeingPulled; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + _moving.ExceptWith(_stoppedMoving); + _stoppedMoving.Clear(); + } + + public void Reset(RoundRestartCleanupEvent ev) + { + _pullers.Clear(); + _moving.Clear(); + _stoppedMoving.Clear(); + } + + private void OnPullStarted(PullStartedMessage message) + { + SetPuller(message.Puller.Owner, message.Pulled.Owner); + } + + private void OnPullStopped(PullStoppedMessage message) + { + RemovePuller(message.Puller.Owner); + } + + protected void OnPullableMove(EntityUid uid, SharedPullableComponent component, PullableMoveMessage args) + { + _moving.Add(component); + } + + protected void OnPullableStopMove(EntityUid uid, SharedPullableComponent component, PullableStopMovingMessage args) + { + _stoppedMoving.Add(component); + } + + // TODO: When Joint networking is less shitcodey fix this to use a dedicated joints message. + private void HandleContainerInsert(EntInsertedIntoContainerMessage message) + { + if (TryComp(message.Entity, out SharedPullableComponent? pullable)) + { + TryStopPull(pullable); + } + + if (TryComp(message.Entity, out SharedPullerComponent? puller)) + { + if (puller.Pulling == null) return; + + if (!TryComp(puller.Pulling.Value, out SharedPullableComponent? pulling)) + return; + + TryStopPull(pulling); + } + } + + private bool HandleMovePulledObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + if (session?.AttachedEntity is not { } player || + !player.IsValid()) + return false; + + if (!TryGetPulled(player, out var pulled)) + return false; + + if (!TryComp(pulled.Value, out SharedPullableComponent? pullable)) + return false; + + if (_containerSystem.IsEntityInContainer(player)) + return false; + + TryMoveTo(pullable, coords); + + return false; + } + + private void SetPuller(EntityUid puller, EntityUid pulled) + { + _pullers[puller] = pulled; + } + + private bool RemovePuller(EntityUid puller) + { + return _pullers.Remove(puller); + } + + public EntityUid GetPulled(EntityUid by) + { + return _pullers.GetValueOrDefault(by); + } + + public bool TryGetPulled(EntityUid by, [NotNullWhen(true)] out EntityUid? pulled) + { + return (pulled = GetPulled(by)) != null; + } + + public bool IsPulling(EntityUid puller) + { + return _pullers.ContainsKey(puller); + } + } +} diff --git a/Content.Shared/Security/Systems/DeployableBarrierSystem.cs b/Content.Shared/Security/Systems/DeployableBarrierSystem.cs index 953120596cbcbc..9d021c52194d45 100644 --- a/Content.Shared/Security/Systems/DeployableBarrierSystem.cs +++ b/Content.Shared/Security/Systems/DeployableBarrierSystem.cs @@ -1,6 +1,6 @@ using Content.Shared.Lock; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; using Content.Shared.Security.Components; using Robust.Shared.Physics.Systems; @@ -12,7 +12,7 @@ public sealed class DeployableBarrierSystem : EntitySystem [Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] private readonly SharedPointLightSystem _pointLight = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedPullingSystem _pulling = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() @@ -58,8 +58,8 @@ private void ToggleBarrierDeploy(EntityUid uid, bool isDeployed, DeployableBarri var state = isDeployed ? DeployableBarrierState.Deployed : DeployableBarrierState.Idle; _appearance.SetData(uid, DeployableBarrierVisuals.State, state); - if (TryComp(uid, out PullableComponent? pullable)) - _pulling.TryStopPull(uid, pullable); + if (TryComp(uid, out SharedPullableComponent? pullable)) + _pulling.TryStopPull(pullable); SharedPointLightComponent? pointLight = null; if (_pointLight.ResolveLight(uid, ref pointLight)) diff --git a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs index 8d67aec518a683..ebd83624114a0d 100644 --- a/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs +++ b/Content.Shared/Teleportation/Systems/SharedPortalSystem.cs @@ -1,9 +1,9 @@ using System.Linq; using Content.Shared.Ghost; -using Content.Shared.Movement.Pulling.Components; -using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Popups; using Content.Shared.Projectiles; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; using Content.Shared.Teleportation.Components; using Content.Shared.Verbs; using Robust.Shared.Audio; @@ -28,7 +28,7 @@ public abstract class SharedPortalSystem : EntitySystem [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedPullingSystem _pulling = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; private const string PortalFixture = "portalFixture"; @@ -93,15 +93,15 @@ private void OnCollide(EntityUid uid, PortalComponent component, ref StartCollid return; // break pulls before portal enter so we dont break shit - if (TryComp(subject, out var pullable) && pullable.BeingPulled) + if (TryComp(subject, out var pullable) && pullable.BeingPulled) { - _pulling.TryStopPull(subject, pullable); + _pulling.TryStopPull(pullable); } - if (TryComp(subject, out var pullerComp) - && TryComp(pullerComp.Pulling, out var subjectPulling)) + if (TryComp(subject, out var pulling) + && pulling.Pulling != null && TryComp(pulling.Pulling.Value, out var subjectPulling)) { - _pulling.TryStopPull(subject, subjectPulling); + _pulling.TryStopPull(subjectPulling); } // if they came from another portal, just return and wait for them to exit the portal diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index 7c861a85adb0f7..54294318315053 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -20,11 +20,6 @@ public sealed class ThrowingSystem : EntitySystem { public const float ThrowAngularImpulse = 5f; - /// - /// Speed cap on rotation in case of click-spam. - /// - public const float ThrowAngularCap = 3f * MathF.PI; - public const float PushbackDefault = 2f; /// @@ -47,17 +42,15 @@ public void TryThrow( float strength = 1.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, - bool recoil = true, - bool animated = true, bool playSound = true) { - var thrownPos = _transform.GetMapCoordinates(uid); - var mapPos = _transform.ToMapCoordinates(coordinates); + var thrownPos = Transform(uid).MapPosition; + var mapPos = coordinates.ToMap(EntityManager, _transform); if (mapPos.MapId != thrownPos.MapId) return; - TryThrow(uid, mapPos.Position - thrownPos.Position, strength, user, pushbackRatio, recoil: recoil, animated: animated, playSound: playSound); + TryThrow(uid, mapPos.Position - thrownPos.Position, strength, user, pushbackRatio, playSound); } /// @@ -72,8 +65,6 @@ public void TryThrow(EntityUid uid, float strength = 1.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, - bool recoil = true, - bool animated = true, bool playSound = true) { var physicsQuery = GetEntityQuery(); @@ -81,6 +72,7 @@ public void TryThrow(EntityUid uid, return; var projectileQuery = GetEntityQuery(); + var tagQuery = GetEntityQuery(); TryThrow( uid, @@ -90,7 +82,8 @@ public void TryThrow(EntityUid uid, projectileQuery, strength, user, - pushbackRatio, recoil: recoil, animated: animated, playSound: playSound); + pushbackRatio, + playSound); } /// @@ -108,8 +101,6 @@ public void TryThrow(EntityUid uid, float strength = 1.0f, EntityUid? user = null, float pushbackRatio = PushbackDefault, - bool recoil = true, - bool animated = true, bool playSound = true) { if (strength <= 0 || direction == Vector2Helpers.Infinity || direction == Vector2Helpers.NaN || direction == Vector2.Zero) @@ -125,17 +116,12 @@ public void TryThrow(EntityUid uid, if (projectileQuery.TryGetComponent(uid, out var proj) && !proj.OnlyCollideWhenShot) return; - var comp = new ThrownItemComponent - { - Thrower = user, - Animate = animated, - }; + var comp = new ThrownItemComponent(); + comp.Thrower = user; // Estimate time to arrival so we can apply OnGround status and slow it much faster. var time = direction.Length() / strength; comp.ThrownTime = _gameTiming.CurTime; - // TODO: This is a bandaid, don't do this. - // if you want to force landtime have the caller handle it or add a new method. // did we launch this with something stronger than our hands? if (TryComp(comp.Thrower, out var hands) && strength > hands.ThrowForceMultiplier) comp.LandTime = comp.ThrownTime + TimeSpan.FromSeconds(time); @@ -180,8 +166,7 @@ public void TryThrow(EntityUid uid, if (user == null) return; - if (recoil) - _recoil.KickCamera(user.Value, -direction * 0.04f); + _recoil.KickCamera(user.Value, -direction * 0.04f); // Give thrower an impulse in the other direction if (pushbackRatio != 0.0f && diff --git a/Content.Shared/Throwing/ThrownItemComponent.cs b/Content.Shared/Throwing/ThrownItemComponent.cs index 8fb1173e6da8de..ab80e079383620 100644 --- a/Content.Shared/Throwing/ThrownItemComponent.cs +++ b/Content.Shared/Throwing/ThrownItemComponent.cs @@ -8,12 +8,6 @@ namespace Content.Shared.Throwing [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class ThrownItemComponent : Component { - /// - /// Should the in-air throwing animation play. - /// - [DataField, AutoNetworkedField] - public bool Animate = true; - /// /// The entity that threw this entity. /// diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index 344cb8cf993ec7..8d84cf36fa2481 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -3,7 +3,8 @@ using Content.Shared.Database; using Content.Shared.Gravity; using Content.Shared.Physics; -using Content.Shared.Movement.Pulling.Events; +using Content.Shared.Physics.Pull; +using Robust.Shared.GameStates; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; @@ -93,8 +94,8 @@ private void OnSleep(EntityUid uid, ThrownItemComponent thrownItem, ref PhysicsS private void HandlePullStarted(PullStartedMessage message) { // TODO: this isn't directed so things have to be done the bad way - if (EntityManager.TryGetComponent(message.PulledUid, out ThrownItemComponent? thrownItemComponent)) - StopThrow(message.PulledUid, thrownItemComponent); + if (EntityManager.TryGetComponent(message.Pulled.Owner, out ThrownItemComponent? thrownItemComponent)) + StopThrow(message.Pulled.Owner, thrownItemComponent); } public void StopThrow(EntityUid uid, ThrownItemComponent thrownItemComponent)