From 632e72b817c110dcda6fa008ff55d26136753be9 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 31 Jul 2021 03:14:00 +0200 Subject: [PATCH] Improve hands & pulling (#4389) --- .../Actions/ClientActionsComponent.cs | 38 +--- .../Hands/HandVirtualPullItemStatus.xaml | 3 + .../Hands/HandVirtualPullItemStatus.xaml.cs | 13 ++ Content.Client/Hands/HandsComponent.cs | 127 +----------- Content.Client/Hands/HandsGui.xaml | 6 + .../Hands/{HandsGui.cs => HandsGui.xaml.cs} | 142 ++++++------- Content.Client/Hands/HandsSystem.cs | 80 -------- .../Hands/Systems/HandVirtualPullSystem.cs | 18 ++ Content.Client/Hands/Systems/HandsSystem.cs | 149 ++++++++++++++ Content.Client/Items/ItemStatusMessages.cs | 32 +++ .../Items/Managers/IItemSlotManager.cs | 19 +- .../Items/Managers/ItemSlotManager.cs | 56 +++++- Content.Client/Items/UI/ItemSlotButton.cs | 35 +++- Content.Client/Items/UI/ItemStatusPanel.cs | 40 +++- .../Fluids/Components/SpillableComponent.cs | 1 + .../Hands/Components/HandsComponent.cs | 97 ++------- .../Hands/Systems/HandVirtualPullSystem.cs | 57 ++++++ .../Hands/{ => Systems}/HandsSystem.cs | 136 ++++++++++++- .../Interaction/InteractionSystem.cs | 23 ++- .../Components/HandVirtualPullComponent.cs | 50 +++++ .../Hands/Components/SharedHandsComponent.cs | 186 +++++++----------- Content.Shared/Hands/SharedHandsSystem.cs | 48 ++--- Content.Shared/Interaction/BeforeInteract.cs | 53 +++++ .../{DragDrop => Interaction}/IDropped.cs | 2 +- .../Components/SharedPullableComponent.cs | 40 +--- .../Components/SharedPullerComponent.cs | 29 +-- .../Events}/PullAttemptMessage.cs | 0 .../Pull => Pulling/Events}/PullMessage.cs | 2 +- .../Events}/PullStartedMessage.cs | 0 .../Events}/PullStoppedMessage.cs | 0 .../Pulling/Systems/SharedPullerSystem.cs | 44 +++++ .../{ => Systems}/SharedPullingSystem.cs | 23 +++ .../Entities/Virtual/virtual_pull_item.yml | 8 + 33 files changed, 945 insertions(+), 612 deletions(-) create mode 100644 Content.Client/Hands/HandVirtualPullItemStatus.xaml create mode 100644 Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs create mode 100644 Content.Client/Hands/HandsGui.xaml rename Content.Client/Hands/{HandsGui.cs => HandsGui.xaml.cs} (68%) delete mode 100644 Content.Client/Hands/HandsSystem.cs create mode 100644 Content.Client/Hands/Systems/HandVirtualPullSystem.cs create mode 100644 Content.Client/Hands/Systems/HandsSystem.cs create mode 100644 Content.Client/Items/ItemStatusMessages.cs create mode 100644 Content.Server/Hands/Systems/HandVirtualPullSystem.cs rename Content.Server/Hands/{ => Systems}/HandsSystem.cs (58%) create mode 100644 Content.Shared/Hands/Components/HandVirtualPullComponent.cs create mode 100644 Content.Shared/Interaction/BeforeInteract.cs rename Content.Shared/{DragDrop => Interaction}/IDropped.cs (97%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullAttemptMessage.cs (100%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullMessage.cs (87%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullStartedMessage.cs (100%) rename Content.Shared/{Physics/Pull => Pulling/Events}/PullStoppedMessage.cs (100%) create mode 100644 Content.Shared/Pulling/Systems/SharedPullerSystem.cs rename Content.Shared/Pulling/{ => Systems}/SharedPullingSystem.cs (88%) create mode 100644 Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml diff --git a/Content.Client/Actions/ClientActionsComponent.cs b/Content.Client/Actions/ClientActionsComponent.cs index 6df12b2581..3caab80cc6 100644 --- a/Content.Client/Actions/ClientActionsComponent.cs +++ b/Content.Client/Actions/ClientActionsComponent.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; using Content.Client.Actions.Assignments; using Content.Client.Actions.UI; using Content.Client.Hands; using Content.Client.Inventory; -using Content.Client.Items.UI; +using Content.Client.Items.Managers; using Content.Shared.Actions.Components; using Content.Shared.Actions.Prototypes; using Robust.Client.GameObjects; @@ -26,12 +25,13 @@ namespace Content.Client.Actions public const byte Slots = 10; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; [ComponentDependency] private readonly HandsComponent? _handsComponent = null; [ComponentDependency] private readonly ClientInventoryComponent? _inventoryComponent = null; private ActionsUI? _ui; - private readonly List _highlightingItemSlots = new(); + private EntityUid _highlightedEntity; /// /// Current assignments for all hotbars / slots for this entity. @@ -225,26 +225,8 @@ namespace Content.Client.Actions { StopHighlightingItemSlots(); - // figure out if it's in hand or inventory and highlight it - foreach (var hand in _handsComponent!.Gui!.Hands) - { - if (hand.HeldItem != item || hand.HandButton == null) continue; - _highlightingItemSlots.Add(hand.HandButton); - hand.HandButton.Highlight(true); - return; - } - - foreach (var (slot, slotItem) in _inventoryComponent!.AllSlots) - { - if (slotItem != item) continue; - foreach (var itemSlotButton in - _inventoryComponent.InterfaceController.GetItemSlotButtons(slot)) - { - _highlightingItemSlots.Add(itemSlotButton); - itemSlotButton.Highlight(true); - } - return; - } + _highlightedEntity = item.Uid; + _itemSlotManager.HighlightEntity(item.Uid); } /// @@ -252,11 +234,11 @@ namespace Content.Client.Actions /// public void StopHighlightingItemSlots() { - foreach (var itemSlot in _highlightingItemSlots) - { - itemSlot.Highlight(false); - } - _highlightingItemSlots.Clear(); + if (_highlightedEntity == default) + return; + + _itemSlotManager.UnHighlightEntity(_highlightedEntity); + _highlightedEntity = default; } public void ToggleActionsMenu() diff --git a/Content.Client/Hands/HandVirtualPullItemStatus.xaml b/Content.Client/Hands/HandVirtualPullItemStatus.xaml new file mode 100644 index 0000000000..9fac1e0d08 --- /dev/null +++ b/Content.Client/Hands/HandVirtualPullItemStatus.xaml @@ -0,0 +1,3 @@ + + diff --git a/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs b/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs new file mode 100644 index 0000000000..f4324f6e41 --- /dev/null +++ b/Content.Client/Hands/HandVirtualPullItemStatus.xaml.cs @@ -0,0 +1,13 @@ +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Hands +{ + public sealed class HandVirtualPullItemStatus : Control + { + public HandVirtualPullItemStatus() + { + RobustXamlLoader.Load(this); + } + } +} diff --git a/Content.Client/Hands/HandsComponent.cs b/Content.Client/Hands/HandsComponent.cs index 0299384e33..4276ca1e9c 100644 --- a/Content.Client/Hands/HandsComponent.cs +++ b/Content.Client/Hands/HandsComponent.cs @@ -1,15 +1,8 @@ using System.Collections.Generic; -using Content.Client.Animations; -using Content.Client.HUD; using Content.Shared.Hands.Components; using Content.Shared.Item; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Network; -using Robust.Shared.Players; -using Robust.Shared.Timing; -using Robust.Shared.ViewVariables; namespace Content.Client.Hands { @@ -18,16 +11,7 @@ namespace Content.Client.Hands [ComponentReference(typeof(SharedHandsComponent))] public class HandsComponent : SharedHandsComponent { - [Dependency] private readonly IGameHud _gameHud = default!; - - [ViewVariables] - public HandsGui? Gui { get; private set; } - - protected override void OnRemove() - { - ClearGui(); - base.OnRemove(); - } + public HandsGui? Gui { get; set; } public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) { @@ -38,94 +22,23 @@ namespace Content.Client.Hands foreach (var handState in state.Hands) { - var newHand = new Hand(handState.Name, handState.Enabled, handState.Location); + var newHand = new Hand(handState.Name, handState.Location); Hands.Add(newHand); } + ActiveHand = state.ActiveHand; UpdateHandContainers(); UpdateHandVisualizer(); - UpdateHandsGuiState(); - } - - public void SettupGui() - { - if (Gui == null) - { - Gui = new HandsGui(); - _gameHud.HandsContainer.AddChild(Gui); - Gui.HandClick += args => OnHandClick(args.HandClicked); - Gui.HandActivate += args => OnActivateInHand(args.HandUsed); - UpdateHandsGuiState(); - } - } - - public void ClearGui() - { - Gui?.Dispose(); - Gui = null; - } - - public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, netChannel, session); - - switch (message) - { - case PickupAnimationMessage msg: - RunPickupAnimation(msg); - break; - } + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this }); } public override void HandsModified() { - base.HandsModified(); - UpdateHandContainers(); UpdateHandVisualizer(); - UpdateHandsGuiState(); - } - private void OnHandClick(string handClicked) - { - if (!TryGetHand(handClicked, out var pressedHand)) - return; - - if (!TryGetActiveHand(out var activeHand)) - return; - - var pressedEntity = pressedHand.HeldEntity; - var activeEntity = activeHand.HeldEntity; - - if (pressedHand == activeHand && activeEntity != null) - { - SendNetworkMessage(new UseInHandMsg()); //use item in hand - return; - } - - if (pressedHand != activeHand && pressedEntity == null) - { - SendNetworkMessage(new ClientChangedHandMsg(pressedHand.Name)); //swap hand - return; - } - - if (pressedHand != activeHand && pressedEntity != null && activeEntity != null) - { - SendNetworkMessage(new ClientAttackByInHandMsg(pressedHand.Name)); //use active item on held item - return; - } - - if (pressedHand != activeHand && pressedEntity != null && activeEntity == null) - { - SendNetworkMessage(new MoveItemFromHandMsg(pressedHand.Name)); //move item in hand to active hand - return; - } - } - - private void OnActivateInHand(string handActivated) - { - SendNetworkMessage(new ActivateInHandMsg(handActivated)); + base.HandsModified(); } public void UpdateHandContainers() @@ -149,27 +62,10 @@ namespace Content.Client.Hands appearance.SetData(HandsVisuals.VisualState, GetHandsVisualState()); } - public void UpdateHandsGuiState() - { - Gui?.SetState(GetHandsGuiState()); - } - - private HandsGuiState GetHandsGuiState() - { - var handStates = new List(); - - foreach (var hand in ReadOnlyHands) - { - var handState = new GuiHand(hand.Name, hand.Location, hand.HeldEntity, hand.Enabled); - handStates.Add(handState); - } - return new HandsGuiState(handStates, ActiveHand); - } - private HandsVisualState GetHandsVisualState() { var hands = new List(); - foreach (var hand in ReadOnlyHands) + foreach (var hand in Hands) { if (hand.HeldEntity == null) continue; @@ -182,16 +78,5 @@ namespace Content.Client.Hands } return new(hands); } - - private void RunPickupAnimation(PickupAnimationMessage msg) - { - if (!Owner.EntityManager.TryGetEntity(msg.EntityUid, out var entity)) - return; - - if (!IoCManager.Resolve().IsFirstTimePredicted) - return; - - ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection); - } } } diff --git a/Content.Client/Hands/HandsGui.xaml b/Content.Client/Hands/HandsGui.xaml new file mode 100644 index 0000000000..d39c044821 --- /dev/null +++ b/Content.Client/Hands/HandsGui.xaml @@ -0,0 +1,6 @@ + + + + + + diff --git a/Content.Client/Hands/HandsGui.cs b/Content.Client/Hands/HandsGui.xaml.cs similarity index 68% rename from Content.Client/Hands/HandsGui.cs rename to Content.Client/Hands/HandsGui.xaml.cs index ff0fa47b23..63590882bf 100644 --- a/Content.Client/Hands/HandsGui.cs +++ b/Content.Client/Hands/HandsGui.xaml.cs @@ -1,87 +1,84 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Content.Client.HUD; using Content.Client.Items.Managers; using Content.Client.Items.UI; using Content.Client.Resources; -using Content.Shared; using Content.Shared.CCVar; using Content.Shared.Hands.Components; -using Content.Shared.Input; +using Robust.Client.AutoGenerated; using Robust.Client.Graphics; -using Robust.Client.Player; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.IoC; using Robust.Shared.Timing; using Robust.Shared.ViewVariables; -using static Robust.Client.UserInterface.Controls.BoxContainer; namespace Content.Client.Hands { - public class HandsGui : Control + [GenerateTypedNameReferences] + public sealed partial class HandsGui : Control { [Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly INetConfigurationManager _configManager = default!; + private readonly HandsSystem _handsSystem; + private readonly HandsComponent _handsComponent; + private Texture StorageTexture => _gameHud.GetHudTexture("back.png"); private Texture BlockedTexture => _resourceCache.GetTexture("/Textures/Interface/Inventory/blocked.png"); private ItemStatusPanel StatusPanel { get; } - private BoxContainer HandsContainer { get; } - - [ViewVariables] - public IReadOnlyList Hands => _hands; - private List _hands = new(); + [ViewVariables] private GuiHand[] _hands = Array.Empty(); private string? ActiveHand { get; set; } - public Action? HandClick; //TODO: Move to Eventbus - - public Action? HandActivate; //TODO: Move to Eventbus - - public HandsGui() + public HandsGui(HandsComponent hands, HandsSystem handsSystem) { - IoCManager.InjectDependencies(this); - _configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme); + _handsComponent = hands; + _handsSystem = handsSystem; - AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - SeparationOverride = 0, - HorizontalAlignment = HAlignment.Center, - Children = - { - new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Children = - { - (StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle)), - (HandsContainer = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalAlignment = HAlignment.Center - }), - } - }, - } - }); + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + StatusPanel = ItemStatusPanel.FromSide(HandLocation.Middle); + StatusContainer.AddChild(StatusPanel); + StatusPanel.SetPositionFirst(); } - public void SetState(HandsGuiState state) + protected override void EnteredTree() { + base.EnteredTree(); + + _handsSystem.GuiStateUpdated += HandsSystemOnGuiStateUpdated; + _configManager.OnValueChanged(CCVars.HudTheme, UpdateHudTheme); + + HandsSystemOnGuiStateUpdated(); + } + + protected override void ExitedTree() + { + base.ExitedTree(); + + _handsSystem.GuiStateUpdated -= HandsSystemOnGuiStateUpdated; + _configManager.UnsubValueChanged(CCVars.HudTheme, UpdateHudTheme); + } + + private void HandsSystemOnGuiStateUpdated() + { + var state = _handsSystem.GetGuiState(); + ActiveHand = state.ActiveHand; _hands = state.GuiHands; + Array.Sort(_hands, HandOrderComparer.Instance); UpdateGui(); } @@ -97,12 +94,15 @@ namespace Content.Client.Hands var handName = hand.Name; newButton.OnPressed += args => OnHandPressed(args, handName); - newButton.OnStoragePressed += args => OnStoragePressed(handName); - - newButton.Blocked.Visible = !hand.Enabled; + newButton.OnStoragePressed += _ => OnStoragePressed(handName); _itemSlotManager.SetItemSlot(newButton, hand.HeldItem); + + // Show blocked overlay if hand is pulling. + newButton.Blocked.Visible = + hand.HeldItem != null && hand.HeldItem.HasComponent(); } + if (TryGetActiveHand(out var activeHand)) { activeHand.HandButton.SetActiveHand(true); @@ -114,7 +114,7 @@ namespace Content.Client.Hands { if (args.Function == EngineKeyFunctions.UIClick) { - HandClick?.Invoke(new HandClickEventArgs(handName)); + _handsSystem.UIHandClick(_handsComponent, handName); } else if (TryGetHand(handName, out var hand)) { @@ -124,7 +124,7 @@ namespace Content.Client.Hands private void OnStoragePressed(string handName) { - HandActivate?.Invoke(new HandActivateEventArgs(handName)); + _handsSystem.UIHandActivate(handName); } private bool TryGetActiveHand([NotNullWhen(true)] out GuiHand? activeHand) @@ -145,6 +145,7 @@ namespace Content.Client.Hands if (hand.Name == handName) foundHand = hand; } + return foundHand != null; } @@ -153,7 +154,9 @@ namespace Content.Client.Hands base.FrameUpdate(args); foreach (var hand in _hands) + { _itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem); + } } private HandButton MakeHandButton(HandLocation buttonLocation) @@ -173,23 +176,31 @@ namespace Content.Client.Hands UpdateGui(); } - public class HandClickEventArgs + private sealed class HandOrderComparer : IComparer { - public string HandClicked { get; } + public static readonly HandOrderComparer Instance = new(); - public HandClickEventArgs(string handClicked) + public int Compare(GuiHand? x, GuiHand? y) { - HandClicked = handClicked; - } - } + if (ReferenceEquals(x, y)) return 0; + if (ReferenceEquals(null, y)) return 1; + if (ReferenceEquals(null, x)) return -1; - public class HandActivateEventArgs - { - public string HandUsed { get; } + var orderX = Map(x.HandLocation); + var orderY = Map(y.HandLocation); - public HandActivateEventArgs(string handUsed) - { - HandUsed = handUsed; + return orderX.CompareTo(orderY); + + static int Map(HandLocation loc) + { + return loc switch + { + HandLocation.Left => 3, + HandLocation.Middle => 2, + HandLocation.Right => 1, + _ => throw new ArgumentOutOfRangeException(nameof(loc), loc, null) + }; + } } } } @@ -203,7 +214,7 @@ namespace Content.Client.Hands /// The set of hands to be displayed. /// [ViewVariables] - public List GuiHands { get; } = new(); + public GuiHand[] GuiHands { get; } /// /// The name of the currently active hand. @@ -211,7 +222,7 @@ namespace Content.Client.Hands [ViewVariables] public string? ActiveHand { get; } - public HandsGuiState(List guiHands, string? activeHand = null) + public HandsGuiState(GuiHand[] guiHands, string? activeHand = null) { GuiHands = guiHands; ActiveHand = activeHand; @@ -247,18 +258,11 @@ namespace Content.Client.Hands [ViewVariables] public HandButton HandButton { get; set; } = default!; - /// - /// If this hand can be used by the player. - /// - [ViewVariables] - public bool Enabled { get; } - - public GuiHand(string name, HandLocation handLocation, IEntity? heldItem, bool enabled) + public GuiHand(string name, HandLocation handLocation, IEntity? heldItem) { Name = name; HandLocation = handLocation; HeldItem = heldItem; - Enabled = enabled; } } } diff --git a/Content.Client/Hands/HandsSystem.cs b/Content.Client/Hands/HandsSystem.cs deleted file mode 100644 index 4ba3a73c6f..0000000000 --- a/Content.Client/Hands/HandsSystem.cs +++ /dev/null @@ -1,80 +0,0 @@ -using Content.Shared.Hands; -using Content.Shared.Hands.Components; -using Content.Shared.Input; -using Robust.Client.GameObjects; -using Robust.Shared.Containers; -using Robust.Shared.GameObjects; -using Robust.Shared.Input.Binding; -using Robust.Shared.Map; -using Robust.Shared.Players; - -namespace Content.Client.Hands -{ - internal sealed class HandsSystem : SharedHandsSystem - { - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent((_, component, _) => component.SettupGui()); - SubscribeLocalEvent((_, component, _) => component.ClearGui()); - - CommandBinds.Builder - .Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed)) - .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) - .Register(); - } - - public override void Shutdown() - { - CommandBinds.Unregister(); - base.Shutdown(); - } - - private void SwapHandsPressed(ICommonSession? session) - { - if (session == null) - return; - - var player = session.AttachedEntity; - - if (player == null) - return; - - if (!player.TryGetComponent(out SharedHandsComponent? hands)) - return; - - if (!hands.TryGetSwapHandsResult(out var nextHand)) - return; - - EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(nextHand)); - } - - private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid) - { - if (session == null) - return false; - - var player = session.AttachedEntity; - - if (player == null) - return false; - - if (!player.TryGetComponent(out SharedHandsComponent? hands)) - return false; - - var activeHand = hands.ActiveHand; - - if (activeHand == null) - return false; - - EntityManager.RaisePredictiveEvent(new RequestDropHeldEntityEvent(activeHand, coords)); - return true; - } - - protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) - { - component.HandsModified(); - } - } -} diff --git a/Content.Client/Hands/Systems/HandVirtualPullSystem.cs b/Content.Client/Hands/Systems/HandVirtualPullSystem.cs new file mode 100644 index 0000000000..0f47efd9dc --- /dev/null +++ b/Content.Client/Hands/Systems/HandVirtualPullSystem.cs @@ -0,0 +1,18 @@ +using Content.Client.Items; +using Content.Shared.Hands.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Client.Hands +{ + [UsedImplicitly] + public sealed class HandVirtualPullSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + Subs.ItemStatus(_ => new HandVirtualPullItemStatus()); + } + } +} diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs new file mode 100644 index 0000000000..5f2e40f2e5 --- /dev/null +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +using Content.Client.Animations; +using Content.Client.HUD; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.Player; +using Robust.Shared.Containers; +using Robust.Shared.GameObjects; +using Robust.Shared.Input.Binding; +using Robust.Shared.IoC; +using Robust.Shared.Timing; + +namespace Content.Client.Hands +{ + [UsedImplicitly] + public sealed class HandsSystem : SharedHandsSystem + { + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly IGameHud _gameHud = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + + public event Action? GuiStateUpdated; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandlePlayerAttached); + SubscribeLocalEvent(HandlePlayerDetached); + SubscribeLocalEvent(HandleCompRemove); + SubscribeLocalEvent(HandleHandsModified); + + SubscribeNetworkEvent(HandlePickupAnimation); + } + + public override void Shutdown() + { + CommandBinds.Unregister(); + base.Shutdown(); + } + + private void HandleHandsModified(HandsModifiedMessage ev) + { + if (ev.Hands.Owner == _playerManager.LocalPlayer?.ControlledEntity) + GuiStateUpdated?.Invoke(); + } + + protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) + { + if (uid == _playerManager.LocalPlayer?.ControlledEntity?.Uid) + GuiStateUpdated?.Invoke(); + } + + private void HandlePickupAnimation(PickupAnimationMessage msg) + { + if (!EntityManager.TryGetEntity(msg.EntityUid, out var entity)) + return; + + if (!_gameTiming.IsFirstTimePredicted) + return; + + ReusableAnimations.AnimateEntityPickup(entity, msg.InitialPosition, msg.PickupDirection); + } + + public HandsGuiState GetGuiState() + { + var player = _playerManager.LocalPlayer?.ControlledEntity; + + if (player == null || !player.TryGetComponent(out HandsComponent? hands)) + return new HandsGuiState(Array.Empty()); + + var states = hands.Hands + .Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity)) + .ToArray(); + + return new HandsGuiState(states, hands.ActiveHand); + } + + public void UIHandClick(HandsComponent hands, string handName) + { + if (!hands.TryGetHand(handName, out var pressedHand)) + return; + + if (!hands.TryGetActiveHand(out var activeHand)) + return; + + var pressedEntity = pressedHand.HeldEntity; + var activeEntity = activeHand.HeldEntity; + + if (pressedHand == activeHand && activeEntity != null) + { + // use item in hand + // it will always be attack_self() in my heart. + RaiseNetworkEvent(new UseInHandMsg()); + return; + } + + if (pressedHand != activeHand && pressedEntity == null) + { + // change active hand + RaiseNetworkEvent(new RequestSetHandEvent(handName)); + return; + } + + if (pressedHand != activeHand && pressedEntity != null && activeEntity != null) + { + // use active item on held item + RaiseNetworkEvent(new ClientInteractUsingInHandMsg(pressedHand.Name)); + return; + } + + if (pressedHand != activeHand && pressedEntity != null && activeEntity == null) + { + // use active item on held item + RaiseNetworkEvent(new MoveItemFromHandMsg(pressedHand.Name)); + } + } + + public void UIHandActivate(string handName) + { + RaiseNetworkEvent (new ActivateInHandMsg(handName)); + } + + private void HandlePlayerAttached(EntityUid uid, HandsComponent component, PlayerAttachedEvent args) + { + component.Gui = new HandsGui(component, this); + _gameHud.HandsContainer.AddChild(component.Gui); + } + + private static void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args) + { + ClearGui(component); + } + + private static void HandleCompRemove(EntityUid uid, HandsComponent component, ComponentRemove args) + { + ClearGui(component); + } + + private static void ClearGui(HandsComponent comp) + { + comp.Gui?.Orphan(); + comp.Gui = null; + } + } +} diff --git a/Content.Client/Items/ItemStatusMessages.cs b/Content.Client/Items/ItemStatusMessages.cs new file mode 100644 index 0000000000..93ebaeabe0 --- /dev/null +++ b/Content.Client/Items/ItemStatusMessages.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Robust.Client.UserInterface; +using Robust.Shared.GameObjects; + +namespace Content.Client.Items +{ + public sealed class ItemStatusCollectMessage : EntityEventArgs + { + public List Controls = new(); + } + + public static class ItemStatusRegisterExt + { + /// + /// Register an item status control for a component. + /// + /// The handle from within entity system initialize. + /// A delegate to create the actual control. + /// The type of component for which this control should be made. + public static void ItemStatus( + this EntitySystem.Subscriptions subs, + Func createControl) + where TComp : IComponent + { + subs.SubscribeLocalEvent((uid, _, args) => + { + args.Controls.Add(createControl(uid)); + }); + } + } +} diff --git a/Content.Client/Items/Managers/IItemSlotManager.cs b/Content.Client/Items/Managers/IItemSlotManager.cs index 127eedc1b4..bbebaf55b4 100644 --- a/Content.Client/Items/Managers/IItemSlotManager.cs +++ b/Content.Client/Items/Managers/IItemSlotManager.cs @@ -1,4 +1,5 @@ -using Content.Client.Items.UI; +using System; +using Content.Client.Items.UI; using Robust.Client.UserInterface; using Robust.Shared.GameObjects; @@ -10,5 +11,21 @@ namespace Content.Client.Items.Managers void UpdateCooldown(ItemSlotButton? cooldownTexture, IEntity? entity); bool SetItemSlot(ItemSlotButton button, IEntity? entity); void HoverInSlot(ItemSlotButton button, IEntity? entity, bool fits); + event Action? EntityHighlightedUpdated; + bool IsHighlighted(EntityUid uid); + + /// + /// Highlight all slot controls that contain the specified entity. + /// + /// The UID of the entity to highlight. + /// + void HighlightEntity(EntityUid uid); + + /// + /// Remove highlighting for the specified entity. + /// + /// The UID of the entity to unhighlight. + /// + void UnHighlightEntity(EntityUid uid); } } diff --git a/Content.Client/Items/Managers/ItemSlotManager.cs b/Content.Client/Items/Managers/ItemSlotManager.cs index 5fd3f0fbe0..3435913914 100644 --- a/Content.Client/Items/Managers/ItemSlotManager.cs +++ b/Content.Client/Items/Managers/ItemSlotManager.cs @@ -1,8 +1,11 @@ +using System; +using System.Collections.Generic; using Content.Client.Examine; using Content.Client.Items.UI; using Content.Client.Storage; using Content.Client.Verbs; using Content.Shared.Cooldown; +using Content.Shared.Hands.Components; using Content.Shared.Input; using Robust.Client.GameObjects; using Robust.Client.Graphics; @@ -28,6 +31,11 @@ namespace Content.Client.Items.Managers [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IComponentManager _componentManager = default!; + + private readonly HashSet _highlightEntities = new(); + + public event Action? EntityHighlightedUpdated; public bool SetItemSlot(ItemSlotButton button, IEntity? entity) { @@ -38,13 +46,26 @@ namespace Content.Client.Items.Managers } else { - if (!entity.TryGetComponent(out ISpriteComponent? sprite)) + ISpriteComponent? sprite; + if (entity.TryGetComponent(out HandVirtualPullComponent? virtPull) + && _componentManager.TryGetComponent(virtPull.PulledEntity, out ISpriteComponent pulledSprite)) + { + sprite = pulledSprite; + } + else if (!entity.TryGetComponent(out sprite)) + { return false; + } button.ClearHover(); button.SpriteView.Sprite = sprite; button.StorageButton.Visible = entity.HasComponent(); } + + button.Entity = entity?.Uid ?? default; + + // im lazy + button.UpdateSlotHighlighted(); return true; } @@ -145,5 +166,38 @@ namespace Content.Client.Items.Managers button.HoverSpriteView.Sprite = hoverSprite; } + + public bool IsHighlighted(EntityUid uid) + { + return _highlightEntities.Contains(uid); + } + + public void HighlightEntity(EntityUid uid) + { + if (!_highlightEntities.Add(uid)) + return; + + EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, true)); + } + + public void UnHighlightEntity(EntityUid uid) + { + if (!_highlightEntities.Remove(uid)) + return; + + EntityHighlightedUpdated?.Invoke(new EntitySlotHighlightedEventArgs(uid, false)); + } + } + + public readonly struct EntitySlotHighlightedEventArgs + { + public EntitySlotHighlightedEventArgs(EntityUid entity, bool newHighlighted) + { + Entity = entity; + NewHighlighted = newHighlighted; + } + + public EntityUid Entity { get; } + public bool NewHighlighted { get; } } } diff --git a/Content.Client/Items/UI/ItemSlotButton.cs b/Content.Client/Items/UI/ItemSlotButton.cs index 8ffbdc5cf8..201fa28f5c 100644 --- a/Content.Client/Items/UI/ItemSlotButton.cs +++ b/Content.Client/Items/UI/ItemSlotButton.cs @@ -1,18 +1,24 @@ using System; using Content.Client.Cooldown; +using Content.Client.Items.Managers; using Content.Client.Stylesheets; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Shared.GameObjects; using Robust.Shared.Input; +using Robust.Shared.IoC; using Robust.Shared.Maths; namespace Content.Client.Items.UI { - public class ItemSlotButton : Control + public class ItemSlotButton : Control, IEntityEventSubscriber { private const string HighlightShader = "SelectionOutlineInrange"; + [Dependency] private readonly IItemSlotManager _itemSlotManager = default!; + + public EntityUid Entity { get; set; } public TextureRect Button { get; } public SpriteView SpriteView { get; } public SpriteView HoverSpriteView { get; } @@ -32,6 +38,8 @@ namespace Content.Client.Items.UI public ItemSlotButton(Texture texture, Texture storageTexture, string textureName) { + IoCManager.InjectDependencies(this); + MinSize = (64, 64); TextureName = textureName; @@ -101,6 +109,31 @@ namespace Content.Client.Items.UI }); } + protected override void EnteredTree() + { + base.EnteredTree(); + + _itemSlotManager.EntityHighlightedUpdated += HandleEntitySlotHighlighted; + UpdateSlotHighlighted(); + } + + protected override void ExitedTree() + { + base.ExitedTree(); + + _itemSlotManager.EntityHighlightedUpdated -= HandleEntitySlotHighlighted; + } + + private void HandleEntitySlotHighlighted(EntitySlotHighlightedEventArgs entitySlotHighlightedEventArgs) + { + UpdateSlotHighlighted(); + } + + public void UpdateSlotHighlighted() + { + Highlight(_itemSlotManager.IsHighlighted(Entity)); + } + public void ClearHover() { if (EntityHover) diff --git a/Content.Client/Items/UI/ItemStatusPanel.cs b/Content.Client/Items/UI/ItemStatusPanel.cs index d880d7ea49..d1dbd714c6 100644 --- a/Content.Client/Items/UI/ItemStatusPanel.cs +++ b/Content.Client/Items/UI/ItemStatusPanel.cs @@ -8,7 +8,9 @@ using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Timing; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; using static Content.Client.IoC.StaticIoC; @@ -18,6 +20,8 @@ namespace Content.Client.Items.UI { public class ItemStatusPanel : Control { + [Dependency] private readonly IEntityManager _entityManager = default!; + [ViewVariables] private readonly List<(IItemStatus, Control)> _activeStatusComponents = new(); @@ -33,6 +37,8 @@ namespace Content.Client.Items.UI public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign) { + IoCManager.InjectDependencies(this); + var panel = new StyleBoxTexture { Texture = texture @@ -117,6 +123,13 @@ namespace Content.Client.Items.UI return new ItemStatusPanel(ResC.GetTexture(texture), cutOut, flat, textAlign); } + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + UpdateItemName(); + } + public void Update(IEntity? entity) { if (entity == null) @@ -131,12 +144,29 @@ namespace Content.Client.Items.UI { _entity = entity; BuildNewEntityStatus(); - _itemNameLabel.Text = entity.Name; + + UpdateItemName(); } _panel.Visible = true; } + private void UpdateItemName() + { + if (_entity == null) + return; + + if (_entity.TryGetComponent(out HandVirtualPullComponent? virtualPull) + && _entityManager.TryGetEntity(virtualPull.PulledEntity, out var pulledEnt)) + { + _itemNameLabel.Text = pulledEnt.Name; + } + else + { + _itemNameLabel.Text = _entity.Name; + } + } + private void ClearOldStatus() { _statusContents.RemoveAllChildren(); @@ -162,6 +192,14 @@ namespace Content.Client.Items.UI _activeStatusComponents.Add((statusComponent, control)); } + + var collectMsg = new ItemStatusCollectMessage(); + _entity.EntityManager.EventBus.RaiseLocalEvent(_entity.Uid, collectMsg); + + foreach (var control in collectMsg.Controls) + { + _statusContents.AddChild(control); + } } } } diff --git a/Content.Server/Fluids/Components/SpillableComponent.cs b/Content.Server/Fluids/Components/SpillableComponent.cs index 79e1f267a7..bd5dca6d3d 100644 --- a/Content.Server/Fluids/Components/SpillableComponent.cs +++ b/Content.Server/Fluids/Components/SpillableComponent.cs @@ -2,6 +2,7 @@ using Content.Shared.ActionBlocker; using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Solution.Components; using Content.Shared.DragDrop; +using Content.Shared.Interaction; using Content.Shared.Interaction.Events; using Content.Shared.Notification.Managers; using Content.Shared.Verbs; diff --git a/Content.Server/Hands/Components/HandsComponent.cs b/Content.Server/Hands/Components/HandsComponent.cs index c3f65060b5..f8b84c9992 100644 --- a/Content.Server/Hands/Components/HandsComponent.cs +++ b/Content.Server/Hands/Components/HandsComponent.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -10,7 +11,6 @@ using Content.Shared.Audio; using Content.Shared.Body.Part; using Content.Shared.Hands.Components; using Content.Shared.Notification.Managers; -using Content.Shared.Physics.Pull; using Content.Shared.Pulling.Components; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -19,9 +19,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; -using Robust.Shared.Network; using Robust.Shared.Player; -using Robust.Shared.Players; namespace Content.Server.Hands.Components { @@ -35,48 +33,6 @@ namespace Content.Server.Hands.Components int IDisarmedAct.Priority => int.MaxValue; // We want this to be the last disarm act to run. - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - switch (message) - { - case PullAttemptMessage msg: - AttemptPull(msg); - break; - case PullStartedMessage: - StartPulling(); - break; - case PullStoppedMessage: - StopPulling(); - break; - } - } - - public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) - { - base.HandleNetworkMessage(message, channel, session); - - switch (message) - { - case ClientChangedHandMsg msg: - ActiveHand = msg.HandName; - break; - case ClientAttackByInHandMsg msg: - InteractHandWithActiveHand(msg.HandName); - break; - case UseInHandMsg: - UseActiveHeldEntity(); - break; - case ActivateInHandMsg msg: - ActivateHeldEntity(msg.HandName); - break; - case MoveItemFromHandMsg msg: - TryMoveHeldEntityToActiveHand(msg.HandName); - break; - } - } - protected override void OnHeldEntityRemovedFromHand(IEntity heldEntity, HandState handState) { if (heldEntity.TryGetComponent(out ItemComponent? item)) @@ -141,7 +97,8 @@ namespace Content.Server.Hands.Components if (pickupDirection == initialPosition.ToMapPos(Owner.EntityManager)) return; - SendNetworkMessage(new PickupAnimationMessage(entity.Uid, pickupDirection, initialPosition)); + Owner.EntityManager.EntityNetManager!.SendSystemNetworkMessage( + new PickupAnimationMessage(entity.Uid, pickupDirection, initialPosition)); } #region Pull/Disarm @@ -151,9 +108,17 @@ namespace Content.Server.Hands.Components if (args.Part.PartType != BodyPartType.Hand) return; - var handLocation = ReadOnlyHands.Count == 0 ? HandLocation.Right : HandLocation.Left; //TODO: make hand body part have a handlocation? + // If this annoys you, which it should. + // Ping Smugleaf. + var location = args.Part.Symmetry switch + { + BodyPartSymmetry.None => HandLocation.Middle, + BodyPartSymmetry.Left => HandLocation.Left, + BodyPartSymmetry.Right => HandLocation.Right, + _ => throw new ArgumentOutOfRangeException() + }; - AddHand(args.Slot, handLocation); + AddHand(args.Slot, location); } void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs args) @@ -205,41 +170,13 @@ namespace Content.Server.Hands.Components return pullable.TryStopPull(); } - private void AttemptPull(PullAttemptMessage msg) - { - if (!ReadOnlyHands.Any(hand => hand.Enabled)) - { - msg.Cancelled = true; - } - } - - private void StartPulling() - { - var firstFreeHand = Hands.FirstOrDefault(hand => hand.Enabled); - - if (firstFreeHand == null) - return; - - DisableHand(firstFreeHand); - } - - private void StopPulling() - { - var firstOccupiedHand = Hands.FirstOrDefault(hand => !hand.Enabled); - - if (firstOccupiedHand == null) - return; - - EnableHand(firstOccupiedHand); - } - #endregion #region Old public methods - public IEnumerable HandNames => ReadOnlyHands.Select(h => h.Name); + public IEnumerable HandNames => Hands.Select(h => h.Name); - public int Count => ReadOnlyHands.Count; + public int Count => Hands.Count; /// /// Returns a list of all hand names, with the active hand being first. @@ -249,9 +186,9 @@ namespace Content.Server.Hands.Components if (ActiveHand != null) yield return ActiveHand; - foreach (var hand in ReadOnlyHands) + foreach (var hand in Hands) { - if (hand.Name == ActiveHand || !hand.Enabled) + if (hand.Name == ActiveHand) continue; yield return hand.Name; diff --git a/Content.Server/Hands/Systems/HandVirtualPullSystem.cs b/Content.Server/Hands/Systems/HandVirtualPullSystem.cs new file mode 100644 index 0000000000..f95a4747ca --- /dev/null +++ b/Content.Server/Hands/Systems/HandVirtualPullSystem.cs @@ -0,0 +1,57 @@ +using Content.Server.Pulling; +using Content.Shared.Hands; +using Content.Shared.Hands.Components; +using Content.Shared.Interaction; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Server.Hands +{ + [UsedImplicitly] + public sealed class HandVirtualPullSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(HandlePullerDropped); + SubscribeLocalEvent(HandlePullerUnequipped); + + SubscribeLocalEvent(HandleBeforeInteract); + } + + private static void HandleBeforeInteract( + EntityUid uid, + HandVirtualPullComponent component, + BeforeInteractEvent args) + { + // No interactions with a virtual pull, please. + args.Handled = true; + } + + // If the virtual pull gets removed from the hands for any reason, cancel the pull and delete it. + private void HandlePullerUnequipped(EntityUid uid, HandVirtualPullComponent component, UnequippedHandEvent args) + { + MaybeDelete(component, args.User); + } + + private void HandlePullerDropped(EntityUid uid, HandVirtualPullComponent component, DroppedEvent args) + { + MaybeDelete(component, args.User); + } + + private void MaybeDelete(HandVirtualPullComponent comp, IEntity? user) + { + var pulled = comp.PulledEntity; + + if (!ComponentManager.TryGetComponent(pulled, out PullableComponent? pullable)) + return; + + if (pullable.Puller != user) + return; + + pullable.TryStopPull(user); + comp.Owner.QueueDelete(); + } + } +} diff --git a/Content.Server/Hands/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs similarity index 58% rename from Content.Server/Hands/HandsSystem.cs rename to Content.Server/Hands/Systems/HandsSystem.cs index 4a76d07101..7e6adca66c 100644 --- a/Content.Server/Hands/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -13,9 +13,9 @@ using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Input; using Content.Shared.Notification.Managers; +using Content.Shared.Physics.Pull; using JetBrains.Annotations; using Robust.Server.Player; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; @@ -23,6 +23,7 @@ using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Players; +using Robust.Shared.Utility; using static Content.Shared.Inventory.EquipmentSlotDefines; namespace Content.Server.Hands @@ -38,15 +39,126 @@ namespace Content.Server.Hands base.Initialize(); SubscribeLocalEvent(HandleExamined); + SubscribeNetworkEvent(HandleActivateInHand); + SubscribeNetworkEvent(HandleInteractUsingInHand); + SubscribeNetworkEvent(HandleUseInHand); + SubscribeNetworkEvent(HandleMoveItemFromHand); + + SubscribeLocalEvent(HandlePullAttempt); + SubscribeLocalEvent(HandlePullStarted); + SubscribeLocalEvent(HandlePullStopped); CommandBinds.Builder .Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem)) .Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem)) .Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack)) .Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt)) + .Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false)) + .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) .Register(); } + private static void HandlePullAttempt(EntityUid uid, HandsComponent component, PullAttemptMessage args) + { + // Cancel pull if all hands full. + if (component.Hands.All(hand => !hand.IsEmpty)) + args.Cancelled = true; + } + + private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) + { + foreach (var handName in component.ActivePriorityEnumerable()) + { + var hand = component.GetHand(handName); + if (!hand.IsEmpty) + continue; + + var pos = component.Owner.Transform.Coordinates; + var virtualPull = EntityManager.SpawnEntity("HandVirtualPull", pos); + var virtualPullComp = virtualPull.GetComponent(); + virtualPullComp.PulledEntity = args.Pulled.Owner.Uid; + component.PutEntityIntoHand(hand, virtualPull); + return; + } + + DebugTools.Assert("Unable to find available hand when starting pulling??"); + } + + private void HandlePullStopped(EntityUid uid, HandsComponent component, PullStoppedMessage args) + { + // Try find hand that is doing this pull. + // and clear it. + foreach (var hand in component.Hands) + { + if (hand.HeldEntity == null + || !hand.HeldEntity.TryGetComponent(out HandVirtualPullComponent? virtualPull) + || virtualPull.PulledEntity != args.Pulled.Owner.Uid) + continue; + + hand.HeldEntity.Delete(); + break; + } + } + + private void SwapHandsPressed(ICommonSession? session) + { + var player = session?.AttachedEntity; + + if (player == null) + return; + + if (!player.TryGetComponent(out SharedHandsComponent? hands)) + return; + + if (!hands.TryGetSwapHandsResult(out var nextHand)) + return; + + hands.ActiveHand = nextHand; + } + + private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + var player = session?.AttachedEntity; + + if (player == null) + return false; + + if (!player.TryGetComponent(out SharedHandsComponent? hands)) + return false; + + var activeHand = hands.ActiveHand; + + if (activeHand == null) + return false; + + hands.TryDropHand(activeHand, coords); + return false; + } + + private void HandleMoveItemFromHand(MoveItemFromHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.TryMoveHeldEntityToActiveHand(msg.HandName); + } + + private void HandleUseInHand(UseInHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.UseActiveHeldEntity(); + } + + private void HandleInteractUsingInHand(ClientInteractUsingInHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.InteractHandWithActiveHand(msg.HandName); + } + public override void Shutdown() { base.Shutdown(); @@ -54,6 +166,14 @@ namespace Content.Server.Hands CommandBinds.Unregister(); } + private void HandleActivateInHand(ActivateInHandMsg msg, EntitySessionEventArgs args) + { + if (!TryGetHandsComp(args.SenderSession, out var hands)) + return; + + hands.ActivateHeldEntity(msg.HandName); + } + protected override void DropAllItemsInHands(IEntity entity, bool doMobChecks = true) { base.DropAllItemsInHands(entity, doMobChecks); @@ -75,25 +195,21 @@ namespace Content.Server.Hands } } - protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) - { - component.Dirty(); - } - - private bool TryGetHandsComp(ICommonSession? session, [NotNullWhen(true)] out SharedHandsComponent? hands) + private static bool TryGetHandsComp( + ICommonSession? session, + [NotNullWhen(true)] out SharedHandsComponent? hands) { hands = default; if (session is not IPlayerSession playerSession) return false; - var playerEnt = playerSession?.AttachedEntity; + var playerEnt = playerSession.AttachedEntity; if (playerEnt == null || !playerEnt.IsValid()) return false; - playerEnt.TryGetComponent(out hands); - return hands != null; + return playerEnt.TryGetComponent(out hands); } private void HandleActivateItem(ICommonSession? session) diff --git a/Content.Server/Interaction/InteractionSystem.cs b/Content.Server/Interaction/InteractionSystem.cs index 61a5ce724e..6908b146b5 100644 --- a/Content.Server/Interaction/InteractionSystem.cs +++ b/Content.Server/Interaction/InteractionSystem.cs @@ -387,6 +387,18 @@ namespace Content.Server.Interaction return false; } + private async Task InteractDoBefore( + IEntity user, + IEntity used, + IEntity? target, + EntityCoordinates clickLocation, + bool canReach) + { + var ev = new BeforeInteractEvent(user, used, target, clickLocation, canReach); + RaiseLocalEvent(used.Uid, ev, false); + return ev.Handled; + } + /// /// Uses a item/object on an entity /// Finds components with the InteractUsing interface and calls their function @@ -397,6 +409,9 @@ namespace Content.Server.Interaction if (!_actionBlockerSystem.CanInteract(user)) return; + if (await InteractDoBefore(user, used, target, clickLocation, true)) + return; + // all interactions should only happen when in range / unobstructed, so no range check is needed var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation); RaiseLocalEvent(target.Uid, interactUsingEvent); @@ -696,6 +711,9 @@ namespace Content.Server.Interaction /// public async Task InteractUsingRanged(IEntity user, IEntity used, IEntity? target, EntityCoordinates clickLocation, bool inRangeUnobstructed) { + if (await InteractDoBefore(user, used, inRangeUnobstructed ? target : null, clickLocation, false)) + return true; + if (target != null) { var rangedMsg = new RangedInteractEvent(user, used, target, clickLocation); @@ -715,10 +733,7 @@ namespace Content.Server.Interaction } } - if (inRangeUnobstructed) - return await InteractDoAfter(user, used, target, clickLocation, false); - else - return await InteractDoAfter(user, used, null, clickLocation, false); + return await InteractDoAfter(user, used, inRangeUnobstructed ? target : null, clickLocation, false); } public void DoAttack(IEntity user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default) diff --git a/Content.Shared/Hands/Components/HandVirtualPullComponent.cs b/Content.Shared/Hands/Components/HandVirtualPullComponent.cs new file mode 100644 index 0000000000..42f6bd85f3 --- /dev/null +++ b/Content.Shared/Hands/Components/HandVirtualPullComponent.cs @@ -0,0 +1,50 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Players; +using Robust.Shared.Serialization; + +namespace Content.Shared.Hands.Components +{ + [RegisterComponent] + [NetworkedComponent] + public sealed class HandVirtualPullComponent : Component + { + private EntityUid _pulledEntity; + public override string Name => "HandVirtualPull"; + + public EntityUid PulledEntity + { + get => _pulledEntity; + set + { + _pulledEntity = value; + Dirty(); + } + } + + public override ComponentState GetComponentState(ICommonSession player) + { + return new VirtualPullComponentState(_pulledEntity); + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + if (curState is not VirtualPullComponentState pullState) + return; + + _pulledEntity = pullState.PulledEntity; + } + + [Serializable, NetSerializable] + public sealed class VirtualPullComponentState : ComponentState + { + public readonly EntityUid PulledEntity; + + public VirtualPullComponentState(EntityUid pulledEntity) + { + PulledEntity = pulledEntity; + } + } + } +} diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index 15a2ca3d69..9e1978b06f 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -55,11 +55,11 @@ namespace Content.Shared.Hands.Components } } } + private string? _activeHand; [ViewVariables] - public IReadOnlyList ReadOnlyHands => Hands; - protected readonly List Hands = new(); + public readonly List Hands = new(); /// /// The amount of throw impulse per distance the player is from the throw target. @@ -89,7 +89,10 @@ namespace Content.Shared.Hands.Components public virtual void HandsModified() { + // todo axe all this for ECS. Dirty(); + + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this }); } public void AddHand(string handName, HandLocation handLocation) @@ -100,7 +103,7 @@ namespace Content.Shared.Hands.Components var container = ContainerHelpers.CreateContainer(Owner, handName); container.OccludesLight = false; - Hands.Add(new Hand(handName, true, handLocation, container)); + Hands.Add(new Hand(handName, handLocation, container)); if (ActiveHand == null) ActiveHand = handName; @@ -125,48 +128,55 @@ namespace Content.Shared.Hands.Components Hands.Remove(hand); if (ActiveHand == hand.Name) - ActiveHand = ReadOnlyHands.FirstOrDefault()?.Name; + ActiveHand = Hands.FirstOrDefault()?.Name; HandCountChanged(); HandsModified(); } - public bool HasHand(string handName) - { - foreach (var hand in Hands) - { - if (hand.Name == handName) - return true; - } - return false; - } - - private Hand? GetHand(string handName) - { - foreach (var hand in Hands) - { - if (hand.Name == handName) - return hand; - } - return null; - } - private Hand? GetActiveHand() { if (ActiveHand == null) return null; - return GetHand(ActiveHand); + return GetHandOrNull(ActiveHand); } - protected bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand) + public bool HasHand(string handName) { - foundHand = GetHand(handName); - return foundHand != null; + return TryGetHand(handName, out _); } - protected bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand) + public Hand? GetHandOrNull(string handName) + { + return TryGetHand(handName, out var hand) ? hand : null; + } + + public Hand GetHand(string handName) + { + if (!TryGetHand(handName, out var hand)) + throw new KeyNotFoundException($"Unable to find hand with name {handName}"); + + return hand; + } + + public bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand) + { + foreach (var hand in Hands) + { + if (hand.Name == handName) + { + foundHand = hand; + return true; + }; + } + + foundHand = null; + return false; + } + + public bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand) { activeHand = GetActiveHand(); return activeHand != null; @@ -211,7 +221,7 @@ namespace Content.Shared.Hands.Components public IEnumerable GetAllHeldEntities() { - foreach (var hand in ReadOnlyHands) + foreach (var hand in Hands) { if (hand.HeldEntity != null) yield return hand.HeldEntity; @@ -416,7 +426,7 @@ namespace Content.Shared.Hands.Components /// /// Drops a hands contents to the target location. /// - private void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true) + public void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true) { var heldEntity = hand.HeldEntity; @@ -538,16 +548,7 @@ namespace Content.Shared.Hands.Components public bool CanPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true) { - if (!TryGetActiveHand(out var hand)) - return false; - - if (checkActionBlocker && !PlayerCanPickup()) - return false; - - if (!CanInsertEntityIntoHand(hand, entity)) - return false; - - return true; + return ActiveHand != null && CanPickupEntity(ActiveHand, entity, checkActionBlocker); } /// @@ -563,10 +564,7 @@ namespace Content.Shared.Hands.Components public bool TryPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true) { - if (!TryGetActiveHand(out var hand)) - return false; - - return TryPickupEntity(hand, entity, checkActionBlocker); + return ActiveHand != null && TryPickupEntity(ActiveHand, entity, checkActionBlocker); } /// @@ -574,9 +572,6 @@ namespace Content.Shared.Hands.Components /// protected bool CanInsertEntityIntoHand(Hand hand, IEntity entity) { - if (!hand.Enabled) - return false; - var handContainer = hand.Container; if (handContainer == null) return false; @@ -602,7 +597,7 @@ namespace Content.Shared.Hands.Components /// /// Puts an entity into the player's hand, assumes that the insertion is allowed. /// - private void PutEntityIntoHand(Hand hand, IEntity entity) + public void PutEntityIntoHand(Hand hand, IEntity entity) { var handContainer = hand.Container; if (handContainer == null) @@ -658,7 +653,7 @@ namespace Content.Shared.Hands.Components if (newActiveIndex > finalHandIndex) newActiveIndex = 0; - nextHand = ReadOnlyHands[newActiveIndex].Name; + nextHand = Hands[newActiveIndex].Name; return true; } @@ -752,7 +747,7 @@ namespace Content.Shared.Hands.Components Hand? priorityHand = null; if (priorityHandName != null) - priorityHand = GetHand(priorityHandName); + priorityHand = GetHandOrNull(priorityHandName); return TryPutInAnyHand(entity, priorityHand, checkActionBlocker); } @@ -793,43 +788,16 @@ namespace Content.Shared.Hands.Components protected virtual void DoActivate(IEntity heldEntity) { } protected virtual void HandlePickupAnimation(IEntity entity) { } - - protected void EnableHand(Hand hand) - { - hand.Enabled = true; - Dirty(); - } - - protected void DisableHand(Hand hand) - { - hand.Enabled = false; - DropHeldEntityToFloor(hand, intentionalDrop: false); - Dirty(); - } } - public interface IReadOnlyHand + public class Hand { + [ViewVariables] public string Name { get; } - public bool Enabled { get; } - + [ViewVariables] public HandLocation Location { get; } - public abstract IEntity? HeldEntity { get; } - } - - public class Hand : IReadOnlyHand - { - [ViewVariables] - public string Name { get; set; } - - [ViewVariables] - public bool Enabled { get; set; } - - [ViewVariables] - public HandLocation Location { get; set; } - /// /// The container used to hold the contents of this hand. Nullable because the client must get the containers via , /// which may not be synced with the server when the client hands are created. @@ -840,37 +808,36 @@ namespace Content.Shared.Hands.Components [ViewVariables] public IEntity? HeldEntity => Container?.ContainedEntities?.FirstOrDefault(); - public Hand(string name, bool enabled, HandLocation location, IContainer? container = null) + public bool IsEmpty => HeldEntity == null; + + public Hand(string name, HandLocation location, IContainer? container = null) { Name = name; - Enabled = enabled; Location = location; Container = container; } public HandState ToHandState() { - return new(Name, Location, Enabled); + return new(Name, Location); } } [Serializable, NetSerializable] - public sealed class HandState + public struct HandState { public string Name { get; } public HandLocation Location { get; } - public bool Enabled { get; } - public HandState(string name, HandLocation location, bool enabled) + public HandState(string name, HandLocation location) { Name = name; Location = location; - Enabled = enabled; } } [Serializable, NetSerializable] - public class HandsComponentState : ComponentState + public sealed class HandsComponentState : ComponentState { public HandState[] Hands { get; } public string? ActiveHand { get; } @@ -886,25 +853,20 @@ namespace Content.Shared.Hands.Components /// A message that calls the use interaction on an item in hand, presumed for now the interaction will occur only on the active hand. /// [Serializable, NetSerializable] - public class UseInHandMsg : ComponentMessage + public sealed class UseInHandMsg : EntityEventArgs { - public UseInHandMsg() - { - Directed = true; - } } /// /// A message that calls the activate interaction on the item in the specified hand. /// [Serializable, NetSerializable] - public class ActivateInHandMsg : ComponentMessage + public class ActivateInHandMsg : EntityEventArgs { public string HandName { get; } public ActivateInHandMsg(string handName) { - Directed = true; HandName = handName; } } @@ -913,13 +875,12 @@ namespace Content.Shared.Hands.Components /// Uses the item in the active hand on the item in the specified hand. /// [Serializable, NetSerializable] - public class ClientAttackByInHandMsg : ComponentMessage + public class ClientInteractUsingInHandMsg : EntityEventArgs { public string HandName { get; } - public ClientAttackByInHandMsg(string handName) + public ClientInteractUsingInHandMsg(string handName) { - Directed = true; HandName = handName; } } @@ -928,28 +889,12 @@ namespace Content.Shared.Hands.Components /// Moves an item from one hand to the active hand. /// [Serializable, NetSerializable] - public class MoveItemFromHandMsg : ComponentMessage + public class MoveItemFromHandMsg : EntityEventArgs { public string HandName { get; } public MoveItemFromHandMsg(string handName) { - Directed = true; - HandName = handName; - } - } - - /// - /// Sets the player's active hand to a specified hand. - /// - [Serializable, NetSerializable] - public class ClientChangedHandMsg : ComponentMessage - { - public string HandName { get; } - - public ClientChangedHandMsg(string handName) - { - Directed = true; HandName = handName; } } @@ -975,7 +920,7 @@ namespace Content.Shared.Hands.Components } [Serializable, NetSerializable] - public class PickupAnimationMessage : ComponentMessage + public class PickupAnimationMessage : EntityEventArgs { public EntityUid EntityUid { get; } public EntityCoordinates InitialPosition { get; } @@ -983,10 +928,15 @@ namespace Content.Shared.Hands.Components public PickupAnimationMessage(EntityUid entityUid, Vector2 pickupDirection, EntityCoordinates initialPosition) { - Directed = true; EntityUid = entityUid; PickupDirection = pickupDirection; InitialPosition = initialPosition; } } + + [Serializable, NetSerializable] + public struct HandsModifiedMessage + { + public SharedHandsComponent Hands; + } } diff --git a/Content.Shared/Hands/SharedHandsSystem.cs b/Content.Shared/Hands/SharedHandsSystem.cs index 9a9bdec3d8..96bd96d793 100644 --- a/Content.Shared/Hands/SharedHandsSystem.cs +++ b/Content.Shared/Hands/SharedHandsSystem.cs @@ -1,7 +1,6 @@ using Content.Shared.Hands.Components; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Serialization; using System; @@ -16,11 +15,7 @@ namespace Content.Shared.Hands SubscribeLocalEvent(HandleContainerModified); SubscribeLocalEvent(HandleContainerModified); - SubscribeLocalEvent(HandleSetHand); - SubscribeNetworkEvent(HandleSetHand); - - SubscribeLocalEvent(HandleDrop); - SubscribeNetworkEvent(HandleDrop); + SubscribeAllEvent(HandleSetHand); } public void DropHandItems(IEntity entity, bool doMobChecks = true) @@ -38,14 +33,16 @@ namespace Content.Shared.Hands eventBus.RaiseLocalEvent(uid, msg); - if (msg.Cancelled) return; + if (msg.Cancelled) + return; if (entity.TryGetContainerMan(out var containerManager)) { var parentMsg = new ContainedEntityDropHandItemsAttemptEvent(uid); eventBus.RaiseLocalEvent(containerManager.Owner.Uid, parentMsg); - if (parentMsg.Cancelled) return; + if (parentMsg.Cancelled) + return; } DropAllItemsInHands(entity, doMobChecks); @@ -55,9 +52,9 @@ namespace Content.Shared.Hands { } - private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) + private static void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) { - var entity = eventArgs.SenderSession?.AttachedEntity; + var entity = eventArgs.SenderSession.AttachedEntity; if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands)) return; @@ -65,17 +62,13 @@ namespace Content.Shared.Hands hands.ActiveHand = msg.HandName; } - private void HandleDrop(RequestDropHeldEntityEvent msg, EntitySessionEventArgs eventArgs) + protected virtual void HandleContainerModified( + EntityUid uid, + SharedHandsComponent component, + ContainerModifiedMessage args) { - var entity = eventArgs.SenderSession?.AttachedEntity; - - if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands)) - return; - - hands.TryDropHand(msg.HandName, msg.DropTarget); + component.Dirty(); } - - protected abstract void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args); } public sealed class ContainedEntityDropHandItemsAttemptEvent : CancellableEntityEventArgs @@ -103,21 +96,4 @@ namespace Content.Shared.Hands HandName = handName; } } - - [Serializable, NetSerializable] - public class RequestDropHeldEntityEvent : EntityEventArgs - { - /// - /// The hand to drop from. - /// - public string HandName { get; } - - public EntityCoordinates DropTarget { get; } - - public RequestDropHeldEntityEvent(string handName, EntityCoordinates dropTarget) - { - HandName = handName; - DropTarget = dropTarget; - } - } } diff --git a/Content.Shared/Interaction/BeforeInteract.cs b/Content.Shared/Interaction/BeforeInteract.cs new file mode 100644 index 0000000000..2c8bb5ca83 --- /dev/null +++ b/Content.Shared/Interaction/BeforeInteract.cs @@ -0,0 +1,53 @@ +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; + +namespace Content.Shared.Interaction +{ + /// + /// Raised directed on the used object when clicking on another object before an interaction is handled. + /// + [PublicAPI] + public class BeforeInteractEvent : HandledEntityEventArgs + { + /// + /// Entity that triggered the interaction. + /// + public IEntity User { get; } + + /// + /// Entity that the user used to interact. + /// + public IEntity Used { get; } + + /// + /// Entity that was interacted on. This can be null if the attack did not click on an entity. + /// + public IEntity? Target { get; } + + /// + /// Location that the user clicked outside of their interaction range. + /// + public EntityCoordinates ClickLocation { get; } + + /// + /// Is the click location close enough to reach by the player? This does not check for obstructions, just that the target is within + /// reach radius around the user. + /// + public bool CanReach { get; } + + public BeforeInteractEvent( + IEntity user, + IEntity used, + IEntity? target, + EntityCoordinates clickLocation, + bool canReach) + { + User = user; + Used = used; + Target = target; + ClickLocation = clickLocation; + CanReach = canReach; + } + } +} diff --git a/Content.Shared/DragDrop/IDropped.cs b/Content.Shared/Interaction/IDropped.cs similarity index 97% rename from Content.Shared/DragDrop/IDropped.cs rename to Content.Shared/Interaction/IDropped.cs index c09f7fc7df..b1daed20fc 100644 --- a/Content.Shared/DragDrop/IDropped.cs +++ b/Content.Shared/Interaction/IDropped.cs @@ -3,7 +3,7 @@ using JetBrains.Annotations; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; -namespace Content.Shared.DragDrop +namespace Content.Shared.Interaction { /// /// This interface gives components behavior when they're dropped by a mob. diff --git a/Content.Shared/Pulling/Components/SharedPullableComponent.cs b/Content.Shared/Pulling/Components/SharedPullableComponent.cs index 34890165eb..a25f3915ff 100644 --- a/Content.Shared/Pulling/Components/SharedPullableComponent.cs +++ b/Content.Shared/Pulling/Components/SharedPullableComponent.cs @@ -50,6 +50,8 @@ namespace Content.Shared.Pulling.Components return; } + var eventBus = Owner.EntityManager.EventBus; + // New value. Abandon being pulled by any existing object. if (_puller != null) { @@ -64,10 +66,9 @@ namespace Content.Shared.Pulling.Components { var message = new PullStoppedMessage(oldPullerPhysics, _physics); - oldPuller.SendMessage(null, message); - Owner.SendMessage(null, message); + eventBus.RaiseLocalEvent(oldPuller.Uid, message, broadcast: false); + eventBus.RaiseLocalEvent(Owner.Uid, message); - oldPuller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message); _physics.WakeBody(); } // else-branch warning is handled below @@ -125,14 +126,14 @@ namespace Content.Shared.Pulling.Components var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics); - value.SendMessage(null, pullAttempt); + eventBus.RaiseLocalEvent(value.Uid, pullAttempt, broadcast: false); if (pullAttempt.Cancelled) { return; } - Owner.SendMessage(null, pullAttempt); + eventBus.RaiseLocalEvent(Owner.Uid, pullAttempt); if (pullAttempt.Cancelled) { @@ -147,10 +148,8 @@ namespace Content.Shared.Pulling.Components var message = new PullStartedMessage(PullerPhysics, _physics); - _puller.SendMessage(null, message); - Owner.SendMessage(null, message); - - _puller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message); + eventBus.RaiseLocalEvent(_puller.Uid, message, broadcast: false); + eventBus.RaiseLocalEvent(Owner.Uid, message); var union = PullerPhysics.GetWorldAABB().Union(_physics.GetWorldAABB()); var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f; @@ -335,29 +334,6 @@ namespace Content.Shared.Pulling.Components Puller = entity; } - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - if (message is not PullMessage pullMessage || - pullMessage.Pulled.Owner != Owner) - { - return; - } - - var pulledStatus = Owner.GetComponentOrNull(); - - switch (message) - { - case PullStartedMessage: - pulledStatus?.ShowAlert(AlertType.Pulled); - break; - case PullStoppedMessage: - pulledStatus?.ClearAlert(AlertType.Pulled); - break; - } - } - protected override void OnRemove() { TryStopPull(); diff --git a/Content.Shared/Pulling/Components/SharedPullerComponent.cs b/Content.Shared/Pulling/Components/SharedPullerComponent.cs index 12a539ab12..1579339958 100644 --- a/Content.Shared/Pulling/Components/SharedPullerComponent.cs +++ b/Content.Shared/Pulling/Components/SharedPullerComponent.cs @@ -1,6 +1,4 @@ -using Content.Shared.Alert; -using Content.Shared.Movement.Components; -using Content.Shared.Physics.Pull; +using Content.Shared.Movement.Components; using Robust.Shared.GameObjects; using Component = Robust.Shared.GameObjects.Component; @@ -46,30 +44,5 @@ namespace Content.Shared.Pulling.Components base.OnRemove(); } - - public override void HandleMessage(ComponentMessage message, IComponent? component) - { - base.HandleMessage(message, component); - - if (message is not PullMessage pullMessage || - pullMessage.Puller.Owner != Owner) - { - return; - } - - SharedAlertsComponent? ownerStatus = Owner.GetComponentOrNull(); - - switch (message) - { - case PullStartedMessage msg: - Pulling = msg.Pulled.Owner; - ownerStatus?.ShowAlert(AlertType.Pulling); - break; - case PullStoppedMessage _: - Pulling = null; - ownerStatus?.ClearAlert(AlertType.Pulling); - break; - } - } } } diff --git a/Content.Shared/Physics/Pull/PullAttemptMessage.cs b/Content.Shared/Pulling/Events/PullAttemptMessage.cs similarity index 100% rename from Content.Shared/Physics/Pull/PullAttemptMessage.cs rename to Content.Shared/Pulling/Events/PullAttemptMessage.cs diff --git a/Content.Shared/Physics/Pull/PullMessage.cs b/Content.Shared/Pulling/Events/PullMessage.cs similarity index 87% rename from Content.Shared/Physics/Pull/PullMessage.cs rename to Content.Shared/Pulling/Events/PullMessage.cs index cbdb5af3a1..4376720a03 100644 --- a/Content.Shared/Physics/Pull/PullMessage.cs +++ b/Content.Shared/Pulling/Events/PullMessage.cs @@ -3,7 +3,7 @@ using Robust.Shared.Physics; namespace Content.Shared.Physics.Pull { - public class PullMessage : ComponentMessage + public class PullMessage : EntityEventArgs { public readonly IPhysBody Puller; public readonly IPhysBody Pulled; diff --git a/Content.Shared/Physics/Pull/PullStartedMessage.cs b/Content.Shared/Pulling/Events/PullStartedMessage.cs similarity index 100% rename from Content.Shared/Physics/Pull/PullStartedMessage.cs rename to Content.Shared/Pulling/Events/PullStartedMessage.cs diff --git a/Content.Shared/Physics/Pull/PullStoppedMessage.cs b/Content.Shared/Pulling/Events/PullStoppedMessage.cs similarity index 100% rename from Content.Shared/Physics/Pull/PullStoppedMessage.cs rename to Content.Shared/Pulling/Events/PullStoppedMessage.cs diff --git a/Content.Shared/Pulling/Systems/SharedPullerSystem.cs b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs new file mode 100644 index 0000000000..d05754c851 --- /dev/null +++ b/Content.Shared/Pulling/Systems/SharedPullerSystem.cs @@ -0,0 +1,44 @@ +using Content.Shared.Alert; +using Content.Shared.Physics.Pull; +using Content.Shared.Pulling.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; + +namespace Content.Shared.Pulling +{ + [UsedImplicitly] + public sealed class SharedPullerSystem : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(PullerHandlePullStarted); + SubscribeLocalEvent(PullerHandlePullStopped); + } + + private static void PullerHandlePullStarted( + EntityUid uid, + SharedPullerComponent component, + PullStartedMessage args) + { + if (args.Puller.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ShowAlert(AlertType.Pulling); + } + + private static void PullerHandlePullStopped( + EntityUid uid, + SharedPullerComponent component, + PullStoppedMessage args) + { + if (args.Puller.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ClearAlert(AlertType.Pulling); + } + } +} diff --git a/Content.Shared/Pulling/SharedPullingSystem.cs b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs similarity index 88% rename from Content.Shared/Pulling/SharedPullingSystem.cs rename to Content.Shared/Pulling/Systems/SharedPullingSystem.cs index 0bb04d19b4..5783ee332d 100644 --- a/Content.Shared/Pulling/SharedPullingSystem.cs +++ b/Content.Shared/Pulling/Systems/SharedPullingSystem.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Content.Shared.Alert; using Content.Shared.GameTicking; using Content.Shared.Input; using Content.Shared.Physics.Pull; @@ -56,12 +57,34 @@ namespace Content.Shared.Pulling SubscribeLocalEvent(PullerMoved); SubscribeLocalEvent(HandleContainerInsert); + SubscribeLocalEvent(PullableHandlePullStarted); + SubscribeLocalEvent(PullableHandlePullStopped); + CommandBinds.Builder .Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject)) .Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject)) .Register(); } + // Raise a "you are being pulled" alert if the pulled entity has alerts. + private static void PullableHandlePullStarted(EntityUid uid, SharedPullableComponent component, PullStartedMessage args) + { + if (args.Pulled.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ShowAlert(AlertType.Pulled); + } + + private static void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args) + { + if (args.Pulled.Owner.Uid != uid) + return; + + if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts)) + alerts.ClearAlert(AlertType.Pulled); + } + public override void Update(float frameTime) { base.Update(frameTime); diff --git a/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml b/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml new file mode 100644 index 0000000000..aa5b7bdb22 --- /dev/null +++ b/Resources/Prototypes/Entities/Virtual/virtual_pull_item.yml @@ -0,0 +1,8 @@ +# This item is stored in the hand slot while you are pulling something, to represent that and what you are pulling. +- type: entity + id: HandVirtualPull + name: VIRTUAL PULL YOU SHOULD NOT SEE THIS + abstract: true + components: + - type: Item + - type: HandVirtualPull