From 0a82aba88e3f6c48cc417859a9a70485993b1f45 Mon Sep 17 00:00:00 2001 From: DrSmugleaf Date: Mon, 27 Jul 2020 00:54:32 +0200 Subject: [PATCH] Add pulling (#1409) * Initial framework for pulling. * Make it possible to pull items via (temporary) keybind Ctrl+Click, make items follow the player correctly. * Make other objects pullable, implement functionality for moving an object being pulled, make only one object able to be pulled at a time. * Make sure that MoveTo won't allow collisions with the player * Update everything to work with the new physics engine * Update Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs Co-authored-by: ComicIronic * Physics update and convert to direct type casts * Add notnull checks * Add pull keybinds to the tutorial window * Move PullController to shared * Fix pulled items getting left behind * Fix moving pulled objects into walls * Remove flooring of coordinates when moving pulled objects * Add missing null check in PutInHand * Change pulling keybind to control and throwing to alt * Change PhysicsComponent references to IPhysicsComponent * Add trying to pull a pulled entity disabling the pull * Add pulled status effect * Fix merge conflicts * Merge fixes * Make players pullable * Fix being able to pull yourself * Change pull moving to use a velocity * Update pulled and pulling icons to not be buckle A tragedy * Make pulled and pulling icons more consistent * Remove empty not pulled and not pulling images * Pulled icon update * Pulled icon update * Add clicking pulling status effect to stop the pull * Fix spacewalking when pulling * Merge conflict fixes * Add a pull verb * Fix nullable error * Add pulling through the entity drop down menu Co-authored-by: Jackson Lewis Co-authored-by: ComicIronic --- .../Components/Items/HandsComponent.cs | 1 + .../GameObjects/EntitySystems/VerbSystem.cs | 4 +- Content.Client/IgnoredComponents.cs | 1 + Content.Client/Input/ContentContexts.cs | 4 + .../UserInterface/TutorialWindow.cs | 6 +- .../Components/GUI/HandsComponent.cs | 69 +++++++++++ .../Mobs/ServerStatusEffectsComponent.cs | 9 ++ .../Components/Movement/PullableComponent.cs | 10 ++ .../EntitySystems/Click/InteractionSystem.cs | 71 +++++++++++ .../GameObjects/EntitySystems/HandsSystem.cs | 30 ++++- Content.Server/GlobalVerbs/PullingVerb.cs | 76 ++++++++++++ .../Components/Items/IHandsComponent.cs | 3 +- .../Components/Items/ISharedHandsComponent.cs | 9 ++ .../Components/Items/SharedHandsComponent.cs | 32 +++-- .../Mobs/SharedStatusEffectsComponent.cs | 4 +- .../EntitySystems/SharedMoverSystem.cs | 6 + Content.Shared/Input/ContentKeyFunctions.cs | 2 + Content.Shared/Physics/PullController.cs | 114 ++++++++++++++++++ .../Constructible/Storage/Closets/closet.yml | 1 + .../Constructible/Storage/crate_base.yml | 1 + .../Entities/Mobs/Species/human.yml | 1 + Resources/Prototypes/Entities/item_base.yml | 1 + .../Interface/StatusEffects/Pull/pulled.png | Bin 0 -> 3952 bytes .../Interface/StatusEffects/Pull/pulling.png | Bin 0 -> 525 bytes Resources/keybinds.yml | 9 ++ SpaceStation14.sln.DotSettings | 1 + 26 files changed, 450 insertions(+), 15 deletions(-) create mode 100644 Content.Server/GameObjects/Components/Movement/PullableComponent.cs create mode 100644 Content.Server/GlobalVerbs/PullingVerb.cs create mode 100644 Content.Shared/GameObjects/Components/Items/ISharedHandsComponent.cs create mode 100644 Content.Shared/Physics/PullController.cs create mode 100644 Resources/Textures/Interface/StatusEffects/Pull/pulled.png create mode 100644 Resources/Textures/Interface/StatusEffects/Pull/pulling.png 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 0000000000000000000000000000000000000000..3d1aec59f6b8ea7354fb78ea7729e1903517b09c GIT binary patch literal 3952 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 KMNUMnLSTYpzkeA3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/StatusEffects/Pull/pulling.png b/Resources/Textures/Interface/StatusEffects/Pull/pulling.png new file mode 100644 index 0000000000000000000000000000000000000000..d57f12cd63a534e6cab4ca49f9214bc4d0d401b4 GIT binary patch literal 525 zcmV+o0`mQdP)~XQCI&5Fa=<_yjabhaI<-O zwBzJg0~p8GroUT1;IFUyOSS6r@3xO|_|*W~_H@@i-{0&Ph=q}seG>j5E(gs)}HKq0V!pBFaX%gUdFcu5GB$6EdYF83&bJpX92K# z4|5sSQNP#3K`{W>+r{yT17$}p-)8~vr(S3gpb+ zfUF|9z{`!V0$>@C?@{3S6phAL0r)5^`>p9ezbT6fPM`S({oP+ie*(ZeQ+s$?6}p=6 P00000NkvXXu0mjfJ{9Ir literal 0 HcmV?d00001 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