diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs index 8d0d3bf7de..0c13e0800e 100644 --- a/Content.Client/GameObjects/Components/Items/HandsComponent.cs +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -14,6 +14,7 @@ using Robust.Shared.ViewVariables; namespace Content.Client.GameObjects.Components.Items { [RegisterComponent] + [ComponentReference(typeof(ISharedHandsComponent))] public class HandsComponent : SharedHandsComponent { private HandsGui? _gui; diff --git a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs index 80ebb33036..6c5fc11445 100644 --- a/Content.Client/GameObjects/EntitySystems/VerbSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/VerbSystem.cs @@ -447,7 +447,9 @@ namespace Content.Client.GameObjects.EntitySystems } if (args.Function == EngineKeyFunctions.Use || - args.Function == ContentKeyFunctions.Point) + args.Function == ContentKeyFunctions.Point || + args.Function == ContentKeyFunctions.TryPullObject || + args.Function == ContentKeyFunctions.MovePulledObject) { // TODO: Remove an entity from the menu when it is deleted if (_entity.Deleted) diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 3b6f8782cb..84fc46ffa8 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -136,6 +136,7 @@ "TrashSpawner", "Pill", "RCD", + "Pullable", }; } } diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 79a4cfe0f0..1250627a4f 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -27,6 +27,8 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.OpenCharacterMenu); human.AddFunction(ContentKeyFunctions.ActivateItemInWorld); human.AddFunction(ContentKeyFunctions.ThrowItemInHand); + human.AddFunction(ContentKeyFunctions.TryPullObject); + human.AddFunction(ContentKeyFunctions.MovePulledObject); human.AddFunction(ContentKeyFunctions.OpenContextMenu); human.AddFunction(ContentKeyFunctions.OpenCraftingMenu); human.AddFunction(ContentKeyFunctions.OpenInventoryMenu); @@ -36,6 +38,8 @@ namespace Content.Client.Input human.AddFunction(ContentKeyFunctions.ToggleCombatMode); human.AddFunction(ContentKeyFunctions.WideAttack); human.AddFunction(ContentKeyFunctions.Point); + human.AddFunction(ContentKeyFunctions.TryPullObject); + human.AddFunction(ContentKeyFunctions.MovePulledObject); var ghost = contexts.New("ghost", "common"); ghost.AddFunction(EngineKeyFunctions.MoveUp); diff --git a/Content.Client/UserInterface/TutorialWindow.cs b/Content.Client/UserInterface/TutorialWindow.cs index a91dcc01e5..6e4b90f1d6 100644 --- a/Content.Client/UserInterface/TutorialWindow.cs +++ b/Content.Client/UserInterface/TutorialWindow.cs @@ -81,6 +81,8 @@ Use hand/object in hand: [color=#a4885c]{22}[/color] Do wide attack: [color=#a4885c]{23}[/color] Use targeted entity: [color=#a4885c]{11}[/color] Throw held item: [color=#a4885c]{12}[/color] +Pull entity: [color=#a4885c]{30}[/color] +Move pulled entity: [color=#a4885c]{29}[/color] Examine entity: [color=#a4885c]{13}[/color] Point somewhere: [color=#a4885c]{28}[/color] Open entity context menu: [color=#a4885c]{14}[/color] @@ -116,7 +118,9 @@ Toggle sandbox window: [color=#a4885c]{21}[/color]", Key(SmartEquipBelt), Key(FocusOOC), Key(FocusAdminChat), - Key(Point))); + Key(Point), + Key(TryPullObject), + Key(MovePulledObject))); //Gameplay VBox.AddChild(new Label { FontOverride = headerFont, Text = "\nGameplay" }); diff --git a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs index d124e7e293..a39f6ba8ec 100644 --- a/Content.Server/GameObjects/Components/GUI/HandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -3,11 +3,15 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Movement; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Items; using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Shared.BodySystem; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.Physics; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystemMessages; @@ -27,6 +31,7 @@ namespace Content.Server.GameObjects.Components.GUI { [RegisterComponent] [ComponentReference(typeof(IHandsComponent))] + [ComponentReference(typeof(ISharedHandsComponent))] public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved { #pragma warning disable 649 @@ -490,6 +495,34 @@ namespace Content.Server.GameObjects.Components.GUI return false; } + public void StartPull(PullableComponent pullable) + { + if (Owner == pullable.Owner) + { + return; + } + + if (IsPulling) + { + StopPull(); + } + + PulledObject = pullable.Owner.GetComponent(); + var controller = PulledObject!.EnsureController(); + controller!.StartPull(Owner.GetComponent()); + + AddPullingStatuses(); + } + + public void MovePulledObject(GridCoordinates puller, GridCoordinates to) + { + if (PulledObject != null && + PulledObject.TryGetController(out PullController controller)) + { + controller.TryMoveTo(puller, to); + } + } + public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) { base.HandleNetworkMessage(message, channel, session); @@ -600,6 +633,42 @@ namespace Content.Server.GameObjects.Components.GUI } } + private void AddPullingStatuses() + { + if (PulledObject?.Owner != null && + PulledObject.Owner.TryGetComponent(out ServerStatusEffectsComponent pulledStatus)) + { + pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled, + "/Textures/Interface/StatusEffects/Pull/pulled.png"); + } + + if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus)) + { + ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling, + "/Textures/Interface/StatusEffects/Pull/pulling.png"); + } + } + + private void RemovePullingStatuses() + { + if (PulledObject?.Owner != null && + PulledObject.Owner.TryGetComponent(out ServerStatusEffectsComponent pulledStatus)) + { + pulledStatus.RemoveStatusEffect(StatusEffect.Pulled); + } + + if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus)) + { + ownerStatus.RemoveStatusEffect(StatusEffect.Pulling); + } + } + + public override void StopPull() + { + RemovePullingStatuses(); + base.StopPull(); + } + void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs eventArgs) { if (eventArgs.Part.PartType != BodyPartType.Hand) diff --git a/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs b/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs index 208c7227b2..25ea510016 100644 --- a/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/ServerStatusEffectsComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Content.Server.GameObjects.Components.Buckle; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Movement; using Content.Shared.GameObjects.Components.Mobs; using Robust.Shared.GameObjects; @@ -104,6 +105,14 @@ namespace Content.Server.GameObjects.Components.Mobs controller.RemoveController(); break; + case StatusEffect.Pulling: + if (!player.TryGetComponent(out HandsComponent hands)) + { + break; + } + + hands.StopPull(); + break; } break; diff --git a/Content.Server/GameObjects/Components/Movement/PullableComponent.cs b/Content.Server/GameObjects/Components/Movement/PullableComponent.cs new file mode 100644 index 0000000000..4875dfdd2d --- /dev/null +++ b/Content.Server/GameObjects/Components/Movement/PullableComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Movement +{ + [RegisterComponent] + public class PullableComponent: Component + { + public override string Name => "Pullable"; + } +} diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 857ef12576..627d0ffe20 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -1,6 +1,8 @@ using System; using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Movement; using Content.Server.GameObjects.Components.Timing; using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Utility; @@ -9,11 +11,13 @@ using Content.Shared.GameObjects.EntitySystemMessages; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Input; using Content.Shared.Interfaces.GameObjects.Components; +using Content.Shared.Physics; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.Interfaces.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components; using Robust.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.Interfaces.GameObjects; @@ -48,6 +52,7 @@ namespace Content.Server.GameObjects.EntitySystems.Click new PointerInputCmdHandler(HandleWideAttack)) .Bind(ContentKeyFunctions.ActivateItemInWorld, new PointerInputCmdHandler(HandleActivateItemInWorld)) + .Bind(ContentKeyFunctions.TryPullObject, new PointerInputCmdHandler(HandleTryPullObject)) .Register(); } @@ -221,6 +226,72 @@ namespace Content.Server.GameObjects.EntitySystems.Click return true; } + private bool HandleTryPullObject(ICommonSession session, GridCoordinates coords, EntityUid uid) + { + // client sanitization + if (!_mapManager.GridExists(coords.GridID)) + { + Logger.InfoS("system.interaction", $"Invalid Coordinates for pulling: client={session}, coords={coords}"); + return false; + } + + if (uid.IsClientSide()) + { + Logger.WarningS("system.interaction", + $"Client sent pull interaction with client-side entity. Session={session}, Uid={uid}"); + return false; + } + + var player = session.AttachedEntity; + + if (player == null) + { + Logger.WarningS("system.interaction", + $"Client sent pulling interaction with no attached entity. Session={session}, Uid={uid}"); + return false; + } + + if (!EntityManager.TryGetEntity(uid, out var pulledObject)) + { + return false; + } + + if (player == pulledObject) + { + return false; + } + + if (!pulledObject.TryGetComponent(out var pull)) + { + return false; + } + + if (!player.TryGetComponent(out var hands)) + { + return false; + } + + var dist = player.Transform.GridPosition.Position - pulledObject.Transform.GridPosition.Position; + if (dist.LengthSquared > InteractionRangeSquared) + { + return false; + } + + var physics = pull.Owner.GetComponent(); + var controller = physics.EnsureController(); + + if (controller.GettingPulled) + { + hands.StopPull(); + } + else + { + hands.StartPull(pull); + } + + return false; + } + private void UserInteraction(IEntity player, GridCoordinates coordinates, EntityUid clickedUid) { // Get entity clicked upon from UID if valid UID, if not assume no entity clicked upon and null diff --git a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs index 94cdd828ff..5d2101367a 100644 --- a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs @@ -52,7 +52,9 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction .Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem)) .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)) - .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)).Register(); + .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)) + .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) + .Register(); } /// @@ -199,13 +201,16 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction if (plyEnt == null || !plyEnt.IsValid()) return; - if (!plyEnt.TryGetComponent(out HandsComponent handsComp) || !plyEnt.TryGetComponent(out InventoryComponent inventoryComp)) + if (!plyEnt.TryGetComponent(out HandsComponent handsComp) || + !plyEnt.TryGetComponent(out InventoryComponent inventoryComp)) return; if (!inventoryComp.TryGetSlotItem(equipementSlot, out ItemComponent equipmentItem) || !equipmentItem.Owner.TryGetComponent(out var storageComponent)) { - _notifyManager.PopupMessage(plyEnt, plyEnt, Loc.GetString("You have no {0} to take something out of!", EquipmentSlotDefines.SlotNames[equipementSlot].ToLower())); + _notifyManager.PopupMessage(plyEnt, plyEnt, + Loc.GetString("You have no {0} to take something out of!", + EquipmentSlotDefines.SlotNames[equipementSlot].ToLower())); return; } @@ -219,7 +224,9 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction { if (storageComponent.StoredEntities.Count == 0) { - _notifyManager.PopupMessage(plyEnt, plyEnt, Loc.GetString("There's nothing in your {0} to take out!", EquipmentSlotDefines.SlotNames[equipementSlot].ToLower())); + _notifyManager.PopupMessage(plyEnt, plyEnt, + Loc.GetString("There's nothing in your {0} to take out!", + EquipmentSlotDefines.SlotNames[equipementSlot].ToLower())); } else { @@ -229,5 +236,20 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction } } } + + private bool HandleMovePulledObject(ICommonSession session, GridCoordinates coords, EntityUid uid) + { + var playerEntity = session.AttachedEntity; + + if (playerEntity == null || + !playerEntity.TryGetComponent(out var hands)) + { + return false; + } + + hands.MovePulledObject(playerEntity.Transform.GridPosition, coords); + + return false; + } } } diff --git a/Content.Server/GlobalVerbs/PullingVerb.cs b/Content.Server/GlobalVerbs/PullingVerb.cs new file mode 100644 index 0000000000..4fa4822058 --- /dev/null +++ b/Content.Server/GlobalVerbs/PullingVerb.cs @@ -0,0 +1,76 @@ +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Movement; +using Content.Shared.GameObjects; +using Content.Shared.GameObjects.Components.Items; +using Content.Shared.GameObjects.EntitySystems; +using Content.Shared.Physics; +using Robust.Server.Interfaces.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Localization; + +namespace Content.Server.GlobalVerbs +{ + /// + /// Global verb that pulls an entity. + /// + [GlobalVerb] + public class PullingVerb : GlobalVerb + { + public override bool RequireInteractionRange => false; + + public override void GetData(IEntity user, IEntity target, VerbData data) + { + data.Visibility = VerbVisibility.Invisible; + + if (user == target || + !user.HasComponent() || + !target.HasComponent()) + { + return; + } + + var dist = user.Transform.GridPosition.Position - target.Transform.GridPosition.Position; + if (dist.LengthSquared > SharedInteractionSystem.InteractionRangeSquared) + { + return; + } + + if (!user.HasComponent() || + !user.TryGetComponent(out ICollidableComponent userCollidable) || + !target.TryGetComponent(out ICollidableComponent targetCollidable)) + { + return; + } + + var controller = targetCollidable.EnsureController(); + + data.Visibility = VerbVisibility.Visible; + data.Text = controller.Puller == userCollidable + ? Loc.GetString("Stop pulling") + : Loc.GetString("Pull"); + } + + public override void Activate(IEntity user, IEntity target) + { + if (!user.TryGetComponent(out ICollidableComponent userCollidable) || + !target.TryGetComponent(out ICollidableComponent targetCollidable) || + !target.TryGetComponent(out PullableComponent pullable) || + !user.TryGetComponent(out HandsComponent hands)) + { + return; + } + + var controller = targetCollidable.EnsureController(); + + if (controller.Puller == userCollidable) + { + hands.StopPull(); + } + else + { + hands.StartPull(pullable); + } + } + } +} diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs index 1719ab7afc..83d1b509a7 100644 --- a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Content.Server.GameObjects.Components; +using Content.Shared.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystemMessages; @@ -9,7 +10,7 @@ using Robust.Shared.Map; namespace Content.Server.Interfaces.GameObjects.Components.Items { - public interface IHandsComponent : IComponent + public interface IHandsComponent : ISharedHandsComponent { /// /// The hand name of the currently active hand. diff --git a/Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs new file mode 100644 index 0000000000..a6f2188389 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Items +{ + public interface ISharedHandsComponent : IComponent + { + void StopPull(); + } +} diff --git a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs index e445c587a7..47a15e5f10 100644 --- a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs +++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs @@ -1,16 +1,34 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; +#nullable enable +using System; +using Content.Shared.Physics; using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.GameObjects.Components; using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; namespace Content.Shared.GameObjects.Components.Items { - public abstract class SharedHandsComponent : Component + public abstract class SharedHandsComponent : Component, ISharedHandsComponent { public sealed override string Name => "Hands"; public sealed override uint? NetID => ContentNetIDs.HANDS; + + [ViewVariables] + protected ICollidableComponent? PulledObject; + + [ViewVariables] + protected bool IsPulling => PulledObject != null; + + public virtual void StopPull() + { + if (PulledObject != null && + PulledObject.TryGetController(out PullController controller)) + { + controller.StopPull(); + } + + PulledObject = null; + } } [Serializable, NetSerializable] @@ -35,9 +53,9 @@ namespace Content.Shared.GameObjects.Components.Items public class HandsComponentState : ComponentState { public readonly SharedHand[] Hands; - public readonly string ActiveIndex; + public readonly string? ActiveIndex; - public HandsComponentState(SharedHand[] hands, string activeIndex) : base(ContentNetIDs.HANDS) + public HandsComponentState(SharedHand[] hands, string? activeIndex) : base(ContentNetIDs.HANDS) { Hands = hands; ActiveIndex = activeIndex; diff --git a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs index d068021e85..d7fcefb95a 100644 --- a/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs +++ b/Content.Shared/GameObjects/Components/Mobs/SharedStatusEffectsComponent.cs @@ -58,6 +58,8 @@ namespace Content.Shared.GameObjects.Components.Mobs Thirst, Stun, Buckled, - Piloting + Piloting, + Pulling, + Pulled } } diff --git a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs index 75abb9d902..8c2cdcc9c3 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedMoverSystem.cs @@ -132,6 +132,12 @@ namespace Content.Shared.GameObjects.EntitySystems continue; } + // Don't count pulled entities + if (otherCollider.HasController()) + { + continue; + } + // TODO: Item check. var touching = ((collider.CollisionMask & otherCollider.CollisionLayer) != 0x0 || (otherCollider.CollisionMask & collider.CollisionLayer) != 0x0) // Ensure collision diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 9d0ef01344..5cf834bea3 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -22,6 +22,8 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction OpenTutorial = "OpenTutorial"; public static readonly BoundKeyFunction SwapHands = "SwapHands"; public static readonly BoundKeyFunction ThrowItemInHand = "ThrowItemInHand"; + public static readonly BoundKeyFunction TryPullObject = "TryPullObject"; + public static readonly BoundKeyFunction MovePulledObject = "MovePulledObject"; public static readonly BoundKeyFunction ToggleCombatMode = "ToggleCombatMode"; public static readonly BoundKeyFunction MouseMiddle = "MouseMiddle"; public static readonly BoundKeyFunction OpenEntitySpawnWindow = "OpenEntitySpawnWindow"; diff --git a/Content.Shared/Physics/PullController.cs b/Content.Shared/Physics/PullController.cs new file mode 100644 index 0000000000..c0ea728571 --- /dev/null +++ b/Content.Shared/Physics/PullController.cs @@ -0,0 +1,114 @@ +#nullable enable +using System; +using Content.Shared.GameObjects.Components.Items; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.Interfaces.Map; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Physics; + +namespace Content.Shared.Physics +{ + public class PullController : VirtualController + { + private const float DistBeforePull = 1.0f; + + private const float DistBeforeStopPull = SharedInteractionSystem.InteractionRange; + + private ICollidableComponent? _puller; + + public bool GettingPulled => _puller != null; + + private GridCoordinates? _movingTo; + + public ICollidableComponent? Puller => _puller; + + public void StartPull(ICollidableComponent? pull) + { + _puller = pull; + } + + public void StopPull() + { + _puller = null; + ControlledComponent?.TryRemoveController(); + } + + public void TryMoveTo(GridCoordinates from, GridCoordinates to) + { + if (_puller == null || ControlledComponent == null) + { + return; + } + + var mapManager = IoCManager.Resolve(); + + if (!from.InRange(mapManager, to, SharedInteractionSystem.InteractionRange)) + { + return; + } + + var dist = _puller.Owner.Transform.GridPosition.Position - to.Position; + + if (Math.Sqrt(dist.LengthSquared) > DistBeforeStopPull || + Math.Sqrt(dist.LengthSquared) < 0.25f) + { + return; + } + + _movingTo = to; + } + + public override void UpdateBeforeProcessing() + { + if (_puller == null || ControlledComponent == null) + { + return; + } + + // Are we outside of pulling range? + var dist = _puller.Owner.Transform.WorldPosition - ControlledComponent.Owner.Transform.WorldPosition; + + if (dist.Length > DistBeforeStopPull) + { + _puller.Owner.GetComponent().StopPull(); + } + else if (_movingTo.HasValue) + { + var diff = _movingTo.Value.Position - ControlledComponent.Owner.Transform.GridPosition.Position; + LinearVelocity = diff.Normalized * 5; + } + else if (dist.Length > DistBeforePull) + { + LinearVelocity = dist.Normalized * _puller.LinearVelocity.Length * 1.1f; + } + else + { + LinearVelocity = Vector2.Zero; + } + } + + public override void UpdateAfterProcessing() + { + base.UpdateAfterProcessing(); + + if (ControlledComponent == null) + { + _movingTo = null; + return; + } + + if (_movingTo == null) + { + return; + } + + if (ControlledComponent.Owner.Transform.GridPosition.Position.EqualsApprox(_movingTo.Value.Position, 0.01)) + { + _movingTo = null; + } + } + } +} diff --git a/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml b/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml index 2439fda242..bea3b58d87 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/Closets/closet.yml @@ -49,6 +49,7 @@ state_closed: generic_door - type: LoopingSound - type: Anchorable + - type: Pullable placement: snap: - Wall diff --git a/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml b/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml index f7fed23689..8d239a47da 100644 --- a/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml +++ b/Resources/Prototypes/Entities/Constructible/Storage/crate_base.yml @@ -47,3 +47,4 @@ state_open: crate_open state_closed: crate_door - type: LoopingSound + - type: Pullable diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 08a73057fd..2e2989ee36 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -138,6 +138,7 @@ arc: fist - type: Grammar proper: true + - type: Pullable - type: entity save: false diff --git a/Resources/Prototypes/Entities/item_base.yml b/Resources/Prototypes/Entities/item_base.yml index 2ec229b93f..60335dfa4a 100644 --- a/Resources/Prototypes/Entities/item_base.yml +++ b/Resources/Prototypes/Entities/item_base.yml @@ -19,3 +19,4 @@ mass: 5 - type: Sprite drawdepth: Items + - type: Pullable diff --git a/Resources/Textures/Interface/StatusEffects/Pull/pulled.png b/Resources/Textures/Interface/StatusEffects/Pull/pulled.png new file mode 100644 index 0000000000..3d1aec59f6 Binary files /dev/null and b/Resources/Textures/Interface/StatusEffects/Pull/pulled.png differ diff --git a/Resources/Textures/Interface/StatusEffects/Pull/pulling.png b/Resources/Textures/Interface/StatusEffects/Pull/pulling.png new file mode 100644 index 0000000000..d57f12cd63 Binary files /dev/null and b/Resources/Textures/Interface/StatusEffects/Pull/pulling.png differ diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 66d9113fb3..2431c3f3c9 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -102,6 +102,15 @@ binds: type: state key: MouseLeft canFocus: true + mod1: Alt +- function: TryPullObject + type: state + canFocus: true + key: MouseLeft + mod1: Control +- function: MovePulledObject + type: state + key: MouseRight mod1: Control - function: OpenContextMenu type: state diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 038e33b4ba..a356ba2137 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -62,6 +62,7 @@ True True True + True True True True