diff --git a/Content.Client/Animations/ReusableAnimations.cs b/Content.Client/Animations/ReusableAnimations.cs index 7ce8cb358f..38dfd6629c 100644 --- a/Content.Client/Animations/ReusableAnimations.cs +++ b/Content.Client/Animations/ReusableAnimations.cs @@ -30,6 +30,7 @@ namespace Content.Client.Animations } var sprite = entMan.GetComponent(animatableClone); sprite.CopyFrom(sprite0); + sprite.Visible = true; var animations = entMan.GetComponent(animatableClone); animations.AnimationCompleted += (_) => { diff --git a/Content.Client/Hands/HandsComponent.cs b/Content.Client/Hands/HandsComponent.cs index 4a81bd6958..2927ba97bc 100644 --- a/Content.Client/Hands/HandsComponent.cs +++ b/Content.Client/Hands/HandsComponent.cs @@ -1,5 +1,5 @@ using Content.Shared.Hands.Components; -using Robust.Shared.Containers; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -7,48 +7,9 @@ namespace Content.Client.Hands { [RegisterComponent] [ComponentReference(typeof(SharedHandsComponent))] + [Friend(typeof(HandsSystem))] public class HandsComponent : SharedHandsComponent { public HandsGui? Gui { get; set; } - - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - if (curState is not HandsComponentState state) - return; - - Hands.Clear(); - - foreach (var handState in state.Hands) - { - var newHand = new Hand(handState.Name, handState.Location); - Hands.Add(newHand); - } - - ActiveHand = state.ActiveHand; - - HandsModified(); - } - - public override void HandsModified() - { - UpdateHandContainers(); - - base.HandsModified(); - } - - public void UpdateHandContainers() - { - if (!IoCManager.Resolve().TryGetComponent(Owner, out var containerMan)) - return; - - foreach (var hand in Hands) - { - if (hand.Container == null) - { - containerMan.TryGetContainer(hand.Name, out var container); - hand.Container = container; - } - } - } } } diff --git a/Content.Client/Hands/HandsGui.xaml.cs b/Content.Client/Hands/HandsGui.xaml.cs index 71fc1b6cf8..e7eaea7cd1 100644 --- a/Content.Client/Hands/HandsGui.xaml.cs +++ b/Content.Client/Hands/HandsGui.xaml.cs @@ -59,24 +59,18 @@ namespace Content.Client.Hands { 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() + public void Update(HandsGuiState state) { - var state = _handsSystem.GetGuiState(); - ActiveHand = state.ActiveHand; _hands = state.GuiHands; Array.Sort(_hands, HandOrderComparer.Instance); @@ -97,7 +91,7 @@ namespace Content.Client.Hands newButton.OnPressed += args => OnHandPressed(args, handName); newButton.OnStoragePressed += _ => OnStoragePressed(handName); - _itemSlotManager.SetItemSlot(newButton, hand.HeldItem ?? EntityUid.Invalid); + _itemSlotManager.SetItemSlot(newButton, hand.HeldItem); // Show blocked overlay if hand is blocked. newButton.Blocked.Visible = @@ -107,7 +101,7 @@ namespace Content.Client.Hands if (TryGetActiveHand(out var activeHand)) { activeHand.HandButton.SetActiveHand(true); - StatusPanel.Update(activeHand.HeldItem ?? EntityUid.Invalid); + StatusPanel.Update(activeHand.HeldItem); } } @@ -119,7 +113,7 @@ namespace Content.Client.Hands } else if (TryGetHand(handName, out var hand)) { - _itemSlotManager.OnButtonPressed(args, hand.HeldItem ?? EntityUid.Invalid); + _itemSlotManager.OnButtonPressed(args, hand.HeldItem); } } @@ -156,7 +150,7 @@ namespace Content.Client.Hands foreach (var hand in _hands) { - _itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem ?? EntityUid.Invalid); + _itemSlotManager.UpdateCooldown(hand.HandButton, hand.HeldItem); } } diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index ddfb3eb6dc..6b1b2adfa5 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -1,4 +1,4 @@ -using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Client.Animations; using Content.Client.HUD; @@ -9,8 +9,10 @@ using Robust.Client.GameObjects; using Robust.Client.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.Input.Binding; +using Robust.Shared.GameStates; using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Timing; namespace Content.Client.Hands @@ -22,8 +24,6 @@ namespace Content.Client.Hands [Dependency] private readonly IGameHud _gameHud = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; - public event Action? GuiStateUpdated; - public override void Initialize() { base.Initialize(); @@ -31,70 +31,107 @@ namespace Content.Client.Hands SubscribeLocalEvent(HandlePlayerAttached); SubscribeLocalEvent(HandlePlayerDetached); SubscribeLocalEvent(HandleCompRemove); - SubscribeLocalEvent(HandleHandsModified); + SubscribeLocalEvent(HandleComponentState); - SubscribeNetworkEvent(HandlePickupAnimation); + SubscribeNetworkEvent(HandlePickupAnimation); } - public override void Shutdown() + #region StateHandling + private void HandleComponentState(EntityUid uid, HandsComponent component, ref ComponentHandleState args) { - 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) - GuiStateUpdated?.Invoke(); - } - - private void HandlePickupAnimation(PickupAnimationMessage msg) - { - if (!EntityManager.EntityExists(msg.EntityUid)) + if (args.Current is not HandsComponentState state) return; + // Do we have a NEW hand? + var handsModified = component.Hands.Count != state.Hands.Count; + if (!handsModified) + { + for (var i = 0; i < state.Hands.Count; i++) + { + if (component.Hands[i].Name != state.Hands[i].Name || + component.Hands[i].Location != state.Hands[i].Location) + { + handsModified = true; + break; + } + } + } + + if (handsModified) + { + // we have new hands, get the new containers. + component.Hands = state.Hands; + UpdateHandContainers(uid, component); + } + + TrySetActiveHand(uid, state.ActiveHand, component); + } + + /// + /// Used to update the hand-containers when hands have been added or removed. Also updates the GUI + /// + public void UpdateHandContainers(EntityUid uid, HandsComponent? hands = null, ContainerManagerComponent? containerMan = null) + { + if (!Resolve(uid, ref hands, ref containerMan)) + return; + + foreach (var hand in hands.Hands) + { + if (hand.Container == null) + { + hand.Container = hands.Owner.EnsureContainer(hand.Name); + } + } + + if (uid == _playerManager.LocalPlayer?.ControlledEntity) + UpdateGui(); + } + #endregion + + #region PickupAnimation + private void HandlePickupAnimation(PickupAnimationEvent msg) + { + PickupAnimation(msg.ItemUid, msg.InitialPosition, msg.FinalPosition); + } + + public override void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, + EntityUid? exclude) + { + PickupAnimation(item, initialPosition, finalPosition); + } + + public void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition) + { if (!_gameTiming.IsFirstTimePredicted) return; - ReusableAnimations.AnimateEntityPickup(msg.EntityUid, msg.InitialPosition, msg.FinalPosition, EntityManager); - } - - public HandsGuiState GetGuiState() - { - if (GetPlayerHandsComponent() is not { } 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); + if (finalPosition.EqualsApprox(initialPosition.Position, tolerance: 0.1f)) + return; + + ReusableAnimations.AnimateEntityPickup(item, initialPosition, finalPosition); } + #endregion public EntityUid? GetActiveHandEntity() { - if (GetPlayerHandsComponent() is not { ActiveHand: { } active } hands) - return null; - - return hands.GetHand(active).HeldEntity; + return TryGetPlayerHands(out var hands) && hands.TryGetActiveHeldEntity(out var entity) + ? entity + : null; } - private HandsComponent? GetPlayerHandsComponent() + /// + /// Get the hands component of the local player + /// + public bool TryGetPlayerHands([NotNullWhen(true)] out HandsComponent? hands) { var player = _playerManager.LocalPlayer?.ControlledEntity; - - if (player is not {Valid: true} || !EntityManager.TryGetComponent(player.Value, out HandsComponent? hands)) - return null; - - return hands; + hands = null; + return player != null && TryComp(player.Value, out hands); } + /// + /// Called when a user clicked on their hands GUI + /// public void UIHandClick(HandsComponent hands, string handName) { if (!hands.TryGetHand(handName, out var pressedHand)) @@ -117,7 +154,7 @@ namespace Content.Client.Hands if (pressedHand != activeHand && pressedEntity == null) { // change active hand - RaiseNetworkEvent(new RequestSetHandEvent(handName)); + EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName)); return; } @@ -135,9 +172,44 @@ namespace Content.Client.Hands } } + /// + /// Called when a user clicks on an item in their hands GUI. + /// public void UIHandActivate(string handName) { - RaiseNetworkEvent (new ActivateInHandMsg(handName)); + RaiseNetworkEvent(new ActivateInHandMsg(handName)); + } + + #region Gui + public void UpdateGui(HandsComponent? hands = null) + { + if (hands == null && !TryGetPlayerHands(out hands) || hands.Gui == null) + return; + + var states = hands.Hands + .Select(hand => new GuiHand(hand.Name, hand.Location, hand.HeldEntity)) + .ToArray(); + + hands.Gui.Update(new HandsGuiState(states, hands.ActiveHand)); + } + + protected override void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args) + { + base.HandleContainerModified(uid, component, args); + + if (uid == _playerManager.LocalPlayer?.ControlledEntity) + UpdateGui(); + } + + public override bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null) + { + if (!base.TrySetActiveHand(uid, value, handComp)) + return false; + + if (uid == _playerManager.LocalPlayer?.ControlledEntity) + UpdateGui(); + + return true; } private void HandlePlayerAttached(EntityUid uid, HandsComponent component, PlayerAttachedEvent args) @@ -145,6 +217,7 @@ namespace Content.Client.Hands component.Gui = new HandsGui(component, this); _gameHud.HandsContainer.AddChild(component.Gui); component.Gui.SetPositionFirst(); + UpdateGui(component); } private static void HandlePlayerDetached(EntityUid uid, HandsComponent component, PlayerDetachedEvent args) @@ -162,5 +235,6 @@ namespace Content.Client.Hands comp.Gui?.Orphan(); comp.Gui = null; } + #endregion } } diff --git a/Content.Client/Items/Components/ItemComponent.cs b/Content.Client/Items/Components/ItemComponent.cs index 7c60585abe..39e5f8f962 100644 --- a/Content.Client/Items/Components/ItemComponent.cs +++ b/Content.Client/Items/Components/ItemComponent.cs @@ -1,6 +1,4 @@ -using Content.Client.Hands; using Content.Shared.Item; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -8,15 +6,5 @@ namespace Content.Client.Items.Components { [RegisterComponent] [ComponentReference(typeof(SharedItemComponent))] - public class ItemComponent : SharedItemComponent - { - protected override void OnEquippedPrefixChange() - { - if (!Owner.TryGetContainer(out var container)) - return; - - if (IoCManager.Resolve().TryGetComponent(container.Owner, out HandsComponent? hands)) - hands.UpdateHandVisualizer(); - } - } + public class ItemComponent : SharedItemComponent { } } diff --git a/Content.Client/Items/Managers/IItemSlotManager.cs b/Content.Client/Items/Managers/IItemSlotManager.cs index 5cf555554b..43ea8b4eb8 100644 --- a/Content.Client/Items/Managers/IItemSlotManager.cs +++ b/Content.Client/Items/Managers/IItemSlotManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using Content.Client.Items.UI; using Robust.Client.UserInterface; using Robust.Shared.GameObjects; @@ -7,10 +7,10 @@ namespace Content.Client.Items.Managers { public interface IItemSlotManager { - bool OnButtonPressed(GUIBoundKeyEventArgs args, EntityUid item); - void UpdateCooldown(ItemSlotButton? cooldownTexture, EntityUid entity); + bool OnButtonPressed(GUIBoundKeyEventArgs args, EntityUid? item); + void UpdateCooldown(ItemSlotButton? cooldownTexture, EntityUid? entity); bool SetItemSlot(ItemSlotButton button, EntityUid? entity); - void HoverInSlot(ItemSlotButton button, EntityUid entity, bool fits); + void HoverInSlot(ItemSlotButton button, EntityUid? entity, bool fits); event Action? EntityHighlightedUpdated; bool IsHighlighted(EntityUid? uid); diff --git a/Content.Client/Items/Managers/ItemSlotManager.cs b/Content.Client/Items/Managers/ItemSlotManager.cs index 291ad88a24..c4192428e9 100644 --- a/Content.Client/Items/Managers/ItemSlotManager.cs +++ b/Content.Client/Items/Managers/ItemSlotManager.cs @@ -53,34 +53,34 @@ namespace Content.Client.Items.Managers button.StorageButton.Visible = _entityManager.HasComponent(entity); } - button.Entity = entity; + button.Entity = entity ?? default; // im lazy button.UpdateSlotHighlighted(); return true; } - public bool OnButtonPressed(GUIBoundKeyEventArgs args, EntityUid item) + public bool OnButtonPressed(GUIBoundKeyEventArgs args, EntityUid? item) { - if (item == default) + if (item == null) return false; if (args.Function == ContentKeyFunctions.ExamineEntity) { _entitySystemManager.GetEntitySystem() - .DoExamine(item); + .DoExamine(item.Value); } else if (args.Function == ContentKeyFunctions.OpenContextMenu) { - _entitySystemManager.GetEntitySystem().VerbMenu.OpenVerbMenu(item); + _entitySystemManager.GetEntitySystem().VerbMenu.OpenVerbMenu(item.Value); } else if (args.Function == ContentKeyFunctions.ActivateItemInWorld) { - _entityManager.EntityNetManager?.SendSystemNetworkMessage(new InteractInventorySlotEvent(item, altInteract: false)); + _entityManager.EntityNetManager?.SendSystemNetworkMessage(new InteractInventorySlotEvent(item.Value, altInteract: false)); } else if (args.Function == ContentKeyFunctions.AltActivateItemInWorld) { - _entityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(item, altInteract: true)); + _entityManager.RaisePredictiveEvent(new InteractInventorySlotEvent(item.Value, altInteract: true)); } else { @@ -90,7 +90,7 @@ namespace Content.Client.Items.Managers return true; } - public void UpdateCooldown(ItemSlotButton? button, EntityUid entity) + public void UpdateCooldown(ItemSlotButton? button, EntityUid? entity) { var cooldownDisplay = button?.CooldownDisplay; @@ -99,7 +99,7 @@ namespace Content.Client.Items.Managers return; } - if (entity == default || _entityManager.Deleted(entity) || + if (entity == null || _entityManager.Deleted(entity) || !_entityManager.TryGetComponent(entity, out ItemCooldownComponent? cooldown) || !cooldown.CooldownStart.HasValue || !cooldown.CooldownEnd.HasValue) @@ -119,9 +119,9 @@ namespace Content.Client.Items.Managers cooldownDisplay.Visible = ratio > -1f; } - public void HoverInSlot(ItemSlotButton button, EntityUid entity, bool fits) + public void HoverInSlot(ItemSlotButton button, EntityUid? entity, bool fits) { - if (entity == default || !button.MouseIsHovering) + if (entity == null || !button.MouseIsHovering) { button.ClearHover(); return; @@ -135,7 +135,7 @@ namespace Content.Client.Items.Managers // Set green / red overlay at 50% transparency var hoverEntity = _entityManager.SpawnEntity("hoverentity", MapCoordinates.Nullspace); var hoverSprite = _entityManager.GetComponent(hoverEntity); - hoverSprite.CopyFrom(_entityManager.GetComponent(entity)); + hoverSprite.CopyFrom(_entityManager.GetComponent(entity.Value)); hoverSprite.Color = fits ? new Color(0, 255, 0, 127) : new Color(255, 0, 0, 127); button.HoverSpriteView.Sprite = hoverSprite; diff --git a/Content.Client/Items/UI/ItemStatusPanel.cs b/Content.Client/Items/UI/ItemStatusPanel.cs index f0bcb7c8a9..1aa9669f57 100644 --- a/Content.Client/Items/UI/ItemStatusPanel.cs +++ b/Content.Client/Items/UI/ItemStatusPanel.cs @@ -33,7 +33,7 @@ namespace Content.Client.Items.UI private readonly PanelContainer _panel; [ViewVariables] - private EntityUid _entity; + private EntityUid? _entity; public ItemStatusPanel(Texture texture, StyleBox.Margin cutout, StyleBox.Margin flat, Label.AlignMode textAlign) { @@ -130,19 +130,19 @@ namespace Content.Client.Items.UI UpdateItemName(); } - public void Update(EntityUid entity) + public void Update(EntityUid? entity) { - if (entity == default) + if (entity == null) { ClearOldStatus(); - _entity = default; + _entity = null; _panel.Visible = false; return; } if (entity != _entity) { - _entity = entity; + _entity = entity.Value; BuildNewEntityStatus(); UpdateItemName(); @@ -153,7 +153,7 @@ namespace Content.Client.Items.UI private void UpdateItemName() { - if (_entity == default) + if (_entity == null) return; if (_entityManager.TryGetComponent(_entity, out HandVirtualItemComponent? virtualItem) @@ -163,7 +163,7 @@ namespace Content.Client.Items.UI } else { - _itemNameLabel.Text = _entityManager.GetComponent(_entity).EntityName; + _itemNameLabel.Text = _entityManager.GetComponent(_entity.Value).EntityName; } } @@ -185,7 +185,7 @@ namespace Content.Client.Items.UI ClearOldStatus(); - foreach (var statusComponent in _entityManager.GetComponents(_entity)) + foreach (var statusComponent in _entityManager.GetComponents(_entity!.Value)) { var control = statusComponent.MakeControl(); _statusContents.AddChild(control); @@ -194,7 +194,7 @@ namespace Content.Client.Items.UI } var collectMsg = new ItemStatusCollectMessage(); - _entityManager.EventBus.RaiseLocalEvent(_entity, collectMsg); + _entityManager.EventBus.RaiseLocalEvent(_entity!.Value, collectMsg); foreach (var control in collectMsg.Controls) { diff --git a/Content.Server/Construction/ConstructionSystem.Initial.cs b/Content.Server/Construction/ConstructionSystem.Initial.cs index 8f7e24d4be..0a915b1283 100644 --- a/Content.Server/Construction/ConstructionSystem.Initial.cs +++ b/Content.Server/Construction/ConstructionSystem.Initial.cs @@ -332,9 +332,8 @@ namespace Content.Server.Construction } } - if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is {Valid: true} item && - EntityManager.TryGetComponent(item, out SharedItemComponent? itemComp)) - hands.PutInHandOrDrop(itemComp); + if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is {Valid: true} item) + hands.PutInHandOrDrop(item); } // LEGACY CODE. See warning at the top of the file! diff --git a/Content.Server/DoAfter/DoAfterComponent.cs b/Content.Server/DoAfter/DoAfterComponent.cs index 9a68b3ff66..50abca5584 100644 --- a/Content.Server/DoAfter/DoAfterComponent.cs +++ b/Content.Server/DoAfter/DoAfterComponent.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Content.Shared.DoAfter; using Robust.Shared.GameObjects; using Robust.Shared.Players; @@ -31,7 +31,7 @@ namespace Content.Server.DoAfter doAfter.EventArgs.BreakOnUserMove, doAfter.EventArgs.BreakOnTargetMove, doAfter.EventArgs.MovementThreshold, - doAfter.EventArgs.Target ?? EntityUid.Invalid); + doAfter.EventArgs.Target); toAdd.Add(clientDoAfter); } diff --git a/Content.Server/Hands/Components/HandsComponent.cs b/Content.Server/Hands/Components/HandsComponent.cs index 932d634223..82e44dcf1f 100644 --- a/Content.Server/Hands/Components/HandsComponent.cs +++ b/Content.Server/Hands/Components/HandsComponent.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.Act; -using Content.Server.Interaction; using Content.Server.Popups; using Content.Server.Pulling; using Content.Shared.Audio; @@ -13,12 +12,10 @@ using Content.Shared.Item; using Content.Shared.Popups; using Content.Shared.Pulling.Components; using Content.Shared.Sound; -using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Serialization.Manager.Attributes; @@ -37,32 +34,6 @@ namespace Content.Server.Hands.Components int IDisarmedAct.Priority => int.MaxValue; // We want this to be the last disarm act to run. - protected override void OnHeldEntityRemovedFromHand(EntityUid heldEntity, HandState handState) - { - if (_entities.TryGetComponent(heldEntity, out SharedItemComponent? item)) - { - item.RemovedFromSlot(); - _entitySystemManager.GetEntitySystem().UnequippedHandInteraction(Owner, heldEntity, handState); - } - if (_entities.TryGetComponent(heldEntity, out SpriteComponent? sprite)) - { - sprite.RenderOrder = _entities.CurrentTick.Value; - } - } - - protected override void HandlePickupAnimation(EntityUid entity) - { - var initialPosition = EntityCoordinates.FromMap(_entities.GetComponent(Owner).Parent?.Owner ?? Owner, _entities.GetComponent(entity).MapPosition); - - var finalPosition = _entities.GetComponent(Owner).LocalPosition; - - if (finalPosition.EqualsApprox(initialPosition.Position)) - return; - - _entities.EntityNetManager!.SendSystemNetworkMessage( - new PickupAnimationMessage(entity, finalPosition, initialPosition)); - } - #region Pull/Disarm void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs args) @@ -198,24 +169,6 @@ namespace Content.Server.Hands.Components yield return item; } } - - /// - /// Checks if any hand can pick up an item. - /// - public bool CanPutInHand(SharedItemComponent item, bool mobCheck = true) - { - var entity = item.Owner; - - if (mobCheck && !PlayerCanPickup()) - return false; - - foreach (var hand in Hands) - { - if (CanInsertEntityIntoHand(hand, entity)) - return true; - } - return false; - } #endregion } } diff --git a/Content.Server/Hands/Systems/HandVirtualItemSystem.cs b/Content.Server/Hands/Systems/HandVirtualItemSystem.cs index f732f221ef..0eb368e075 100644 --- a/Content.Server/Hands/Systems/HandVirtualItemSystem.cs +++ b/Content.Server/Hands/Systems/HandVirtualItemSystem.cs @@ -4,12 +4,15 @@ using Content.Shared.Hands.Components; using Content.Shared.Interaction; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.Hands.Systems { [UsedImplicitly] public sealed class HandVirtualItemSystem : SharedHandVirtualItemSystem { + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + public override void Initialize() { base.Initialize(); @@ -34,7 +37,7 @@ namespace Content.Server.Hands.Systems var virtualItem = EntityManager.SpawnEntity("HandVirtualItem", pos); var virtualItemComp = EntityManager.GetComponent(virtualItem); virtualItemComp.BlockingEntity = blockingEnt; - hands.PutEntityIntoHand(hand, virtualItem); + _handsSystem.PutEntityIntoHand(user, hand, virtualItem, hands); return true; } } diff --git a/Content.Server/Hands/Systems/HandsSystem.cs b/Content.Server/Hands/Systems/HandsSystem.cs index a067e19747..fd2f3fff2b 100644 --- a/Content.Server/Hands/Systems/HandsSystem.cs +++ b/Content.Server/Hands/Systems/HandsSystem.cs @@ -1,13 +1,14 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Content.Server.Administration.Logs; using Content.Server.Hands.Components; -using Content.Server.Interaction; -using Content.Server.Inventory; using Content.Server.Stack; using Content.Server.Storage.Components; +using Content.Server.Strip; using Content.Server.Throwing; using Content.Shared.ActionBlocker; +using Content.Shared.Database; using Content.Shared.Examine; using Content.Shared.Hands; using Content.Shared.Hands.Components; @@ -16,14 +17,17 @@ using Content.Shared.Inventory; using Content.Shared.Physics.Pull; using Content.Shared.Popups; using JetBrains.Annotations; +using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Containers; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Player; using Robust.Shared.Players; using Robust.Shared.Utility; @@ -36,6 +40,8 @@ namespace Content.Server.Hands.Systems [Dependency] private readonly StackSystem _stackSystem = default!; [Dependency] private readonly HandVirtualItemSystem _virtualItemSystem = default!; [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; + [Dependency] private readonly AdminLogSystem _logSystem = default!; + [Dependency] private readonly StrippableSystem _strippableSystem = default!; public override void Initialize() { @@ -51,27 +57,78 @@ namespace Content.Server.Hands.Systems SubscribeLocalEvent(HandlePullStarted); SubscribeLocalEvent(HandlePullStopped); + SubscribeLocalEvent(GetComponentState); + CommandBinds.Builder - .Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem)) - .Bind(ContentKeyFunctions.AltActivateItemInHand, InputCmdHandler.FromDelegate(HandleAltActivateItem)) + .Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(s => HandleActivateItem(s))) + .Bind(ContentKeyFunctions.AltActivateItemInHand, InputCmdHandler.FromDelegate(s => HandleActivateItem(s, true))) .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(); } + public override void Shutdown() + { + base.Shutdown(); + + CommandBinds.Unregister(); + } + + private void GetComponentState(EntityUid uid, HandsComponent hands, ref ComponentGetState args) + { + args.State = new HandsComponentState(hands.Hands, hands.ActiveHand); + } + + #region EntityInsertRemove + public override void RemoveHeldEntityFromHand(EntityUid uid, Hand hand, SharedHandsComponent? hands = null) + { + base.RemoveHeldEntityFromHand(uid, hand, hands); + + // update gui of anyone stripping this entity. + _strippableSystem.SendUpdate(uid); + + if (TryComp(hand.HeldEntity, out SpriteComponent? sprite)) + sprite.RenderOrder = EntityManager.CurrentTick.Value; + } + + public override void PutEntityIntoHand(EntityUid uid, Hand hand, EntityUid entity, SharedHandsComponent? hands = null) + { + base.PutEntityIntoHand(uid, hand, entity, hands); + + // update gui of anyone stripping this entity. + _strippableSystem.SendUpdate(uid); + + _logSystem.Add(LogType.Pickup, LogImpact.Low, $"{uid} picked up {entity}"); + } + + public override void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, + EntityUid? exclude) + { + if (finalPosition.EqualsApprox(initialPosition.Position, tolerance: 0.1f)) + return; + + var filter = Filter.Pvs(item); + + if (exclude != null) + filter = filter.RemoveWhereAttachedEntity(entity => entity == exclude); + + RaiseNetworkEvent(new PickupAnimationEvent(item, initialPosition, finalPosition), filter); + } + #endregion + + #region pulling private static void HandlePullAttempt(EntityUid uid, HandsComponent component, PullAttemptMessage args) { if (args.Puller.Owner != uid) return; // Cancel pull if all hands full. - if (component.Hands.All(hand => hand.HeldEntity != null)) + if (component.Hands.All(hand => !hand.IsEmpty)) args.Cancelled = true; } + private void HandlePullStarted(EntityUid uid, HandsComponent component, PullStartedMessage args) { if (args.Puller.Owner != uid) @@ -93,132 +150,43 @@ namespace Content.Server.Hands.Systems foreach (var hand in component.Hands) { if (hand.HeldEntity == null - || !EntityManager.TryGetComponent(hand.HeldEntity, out HandVirtualItemComponent? virtualItem) + || !TryComp(hand.HeldEntity, out HandVirtualItemComponent? virtualItem) || virtualItem.BlockingEntity != args.Pulled.Owner) continue; - EntityManager.DeleteEntity(hand.HeldEntity.Value); + QueueDel(hand.HeldEntity.Value); break; } } + #endregion - private void SwapHandsPressed(ICommonSession? session) - { - var player = session?.AttachedEntity; - - if (!player.HasValue || !player.Value.IsValid()) - return; - - if (!EntityManager.TryGetComponent(player.Value, 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.HasValue || !player.Value.IsValid()) - return false; - - if (!EntityManager.TryGetComponent(player.Value, out SharedHandsComponent? hands)) - return false; - - var activeHand = hands.ActiveHand; - - if (activeHand == null) - return false; - - hands.TryDropHand(activeHand, coords); - return false; - } - + #region interactions private void HandleMoveItemFromHand(MoveItemFromHandMsg msg, EntitySessionEventArgs args) { - if (!TryGetHandsComp(args.SenderSession, out var hands)) - return; - - hands.TryMoveHeldEntityToActiveHand(msg.HandName); + if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) + hands.TryMoveHeldEntityToActiveHand(msg.HandName); } - private void HandleUseInHand(UseInHandMsg msg, EntitySessionEventArgs args) { - if (!TryGetHandsComp(args.SenderSession, out var hands)) - return; - - hands.ActivateItem(); + if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) + hands.ActivateItem(); } - 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(); - - CommandBinds.Unregister(); + if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) + hands.InteractHandWithActiveHand(msg.HandName); } private void HandleActivateInHand(ActivateInHandMsg msg, EntitySessionEventArgs args) { - if (!TryGetHandsComp(args.SenderSession, out var hands)) - return; - - hands.ActivateHeldEntity(msg.HandName); + if (TryComp(args.SenderSession.AttachedEntity, out SharedHandsComponent? hands)) + hands.ActivateHeldEntity(msg.HandName); } - //TODO: Actually shows all items/clothing/etc. - private void HandleExamined(EntityUid uid, HandsComponent component, ExaminedEvent args) + private void HandleActivateItem(ICommonSession? session, bool altInteract = false) { - foreach (var inhand in component.GetAllHeldItems()) - { - if (EntityManager.HasComponent(inhand.Owner)) - continue; - - args.PushText(Loc.GetString("comp-hands-examine", ("user", component.Owner), ("item", inhand.Owner))); - } - } - - private bool TryGetHandsComp( - ICommonSession? session, - [NotNullWhen(true)] out SharedHandsComponent? hands) - { - hands = default; - - if (session is not IPlayerSession playerSession) - return false; - - var player = playerSession.AttachedEntity; - - if (player is not {Valid: true}) - return false; - - return EntityManager.TryGetComponent(player, out hands); - } - - private void HandleActivateItem(ICommonSession? session) - { - if (!TryGetHandsComp(session, out var hands)) - return; - - hands.ActivateItem(); - } - - private void HandleAltActivateItem(ICommonSession? session) - { - if (!TryGetHandsComp(session, out var hands)) - return; - - hands.ActivateItem(altInteract: true); + if (TryComp(session?.AttachedEntity, out SharedHandsComponent? hands)) + hands.ActivateItem(altInteract); } private bool HandleThrowItem(ICommonSession? session, EntityCoordinates coords, EntityUid uid) @@ -227,9 +195,9 @@ namespace Content.Server.Hands.Systems return false; if (playerSession.AttachedEntity is not {Valid: true} player || - !EntityManager.EntityExists(player) || + !Exists(player) || player.IsInContainer() || - !EntityManager.TryGetComponent(player, out SharedHandsComponent? hands) || + !TryComp(player, out SharedHandsComponent? hands) || !hands.TryGetActiveHeldEntity(out var throwEnt) || !_actionBlockerSystem.CanThrow(player)) return false; @@ -246,7 +214,7 @@ namespace Content.Server.Hands.Systems else if (!hands.Drop(throwEnt.Value)) return false; - var direction = coords.ToMapPos(EntityManager) - EntityManager.GetComponent(player).WorldPosition; + var direction = coords.ToMapPos(EntityManager) - Transform(player).WorldPosition; if (direction == Vector2.Zero) return true; @@ -273,14 +241,14 @@ namespace Content.Server.Hands.Systems if (session is not IPlayerSession playerSession) return; - if (playerSession.AttachedEntity is not {Valid: true} plyEnt || !EntityManager.EntityExists(plyEnt)) + if (playerSession.AttachedEntity is not {Valid: true} plyEnt || !Exists(plyEnt)) return; if (!EntityManager.TryGetComponent(plyEnt, out SharedHandsComponent? hands)) return; if (!_inventorySystem.TryGetSlotEntity(plyEnt, equipmentSlot, out var slotEntity) || - !EntityManager.TryGetComponent(slotEntity, out ServerStorageComponent? storageComponent)) + !TryComp(slotEntity, out ServerStorageComponent? storageComponent)) { plyEnt.PopupMessage(Loc.GetString("hands-system-missing-equipment-slot", ("slotName", equipmentSlot))); return; @@ -301,11 +269,24 @@ namespace Content.Server.Hands.Systems var lastStoredEntity = Enumerable.Last(storageComponent.StoredEntities); if (storageComponent.Remove(lastStoredEntity)) { - if (!hands.TryPickupEntityToActiveHand(lastStoredEntity)) - EntityManager.GetComponent(lastStoredEntity).Coordinates = EntityManager.GetComponent(plyEnt).Coordinates; + if (!hands.TryPickupEntityToActiveHand(lastStoredEntity, animateUser: true)) + Transform(lastStoredEntity).Coordinates = Transform(plyEnt).Coordinates; } } } } + #endregion + + //TODO: Actually shows all items/clothing/etc. + private void HandleExamined(EntityUid uid, HandsComponent component, ExaminedEvent args) + { + foreach (var inhand in component.GetAllHeldItems()) + { + if (HasComp(inhand.Owner)) + continue; + + args.PushText(Loc.GetString("comp-hands-examine", ("user", component.Owner), ("item", inhand.Owner))); + } + } } } diff --git a/Content.Server/Inventory/ServerInventorySystem.cs b/Content.Server/Inventory/ServerInventorySystem.cs index f9eed3a093..e5aaf831e5 100644 --- a/Content.Server/Inventory/ServerInventorySystem.cs +++ b/Content.Server/Inventory/ServerInventorySystem.cs @@ -46,7 +46,6 @@ namespace Content.Server.Inventory { hands.PutInHand(itemUid.Value); } - } private void OnOpenSlotStorage(OpenSlotStorageNetworkMessage ev) diff --git a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs index b6d9a27029..8ff77a7704 100644 --- a/Content.Server/Light/EntitySystems/PoweredLightSystem.cs +++ b/Content.Server/Light/EntitySystems/PoweredLightSystem.cs @@ -183,7 +183,7 @@ namespace Content.Server.Light.EntitySystems if (userUid != null) { if (EntityManager.TryGetComponent(userUid.Value, out SharedHandsComponent? hands)) - hands.TryPutInActiveHandOrAny(bulb); + hands.PutInHand(bulb); } UpdateLight(uid, light); diff --git a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs index b33e395830..0e3bc6c7d9 100644 --- a/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs +++ b/Content.Server/PneumaticCannon/PneumaticCannonSystem.cs @@ -322,7 +322,7 @@ namespace Content.Server.PneumaticCannon { if (EntityManager.TryGetComponent(user, out var hands)) { - hands.TryPutInActiveHandOrAny(contained); + hands.PutInHand(contained); } user.PopupMessage(Loc.GetString("pneumatic-cannon-component-gas-tank-remove", diff --git a/Content.Server/Strip/StrippableComponent.cs b/Content.Server/Strip/StrippableComponent.cs index 9ccc8f50f2..fbb77386d3 100644 --- a/Content.Server/Strip/StrippableComponent.cs +++ b/Content.Server/Strip/StrippableComponent.cs @@ -47,18 +47,14 @@ namespace Content.Server.Strip _strippableSystem = EntitySystem.Get(); Owner.EnsureComponentWarn(); - var hands = Owner.EnsureComponentWarn(); var cuffed = Owner.EnsureComponentWarn(); cuffed.OnCuffedStateChanged += UpdateState; - hands.OnItemChanged += UpdateState; } protected override void Shutdown() { base.Shutdown(); - if(_entities.TryGetComponent(Owner, out var hands)) - hands.OnItemChanged -= UpdateState; if(_entities.TryGetComponent(Owner, out var cuffed)) cuffed.OnCuffedStateChanged -= UpdateState; } @@ -208,8 +204,8 @@ namespace Content.Server.Strip if (result != DoAfterStatus.Finished) return; userHands.Drop(hand); - hands.TryPickupEntity(hand, item!.Owner, checkActionBlocker: false); - UpdateState(); + hands.TryPickupEntity(hand, item!.Owner, checkActionBlocker: false, animateUser: true); + // hand update will trigger strippable update } /// @@ -314,10 +310,12 @@ namespace Content.Server.Strip var result = await doAfterSystem.WaitDoAfter(doAfterArgs); if (result != DoAfterStatus.Finished) return; - var item = hands.GetItem(hand); + if (!hands.TryGetHeldEntity(hand, out var entity)) + return; + hands.Drop(hand, false); - userHands.PutInHandOrDrop(item!); - UpdateState(); + userHands.PutInHandOrDrop(entity.Value); + // hand update will trigger strippable update } private void HandleUserInterfaceMessage(ServerBoundUserInterfaceMessage obj) diff --git a/Content.Server/Strip/StrippableSystem.cs b/Content.Server/Strip/StrippableSystem.cs index 9d7a1f5d4d..61557d73e6 100644 --- a/Content.Server/Strip/StrippableSystem.cs +++ b/Content.Server/Strip/StrippableSystem.cs @@ -43,7 +43,7 @@ namespace Content.Server.Strip public void SendUpdate(EntityUid uid, StrippableComponent? strippableComponent = null) { - if (!Resolve(uid, ref strippableComponent) || strippableComponent.UserInterface == null) + if (!Resolve(uid, ref strippableComponent, false) || strippableComponent.UserInterface == null) { return; } diff --git a/Content.Server/Weapon/Ranged/RangedWeaponSystem.cs b/Content.Server/Weapon/Ranged/RangedWeaponSystem.cs new file mode 100644 index 0000000000..af6a92fcaa --- /dev/null +++ b/Content.Server/Weapon/Ranged/RangedWeaponSystem.cs @@ -0,0 +1,23 @@ +using Content.Shared.Hands; +using Robust.Shared.GameObjects; + +namespace Content.Server.Weapon.Ranged +{ + public sealed class RangedWeaponSysten : EntitySystem + { + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnHandSelected); + } + + private void OnHandSelected(EntityUid uid, ServerRangedWeaponComponent component, HandSelectedEvent args) + { + // Instead of dirtying on hand-select this component should probably by dirtied whenever it needs to be. + // I take no responsibility for this code. It was like this when I got here. + + component.Dirty(); + } + } +} diff --git a/Content.Server/Weapon/Ranged/ServerRangedWeaponComponent.cs b/Content.Server/Weapon/Ranged/ServerRangedWeaponComponent.cs index ec4e3d6a71..1371c90216 100644 --- a/Content.Server/Weapon/Ranged/ServerRangedWeaponComponent.cs +++ b/Content.Server/Weapon/Ranged/ServerRangedWeaponComponent.cs @@ -28,7 +28,7 @@ using Robust.Shared.ViewVariables; namespace Content.Server.Weapon.Ranged { [RegisterComponent] - public sealed class ServerRangedWeaponComponent : SharedRangedWeaponComponent, IHandSelected + public sealed class ServerRangedWeaponComponent : SharedRangedWeaponComponent { [Dependency] private readonly IEntityManager _entMan = default!; [Dependency] private readonly IMapManager _mapManager = default!; @@ -195,11 +195,5 @@ namespace Content.Server.Weapon.Ranged } FireHandler?.Invoke(user, targetPos); } - - // Probably a better way to do this. - void IHandSelected.HandSelected(HandSelectedEventArgs eventArgs) - { - Dirty(); - } } } diff --git a/Content.Shared/Actions/Components/ItemActionsComponent.cs b/Content.Shared/Actions/Components/ItemActionsComponent.cs index fbe642a15c..27c0c8710c 100644 --- a/Content.Shared/Actions/Components/ItemActionsComponent.cs +++ b/Content.Shared/Actions/Components/ItemActionsComponent.cs @@ -24,7 +24,7 @@ namespace Content.Shared.Actions.Components /// Currently only maintained server side and not synced to client, as are all the equip/unequip events. /// [RegisterComponent] - public class ItemActionsComponent : Component, IEquippedHand, IUnequippedHand + public class ItemActionsComponent : Component { public override string Name => "ItemActions"; @@ -40,7 +40,7 @@ namespace Content.Shared.Actions.Components /// /// hand it's currently in, null if not in a hand. /// - public HandState? InHand; + public Hand? InHand; /// /// Entity currently holding this in hand or equip slot. Null if not held. @@ -179,19 +179,19 @@ namespace Content.Shared.Actions.Components GrantOrUpdate(actionType, toggleOn: toggleOn); } - void IEquippedHand.EquippedHand(EquippedHandEventArgs eventArgs) + public void EquippedHand(EntityUid user, Hand hand) { // this entity cannot be granted actions if no actions component - if (!IoCManager.Resolve().TryGetComponent(eventArgs.User, out var actionsComponent)) + if (!IoCManager.Resolve().TryGetComponent(user, out var actionsComponent)) return; - Holder = eventArgs.User; + Holder = user; HolderActionsComponent = actionsComponent; IsEquipped = true; - InHand = eventArgs.Hand; + InHand = hand; GrantOrUpdateAllToHolder(); } - void IUnequippedHand.UnequippedHand(UnequippedHandEventArgs eventArgs) + public void UnequippedHand() { RevokeAllFromHolder(); Holder = null; diff --git a/Content.Shared/Actions/SharedActionSystem.cs b/Content.Shared/Actions/SharedActionSystem.cs index b03c17d38d..2ca4a42a2f 100644 --- a/Content.Shared/Actions/SharedActionSystem.cs +++ b/Content.Shared/Actions/SharedActionSystem.cs @@ -1,5 +1,7 @@ -using Content.Shared.Actions.Components; +using Content.Shared.Actions.Components; +using Content.Shared.Hands; using Robust.Shared.GameObjects; +using System; namespace Content.Shared.Actions { @@ -16,6 +18,18 @@ namespace Content.Shared.Actions base.Initialize(); UpdatesOutsidePrediction = true; + SubscribeLocalEvent(OnHandUnequipped); + SubscribeLocalEvent(OnHandEquipped); + } + + private void OnHandEquipped(EntityUid uid, ItemActionsComponent component, EquippedHandEvent args) + { + component.EquippedHand(args.User, args.Hand); + } + + private void OnHandUnequipped(EntityUid uid, ItemActionsComponent component, UnequippedHandEvent args) + { + component.UnequippedHand(); } public override void Update(float frameTime) diff --git a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs index 3e2fec8c0a..ba5d102d98 100644 --- a/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs +++ b/Content.Shared/Containers/ItemSlot/ItemSlotsSystem.cs @@ -162,7 +162,7 @@ namespace Content.Shared.Containers.ItemSlots if (args.Handled) return; - if (!EntityManager.TryGetComponent(args.User, out SharedHandsComponent? hands)) + if (!EntityManager.TryGetComponent(args.User, out SharedHandsComponent hands)) return; foreach (var slot in itemSlots.Slots.Values) @@ -370,7 +370,7 @@ namespace Content.Shared.Containers.ItemSlots return false; if (user != null && EntityManager.TryGetComponent(user.Value, out SharedHandsComponent? hands)) - hands.TryPutInActiveHandOrAny(item.Value); + hands.PutInHand(item.Value); return true; } @@ -458,7 +458,7 @@ namespace Content.Shared.Containers.ItemSlots var verbSubject = slot.Name != string.Empty ? Loc.GetString(slot.Name) - : EntityManager.GetComponent(args.Using.Value).EntityName ?? string.Empty; + : Name(args.Using.Value) ?? string.Empty; Verb insertVerb = new(); insertVerb.Act = () => Insert(uid, slot, args.Using.Value, args.User, excludeUserAudio: true); diff --git a/Content.Shared/Hands/Components/SharedHandsComponent.cs b/Content.Shared/Hands/Components/SharedHandsComponent.cs index 21ce645e1d..3c0e9b131d 100644 --- a/Content.Shared/Hands/Components/SharedHandsComponent.cs +++ b/Content.Shared/Hands/Components/SharedHandsComponent.cs @@ -27,43 +27,14 @@ namespace Content.Shared.Hands.Components public sealed override string Name => "Hands"; - public event Action? OnItemChanged; //TODO: Try to replace C# event - /// /// The name of the currently active hand. /// - [ViewVariables(VVAccess.ReadWrite)] - public string? ActiveHand - { - get => _activeHand; - set - { - if (value != null && !HasHand(value)) - { - Logger.Warning($"{nameof(SharedHandsComponent)} on {Owner} tried to set its active hand to {value}, which was not a hand."); - return; - } - if (value == null && Hands.Count != 0) - { - Logger.Error($"{nameof(SharedHandsComponent)} on {Owner} tried to set its active hand to null, when it still had another hand."); - _activeHand = Hands[0].Name; - return; - } - if (value != ActiveHand) - { - DeselectActiveHeldEntity(); - _activeHand = value; - SelectActiveHeldEntity(); - - HandsModified(); - } - } - } - - private string? _activeHand; + [ViewVariables] + public string? ActiveHand; [ViewVariables] - public readonly List Hands = new(); + public List Hands = new(); /// /// The amount of throw impulse per distance the player is from the throw target. @@ -79,66 +50,25 @@ namespace Content.Shared.Hands.Components [ViewVariables(VVAccess.ReadWrite)] public float ThrowRange { get; set; } = 8f; - public override ComponentState GetComponentState() - { - var hands = new HandState[Hands.Count]; - - for (var i = 0; i < Hands.Count; i++) - { - var hand = Hands[i].ToHandState(); - hands[i] = hand; - } - return new HandsComponentState(hands, ActiveHand); - } - - public virtual void HandsModified() - { - // todo axe all this for ECS. - // todo burn it all down. - UpdateHandVisualizer(); - Dirty(); - - _entMan.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this }); - } - - public void UpdateHandVisualizer() - { - var entMan = _entMan; - - if (!entMan.TryGetComponent(Owner, out AppearanceComponent? appearance)) - return; - - var hands = new List(); - foreach (var hand in Hands) - { - if (hand.HeldEntity == null) - continue; - - if (!entMan.TryGetComponent(hand.HeldEntity, out SharedItemComponent? item) || item.RsiPath == null) - continue; - - var handState = new HandVisualState(item.RsiPath, item.EquippedPrefix, hand.Location, item.Color); - hands.Add(handState); - } - - appearance.SetData(HandsVisuals.VisualState, new HandsVisualState(hands)); - } + private bool PlayerCanDrop => EntitySystem.Get().CanDrop(Owner); + private bool PlayerCanPickup => EntitySystem.Get().CanPickup(Owner); public void AddHand(string handName, HandLocation handLocation) { if (HasHand(handName)) return; - var container = Owner.CreateContainer(handName); + var container = Owner.EnsureContainer(handName); container.OccludesLight = false; Hands.Add(new Hand(handName, handLocation, container)); - ActiveHand ??= handName; + if (ActiveHand == null) + EntitySystem.Get().TrySetActiveHand(Owner, handName, this); HandCountChanged(); - HandsModified(); + Dirty(); } public void RemoveHand(string handName) @@ -156,14 +86,14 @@ namespace Content.Shared.Hands.Components Hands.Remove(hand); if (ActiveHand == hand.Name) - ActiveHand = Hands.FirstOrDefault()?.Name; + EntitySystem.Get().TrySetActiveHand(Owner, Hands.FirstOrDefault()?.Name, this); HandCountChanged(); - HandsModified(); + Dirty(); } - private Hand? GetActiveHand() + public Hand? GetActiveHand() { if (ActiveHand == null) return null; @@ -220,7 +150,7 @@ namespace Content.Shared.Hands.Components return hand.HeldEntity != null; } - public bool TryGetHeldEntity(string handName,[NotNullWhen(true)] out EntityUid? heldEntity) + public bool TryGetHeldEntity(string handName, [NotNullWhen(true)] out EntityUid? heldEntity) { heldEntity = null; @@ -228,7 +158,7 @@ namespace Content.Shared.Hands.Components return false; heldEntity = hand.HeldEntity; - return hand.HeldEntity != null; + return heldEntity != null; } public bool TryGetActiveHeldEntity([NotNullWhen(true)] out EntityUid? heldEntity) @@ -251,7 +181,7 @@ namespace Content.Shared.Hands.Components { foreach (var hand in Hands) { - if (hand.HeldEntity.HasValue) + if (hand.HeldEntity != null) yield return hand.HeldEntity.Value; } } @@ -302,7 +232,7 @@ namespace Content.Shared.Hands.Components if (!CanRemoveHeldEntityFromHand(hand)) return false; - if (checkActionBlocker && !PlayerCanDrop()) + if (checkActionBlocker && !PlayerCanDrop) return false; return true; @@ -406,7 +336,7 @@ namespace Content.Shared.Hands.Components if (!CanRemoveHeldEntityFromHand(hand)) return false; - RemoveHeldEntityFromHand(hand); + EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); return true; } @@ -418,62 +348,27 @@ namespace Content.Shared.Hands.Components if (hand.HeldEntity == null) return false; - - return hand.Container?.CanRemove(hand.HeldEntity.Value) ?? false; - } - - /// - /// Checks if the player is allowed to perform drops. - /// - private bool PlayerCanDrop() - { - if (!IoCManager.Resolve().GetEntitySystem().CanDrop(Owner)) + if (!hand.Container!.CanRemove(hand.HeldEntity.Value)) return false; return true; } - /// - /// Removes the contents of a hand from its container. Assumes that the removal is allowed. - /// - private void RemoveHeldEntityFromHand(Hand hand) - { - if (hand.HeldEntity is not { } heldEntity) - return; - - var handContainer = hand.Container; - if (handContainer == null) - return; - - if (hand.Name == ActiveHand) - DeselectActiveHeldEntity(); - - if (!handContainer.Remove(heldEntity)) - { - Logger.Error($"{nameof(SharedHandsComponent)} on {Owner} could not remove {heldEntity} from {handContainer}."); - return; - } - - OnHeldEntityRemovedFromHand(heldEntity, hand.ToHandState()); - - HandsModified(); - } - /// /// Drops a hands contents to the target location. /// public void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation) { - if (hand.HeldEntity is not { } heldEntity) + if (hand.HeldEntity == null) return; - RemoveHeldEntityFromHand(hand); + var heldEntity = hand.HeldEntity.Value; + + EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); EntitySystem.Get().DroppedInteraction(Owner, heldEntity); _entMan.GetComponent(heldEntity).WorldPosition = GetFinalDropCoordinates(targetDropLocation); - - OnItemChanged?.Invoke(); } /// @@ -508,7 +403,7 @@ namespace Content.Shared.Hands.Components if (!CanRemoveHeldEntityFromHand(hand)) return false; - if (checkActionBlocker && !PlayerCanDrop()) + if (checkActionBlocker && !PlayerCanDrop) return false; DropHeldEntity(hand, location); @@ -525,10 +420,12 @@ namespace Content.Shared.Hands.Components private bool CanPutHeldEntityIntoContainer(Hand hand, IContainer targetContainer, bool checkActionBlocker) { - if (hand.HeldEntity is not { } heldEntity) + if (hand.HeldEntity == null) return false; - if (checkActionBlocker && !PlayerCanDrop()) + var heldEntity = hand.HeldEntity.Value; + + if (checkActionBlocker && !PlayerCanDrop) return false; if (!targetContainer.CanInsert(heldEntity)) @@ -542,10 +439,12 @@ namespace Content.Shared.Hands.Components /// private void PutHeldEntityIntoContainer(Hand hand, IContainer targetContainer) { - if (hand.HeldEntity is not { } heldEntity) + if (hand.HeldEntity == null) return; - RemoveHeldEntityFromHand(hand); + var heldEntity = hand.HeldEntity.Value; + + EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); if (!targetContainer.Insert(heldEntity)) { @@ -563,7 +462,7 @@ namespace Content.Shared.Hands.Components if (!TryGetHand(handName, out var hand)) return false; - if (checkActionBlocker && !PlayerCanPickup()) + if (checkActionBlocker && !PlayerCanPickup) return false; if (!CanInsertEntityIntoHand(hand, entity)) @@ -580,17 +479,17 @@ namespace Content.Shared.Hands.Components /// /// Tries to pick up an entity to a specific hand. /// - public bool TryPickupEntity(string handName, EntityUid entity, bool checkActionBlocker = true) + public bool TryPickupEntity(string handName, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false) { if (!TryGetHand(handName, out var hand)) return false; - return TryPickupEntity(hand, entity, checkActionBlocker); + return TryPickupEntity(hand, entity, checkActionBlocker, animateUser); } - public bool TryPickupEntityToActiveHand(EntityUid entity, bool checkActionBlocker = true) + public bool TryPickupEntityToActiveHand(EntityUid entity, bool checkActionBlocker = true, bool animateUser = false) { - return ActiveHand != null && TryPickupEntity(ActiveHand, entity, checkActionBlocker); + return ActiveHand != null && TryPickupEntity(ActiveHand, entity, checkActionBlocker, animateUser); } /// @@ -598,6 +497,9 @@ namespace Content.Shared.Hands.Components /// protected bool CanInsertEntityIntoHand(Hand hand, EntityUid entity) { + if (!_entMan.HasComponent(entity)) + return false; + var handContainer = hand.Container; if (handContainer == null) return false; @@ -611,56 +513,23 @@ namespace Content.Shared.Hands.Components return true; } - /// - /// Checks if the player is allowed to perform pickup actions. - /// - /// - protected bool PlayerCanPickup() - { - if (!EntitySystem.Get().CanPickup(Owner)) - return false; - - return true; - } - - /// - /// Puts an entity into the player's hand, assumes that the insertion is allowed. - /// - public void PutEntityIntoHand(Hand hand, EntityUid entity) - { - var handContainer = hand.Container; - if (handContainer == null) - return; - - if (!handContainer.Insert(entity)) - { - Logger.Error($"{nameof(SharedHandsComponent)} on {Owner} could not insert {entity} into {handContainer}."); - return; - } - - EntitySystem.Get().EquippedHandInteraction(Owner, entity, hand.ToHandState()); - - if (hand.Name == ActiveHand) - SelectActiveHeldEntity(); - - _entMan.GetComponent(entity).LocalPosition = Vector2.Zero; - - OnItemChanged?.Invoke(); - - HandsModified(); - } - - private bool TryPickupEntity(Hand hand, EntityUid entity, bool checkActionBlocker = true) + private bool TryPickupEntity(Hand hand, EntityUid entity, bool checkActionBlocker = true, bool animateUser = false) { if (!CanInsertEntityIntoHand(hand, entity)) return false; - if (checkActionBlocker && !PlayerCanPickup()) + if (checkActionBlocker && !PlayerCanPickup) return false; - HandlePickupAnimation(entity); - PutEntityIntoHand(hand, entity); - EntitySystem.Get().Add(LogType.Pickup, LogImpact.Low, $"{_entMan.ToPrettyString(Owner):user} picked up {_entMan.ToPrettyString(entity):entity}"); + // animation + var handSys = EntitySystem.Get(); + var coordinateEntity = _entMan.GetComponent(Owner).Parent?.Owner ?? Owner; + var initialPosition = EntityCoordinates.FromMap(coordinateEntity, _entMan.GetComponent(entity).MapPosition); + var finalPosition = _entMan.GetComponent(Owner).LocalPosition; + + handSys.PickupAnimation(entity, initialPosition, finalPosition, animateUser ? null : Owner); + handSys.PutEntityIntoHand(Owner, hand, entity, this); + return true; } @@ -740,28 +609,16 @@ namespace Content.Shared.Hands.Components if (!CanInsertEntityIntoHand(activeHand, heldEntity.Value) || !CanRemoveHeldEntityFromHand(hand)) return false; - if (checkActionBlocker && (!PlayerCanDrop() || !PlayerCanPickup())) + if (checkActionBlocker && (!PlayerCanDrop || !PlayerCanPickup)) return false; - RemoveHeldEntityFromHand(hand); - PutEntityIntoHand(activeHand, heldEntity.Value); + EntitySystem.Get().RemoveHeldEntityFromHand(Owner, hand, this); + EntitySystem.Get().PutEntityIntoHand(Owner, activeHand, heldEntity.Value, this); return true; } #endregion - private void DeselectActiveHeldEntity() - { - if (TryGetActiveHeldEntity(out var entity)) - EntitySystem.Get().HandDeselectedInteraction(Owner, entity.Value); - } - - private void SelectActiveHeldEntity() - { - if (TryGetActiveHeldEntity(out var entity)) - EntitySystem.Get().HandSelectedInteraction(Owner, entity.Value); - } - private void HandCountChanged() { _entMan.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner)); @@ -772,36 +629,30 @@ namespace Content.Shared.Hands.Components /// public bool PutInHand(SharedItemComponent item, bool checkActionBlocker = true) { - return TryPutInActiveHandOrAny(item.Owner, checkActionBlocker); - } - - /// - /// Tries to pick up an entity into the active hand. If it cannot, tries to pick up the entity into each other hand. - /// - public bool PutInHand(EntityUid uid, bool checkActionBlocker = true) - { - return TryPutInActiveHandOrAny(uid, checkActionBlocker); + return PutInHand(item.Owner, checkActionBlocker); } /// /// Puts an item any hand, prefering the active hand, or puts it on the floor under the player. /// - public void PutInHandOrDrop(SharedItemComponent item, bool checkActionBlocker = true) => - PutInHandOrDrop(item.Owner, checkActionBlocker); - - public void PutInHandOrDrop(EntityUid uid, bool checkActionBlocker = true) + public void PutInHandOrDrop(EntityUid entity, bool checkActionBlocker = true) { - if (!TryPutInActiveHandOrAny(uid, checkActionBlocker)) - _entMan.GetComponent(uid).Coordinates = _entMan.GetComponent(Owner).Coordinates; - + if (!PutInHand(entity, checkActionBlocker)) + _entMan.GetComponent(entity).Coordinates = _entMan.GetComponent(Owner).Coordinates; } + public void PutInHandOrDrop(SharedItemComponent item, bool checkActionBlocker = true) + { + PutInHandOrDrop(item.Owner, checkActionBlocker); + } + + /// /// Tries to pick up an entity into the active hand. If it cannot, tries to pick up the entity into each other hand. /// - public bool TryPutInActiveHandOrAny(EntityUid entity, bool checkActionBlocker = true) + public bool PutInHand(EntityUid entity, bool checkActionBlocker = true) { - return TryPutInAnyHand(entity, GetActiveHand(), checkActionBlocker); + return PutInHand(entity, GetActiveHand(), checkActionBlocker); } /// @@ -814,13 +665,13 @@ namespace Content.Shared.Hands.Components if (priorityHandName != null) priorityHand = GetHandOrNull(priorityHandName); - return TryPutInAnyHand(entity, priorityHand, checkActionBlocker); + return PutInHand(entity, priorityHand, checkActionBlocker); } /// /// Tries to pick up an entity into the priority hand, if provided. If it cannot, tries to pick up the entity into each other hand. /// - private bool TryPutInAnyHand(EntityUid entity, Hand? priorityHand = null, bool checkActionBlocker = true) + private bool PutInHand(EntityUid entity, Hand? priorityHand = null, bool checkActionBlocker = true) { if (priorityHand != null) { @@ -836,9 +687,23 @@ namespace Content.Shared.Hands.Components return false; } - protected virtual void OnHeldEntityRemovedFromHand(EntityUid heldEntity, HandState handState) { } + /// + /// Checks if any hand can pick up an item. + /// + public bool CanPutInHand(SharedItemComponent item, bool mobCheck = true) + { + var entity = item.Owner; - protected virtual void HandlePickupAnimation(EntityUid entity) { } + if (mobCheck && !PlayerCanPickup) + return false; + + foreach (var hand in Hands) + { + if (CanInsertEntityIntoHand(hand, entity)) + return true; + } + return false; + } } #region visualizerData @@ -877,6 +742,7 @@ namespace Content.Shared.Hands.Components } #endregion + [Serializable, NetSerializable] public class Hand { [ViewVariables] @@ -889,46 +755,29 @@ namespace Content.Shared.Hands.Components /// 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. /// - [ViewVariables] - public IContainer? Container { get; set; } + [ViewVariables, NonSerialized] + public ContainerSlot? Container; [ViewVariables] - public EntityUid? HeldEntity => Container?.ContainedEntities?.Count > 0 ? Container.ContainedEntities[0] : null; + public EntityUid? HeldEntity => Container?.ContainedEntity; + public bool IsEmpty => HeldEntity == null; - public Hand(string name, HandLocation location, IContainer? container = null) + public Hand(string name, HandLocation location, ContainerSlot? container = null) { Name = name; Location = location; Container = container; } - - public HandState ToHandState() - { - return new(Name, Location); - } - } - - [Serializable, NetSerializable] - public struct HandState - { - public string Name { get; } - public HandLocation Location { get; } - - public HandState(string name, HandLocation location) - { - Name = name; - Location = location; - } } [Serializable, NetSerializable] public sealed class HandsComponentState : ComponentState { - public HandState[] Hands { get; } + public List Hands { get; } public string? ActiveHand { get; } - public HandsComponentState(HandState[] hands, string? activeHand = null) + public HandsComponentState(List hands, string? activeHand = null) { Hands = hands; ActiveHand = activeHand; @@ -1004,25 +853,4 @@ namespace Content.Shared.Hands.Components public EntityUid Sender { get; } } - - [Serializable, NetSerializable] - public class PickupAnimationMessage : EntityEventArgs - { - public EntityUid EntityUid { get; } - public EntityCoordinates InitialPosition { get; } - public Vector2 FinalPosition { get; } - - public PickupAnimationMessage(EntityUid entityUid, Vector2 finalPosition, EntityCoordinates initialPosition) - { - EntityUid = entityUid; - FinalPosition = finalPosition; - InitialPosition = initialPosition; - } - } - - [Serializable, NetSerializable] - public struct HandsModifiedMessage - { - public SharedHandsComponent Hands; - } } diff --git a/Content.Shared/Hands/HandEvents.cs b/Content.Shared/Hands/HandEvents.cs new file mode 100644 index 0000000000..a2c727cf47 --- /dev/null +++ b/Content.Shared/Hands/HandEvents.cs @@ -0,0 +1,160 @@ +using System; +using Content.Shared.Hands.Components; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; + +namespace Content.Shared.Hands +{ + /// + /// Raised when an entity item in a hand is deselected. + /// + [PublicAPI] + public class HandDeselectedEvent : HandledEntityEventArgs + { + /// + /// Entity that owns the deselected hand. + /// + public EntityUid User { get; } + + /// + /// Item in the hand that was deselected. + /// + public EntityUid Item { get; } + + public HandDeselectedEvent(EntityUid user, EntityUid item) + { + User = user; + Item = item; + } + } + + /// + /// Raised when an item entity held by a hand is selected. + /// + [PublicAPI] + public class HandSelectedEvent : HandledEntityEventArgs + { + /// + /// Entity that owns the selected hand. + /// + public EntityUid User { get; } + + /// + /// Item in the hand that was selected. + /// + public EntityUid Item { get; } + + public HandSelectedEvent(EntityUid user, EntityUid item) + { + User = user; + Item = item; + } + } + + [Serializable, NetSerializable] + public class RequestSetHandEvent : EntityEventArgs + { + /// + /// The hand to be swapped to. + /// + public string HandName { get; } + + public RequestSetHandEvent(string handName) + { + HandName = handName; + } + } + + [Serializable, NetSerializable] + public class PickupAnimationEvent : EntityEventArgs + { + public EntityUid ItemUid { get; } + public EntityCoordinates InitialPosition { get; } + public Vector2 FinalPosition { get; } + + public PickupAnimationEvent(EntityUid itemUid, EntityCoordinates initialPosition, + Vector2 finalPosition) + { + ItemUid = itemUid; + FinalPosition = finalPosition; + InitialPosition = initialPosition; + } + } + + /// + /// Raised directed on both the blocking entity and user when + /// a virtual hand item is deleted. + /// + public class VirtualItemDeletedEvent : EntityEventArgs + { + public EntityUid BlockingEntity; + public EntityUid User; + + public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user) + { + BlockingEntity = blockingEntity; + User = user; + } + } + + /// + /// Raised when putting an entity into a hand slot + /// + [PublicAPI] + public class EquippedHandEvent : HandledEntityEventArgs + { + /// + /// Entity that equipped the item. + /// + public EntityUid User { get; } + + /// + /// Item that was equipped. + /// + public EntityUid Equipped { get; } + + /// + /// Hand that the item was placed into. + /// + public Hand Hand { get; } + + public EquippedHandEvent(EntityUid user, EntityUid equipped, Hand hand) + { + User = user; + Equipped = equipped; + Hand = hand; + } + } + + /// + /// Raised when removing an entity from an inventory slot. + /// + [PublicAPI] + public class UnequippedHandEvent : HandledEntityEventArgs + { + /// + /// Entity that equipped the item. + /// + public EntityUid User { get; } + + /// + /// Item that was unequipped. + /// + public EntityUid Unequipped { get; } + + /// + /// Hand that the item is removed from. + /// + public Hand Hand { get; } + + public UnequippedHandEvent(EntityUid user, EntityUid unequipped, Hand hand) + { + User = user; + Unequipped = unequipped; + Hand = hand; + } + } +} diff --git a/Content.Shared/Hands/IEquippedHand.cs b/Content.Shared/Hands/IEquippedHand.cs deleted file mode 100644 index d13bd3f815..0000000000 --- a/Content.Shared/Hands/IEquippedHand.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using Content.Shared.Hands.Components; -using Content.Shared.Inventory; -using JetBrains.Annotations; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; - -namespace Content.Shared.Hands -{ - /// - /// This interface gives components behavior when their entity is put in a hand inventory slot, - /// even if it came from another hand slot (which would also fire ). - /// This includes moving the entity from a non-hand slot into a hand slot - /// (which would also fire ). - /// - [RequiresExplicitImplementation] - public interface IEquippedHand - { - [Obsolete("Use EquippedHandMessage instead")] - void EquippedHand(EquippedHandEventArgs eventArgs); - } - - public class EquippedHandEventArgs : EntityEventArgs - { - public EquippedHandEventArgs(EntityUid user, HandState hand) - { - Hand = hand; - User = user; - } - - public readonly HandState Hand; - public readonly EntityUid User; - } - - /// - /// Raised when putting an entity into a hand slot - /// - [PublicAPI] - public class EquippedHandEvent : HandledEntityEventArgs - { - /// - /// Entity that equipped the item. - /// - public EntityUid User { get; } - - /// - /// Item that was equipped. - /// - public EntityUid Equipped { get; } - - /// - /// Hand that the item was placed into. - /// - public HandState Hand { get; } - - public EquippedHandEvent(EntityUid user, EntityUid equipped, HandState hand) - { - User = user; - Equipped = equipped; - Hand = hand; - } - } -} diff --git a/Content.Shared/Hands/IHandDeselected.cs b/Content.Shared/Hands/IHandDeselected.cs deleted file mode 100644 index a038569c18..0000000000 --- a/Content.Shared/Hands/IHandDeselected.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using JetBrains.Annotations; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; - -namespace Content.Shared.Hands -{ - /// - /// This interface gives components behavior when they're held on a deselected hand. - /// - [RequiresExplicitImplementation] - public interface IHandDeselected - { - [Obsolete("Use HandDeselectedMessage instead")] - void HandDeselected(HandDeselectedEventArgs eventArgs); - } - - public class HandDeselectedEventArgs : EventArgs - { - public HandDeselectedEventArgs(EntityUid user) - { - User = user; - } - - public EntityUid User { get; } - } - - /// - /// Raised when an entity item in a hand is deselected. - /// - [PublicAPI] - public class HandDeselectedEvent : HandledEntityEventArgs - { - /// - /// Entity that owns the deselected hand. - /// - public EntityUid User { get; } - - /// - /// Item in the hand that was deselected. - /// - public EntityUid Item { get; } - - public HandDeselectedEvent(EntityUid user, EntityUid item) - { - User = user; - Item = item; - } - } -} diff --git a/Content.Shared/Hands/IHandSelected.cs b/Content.Shared/Hands/IHandSelected.cs deleted file mode 100644 index f26e846cc4..0000000000 --- a/Content.Shared/Hands/IHandSelected.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using JetBrains.Annotations; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; - -namespace Content.Shared.Hands -{ - /// - /// This interface gives components behavior when they're held on the selected hand. - /// - [RequiresExplicitImplementation] - public interface IHandSelected - { - [Obsolete("Use HandSelectedMessage instead")] - void HandSelected(HandSelectedEventArgs eventArgs); - } - - public class HandSelectedEventArgs : EventArgs - { - public HandSelectedEventArgs(EntityUid user) - { - User = user; - } - - public EntityUid User { get; } - } - - /// - /// Raised when an item entity held by a hand is selected. - /// - [PublicAPI] - public class HandSelectedEvent : HandledEntityEventArgs - { - /// - /// Entity that owns the selected hand. - /// - public EntityUid User { get; } - - /// - /// Item in the hand that was selected. - /// - public EntityUid Item { get; } - - public HandSelectedEvent(EntityUid user, EntityUid item) - { - User = user; - Item = item; - } - } -} diff --git a/Content.Shared/Hands/IUnequippedHand.cs b/Content.Shared/Hands/IUnequippedHand.cs deleted file mode 100644 index 89e554fce2..0000000000 --- a/Content.Shared/Hands/IUnequippedHand.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Content.Shared.Hands.Components; -using Content.Shared.Inventory; -using JetBrains.Annotations; -using Robust.Shared.Analyzers; -using Robust.Shared.GameObjects; - -namespace Content.Shared.Hands -{ - /// - /// This interface gives components behavior when their entity is removed from a hand slot, - /// even if it is going into another hand slot (which would also fire ). - /// This includes moving the entity from a hand slot into a non-hand slot (which would also fire ). - /// - [RequiresExplicitImplementation] - public interface IUnequippedHand - { - [Obsolete("Use UnequippedHandMessage instead")] - void UnequippedHand(UnequippedHandEventArgs eventArgs); - } - - public class UnequippedHandEventArgs : EntityEventArgs - { - public UnequippedHandEventArgs(EntityUid user, HandState hand) - { - Hand = hand; - User = user; - } - - public readonly HandState Hand; - public readonly EntityUid User; - } - - /// - /// Raised when removing an entity from an inventory slot. - /// - [PublicAPI] - public class UnequippedHandEvent : HandledEntityEventArgs - { - /// - /// Entity that equipped the item. - /// - public EntityUid User { get; } - - /// - /// Item that was unequipped. - /// - public EntityUid Unequipped { get; } - - /// - /// Hand that the item is removed from. - /// - public HandState Hand { get; } - - public UnequippedHandEvent(EntityUid user, EntityUid unequipped, HandState hand) - { - User = user; - Unequipped = unequipped; - Hand = hand; - } - } -} diff --git a/Content.Shared/Hands/SharedHandsSystem.cs b/Content.Shared/Hands/SharedHandsSystem.cs index 11dfda05ed..e1c7831a2a 100644 --- a/Content.Shared/Hands/SharedHandsSystem.cs +++ b/Content.Shared/Hands/SharedHandsSystem.cs @@ -1,69 +1,213 @@ -using System; +using Content.Shared.Administration.Logs; +using Content.Shared.Database; using Content.Shared.Hands.Components; +using Content.Shared.Input; +using Content.Shared.Item; using Robust.Shared.Containers; using Robust.Shared.GameObjects; -using Robust.Shared.Serialization; +using Robust.Shared.Input.Binding; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Players; +using System.Collections.Generic; namespace Content.Shared.Hands { public abstract class SharedHandsSystem : EntitySystem { + [Dependency] private readonly SharedAdminLogSystem _adminLogSystem = default!; + public override void Initialize() { base.Initialize(); + SubscribeAllEvent(HandleSetHand); + SubscribeLocalEvent(HandleContainerModified); SubscribeLocalEvent(HandleContainerModified); - SubscribeAllEvent(HandleSetHand); + CommandBinds.Builder + .Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(DropPressed)) + .Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(SwapHandsPressed, handle: false)) + .Register(); } + public override void Shutdown() + { + base.Shutdown(); + CommandBinds.Unregister(); + } + + #region interactions + private void SwapHandsPressed(ICommonSession? session) + { + if (!TryComp(session?.AttachedEntity, out SharedHandsComponent? hands)) + return; + + if (!hands.TryGetSwapHandsResult(out var nextHand)) + return; + + TrySetActiveHand(hands.Owner, nextHand, hands); + } + + private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid uid) + { + if (TryComp(session?.AttachedEntity, out SharedHandsComponent? hands)) + hands.TryDropActiveHand(coords); + + return false; + } + #endregion + + #region EntityInsertRemove + /// + /// Removes the contents of a hand from its container. Assumes that the removal is allowed. + /// + public virtual void RemoveHeldEntityFromHand(EntityUid uid, Hand hand, SharedHandsComponent? hands = null) + { + if (!Resolve(uid, ref hands)) + return; + + if (hand.Container?.ContainedEntity == null) + return; + + var entity = hand.Container.ContainedEntity.Value; + + if (!hand.Container!.Remove(entity)) + { + Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not remove {entity} from {hand.Container}."); + return; + } + + if (TryComp(entity, out SharedSpriteComponent? component)) + component.Visible = true; + + hands.Dirty(); + + var unequippedHandMessage = new UnequippedHandEvent(uid, entity, hand); + RaiseLocalEvent(entity, unequippedHandMessage); + if (unequippedHandMessage.Handled) + return; + + if (hand.Name == hands.ActiveHand) + RaiseLocalEvent(entity, new HandDeselectedEvent(uid, entity), false); + } + + /// + /// Puts an entity into the player's hand, assumes that the insertion is allowed. + /// + public virtual void PutEntityIntoHand(EntityUid uid, Hand hand, EntityUid entity, SharedHandsComponent? hands = null) + { + if (!Resolve(uid, ref hands)) + return; + + var handContainer = hand.Container; + if (handContainer == null || handContainer.ContainedEntity != null) + return; + + if (!handContainer.Insert(entity)) + { + Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not insert {entity} into {handContainer}."); + return; + } + + _adminLogSystem.Add(LogType.Pickup, LogImpact.Low, $"{ToPrettyString(uid):user} picked up {ToPrettyString(entity):entity}"); + + if (TryComp(entity, out SharedSpriteComponent? component)) + component.Visible = false; + + hands.Dirty(); + + var equippedHandMessage = new EquippedHandEvent(uid, entity, hand); + RaiseLocalEvent(entity, equippedHandMessage); + + // If one of the interactions resulted in the item being dropped, return early. + if (equippedHandMessage.Handled) + return; + + if (hand.Name == hands.ActiveHand) + RaiseLocalEvent(entity, new HandSelectedEvent(uid, entity), false); + } + + public abstract void PickupAnimation(EntityUid item, EntityCoordinates initialPosition, Vector2 finalPosition, + EntityUid? exclude); + #endregion + + #region visuals + protected virtual void HandleContainerModified(EntityUid uid, SharedHandsComponent hands, ContainerModifiedMessage args) + { + UpdateHandVisualizer(uid, hands); + } + + /// + /// Update the In-Hand sprites + /// + public void UpdateHandVisualizer(EntityUid uid, SharedHandsComponent? handComp = null, AppearanceComponent? appearance = null) + { + if (!Resolve(uid, ref handComp, ref appearance, false)) + return; + + var handsVisuals = new List(); + foreach (var hand in handComp.Hands) + { + if (hand.HeldEntity == null) + continue; + + if (!TryComp(hand.HeldEntity.Value, out SharedItemComponent? item) || item.RsiPath == null) + continue; + + var handState = new HandVisualState(item.RsiPath, item.EquippedPrefix, hand.Location, item.Color); + handsVisuals.Add(handState); + } + + appearance.SetData(HandsVisuals.VisualState, new HandsVisualState(handsVisuals)); + } + #endregion + private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs) { - var entity = eventArgs.SenderSession.AttachedEntity; - - if (entity == null || !EntityManager.TryGetComponent(entity, out SharedHandsComponent? hands)) + if (eventArgs.SenderSession.AttachedEntity == null) return; - hands.ActiveHand = msg.HandName; + TrySetActiveHand(eventArgs.SenderSession.AttachedEntity.Value, msg.HandName); } - protected virtual void HandleContainerModified( - EntityUid uid, - SharedHandsComponent component, - ContainerModifiedMessage args) - { - component.Dirty(); - } - } - - [Serializable, NetSerializable] - public class RequestSetHandEvent : EntityEventArgs - { /// - /// The hand to be swapped to. + /// Set the currently active hand and raise hand (de)selection events directed at the held entities. /// - public string HandName { get; } - - public RequestSetHandEvent(string handName) + /// True if the active hand was set to a NEW value. Setting it to the same value returns false and does + /// not trigger interactions. + public virtual bool TrySetActiveHand(EntityUid uid, string? value, SharedHandsComponent? handComp = null) { - HandName = handName; - } - } + if (!Resolve(uid, ref handComp)) + return false; - /// - /// Raised directed on both the blocking entity and user when - /// a virtual hand item is deleted. - /// - public class VirtualItemDeletedEvent : EntityEventArgs - { - public EntityUid BlockingEntity; - public EntityUid User; + if (value == handComp.ActiveHand) + return false; - public VirtualItemDeletedEvent(EntityUid blockingEntity, EntityUid user) - { - BlockingEntity = blockingEntity; - User = user; + if (value != null && !handComp.HasHand(value)) + { + Logger.Warning($"{nameof(SharedHandsComponent)} on {handComp.Owner} tried to set its active hand to {value}, which was not a hand."); + return false; + } + if (value == null && handComp.Hands.Count != 0) + { + Logger.Error($"{nameof(SharedHandsComponent)} on {handComp.Owner} tried to set its active hand to null, when it still had another hand."); + return false; + } + + if (handComp.TryGetActiveHeldEntity(out var entity)) + RaiseLocalEvent(entity.Value, new HandDeselectedEvent(uid, entity.Value), false); + + handComp.ActiveHand = value; + + if (handComp.TryGetActiveHeldEntity(out entity)) + RaiseLocalEvent(entity.Value, new HandSelectedEvent(uid, entity.Value), false); + + handComp.Dirty(); + return true; } } } diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index c431a8711b..67212bcb5b 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -139,7 +139,6 @@ namespace Content.Shared.Interaction if (!TryComp(user, out SharedHandsComponent? hands)) return; - // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something. var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true); if (target == null || !inRangeUnobstructed) @@ -734,46 +733,6 @@ namespace Content.Shared.Interaction } #endregion - #region Equip Hand - /// - /// Calls EquippedHand on all components that implement the IEquippedHand interface - /// on an item. - /// - public void EquippedHandInteraction(EntityUid user, EntityUid item, HandState hand) - { - var equippedHandMessage = new EquippedHandEvent(user, item, hand); - RaiseLocalEvent(item, equippedHandMessage); - if (equippedHandMessage.Handled) - return; - - var comps = AllComps(item).ToList(); - - foreach (var comp in comps) - { - comp.EquippedHand(new EquippedHandEventArgs(user, hand)); - } - } - - /// - /// Calls UnequippedHand on all components that implement the IUnequippedHand interface - /// on an item. - /// - public void UnequippedHandInteraction(EntityUid user, EntityUid item, HandState hand) - { - var unequippedHandMessage = new UnequippedHandEvent(user, item, hand); - RaiseLocalEvent(item, unequippedHandMessage); - if (unequippedHandMessage.Handled) - return; - - var comps = AllComps(item).ToList(); - - foreach (var comp in comps) - { - comp.UnequippedHand(new UnequippedHandEventArgs(user, hand)); - } - } - #endregion - #region Drop /// /// Activates the Dropped behavior of an object @@ -813,47 +772,6 @@ namespace Content.Shared.Interaction _adminLogSystem.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(user):user} dropped {ToPrettyString(item):entity}"); } #endregion - - #region Hand Selected - /// - /// Calls HandSelected on all components that implement the IHandSelected interface - /// on an item entity on a hand that has just been selected. - /// - public void HandSelectedInteraction(EntityUid user, EntityUid item) - { - var handSelectedMsg = new HandSelectedEvent(user, item); - RaiseLocalEvent(item, handSelectedMsg); - if (handSelectedMsg.Handled) - return; - - var comps = AllComps(item).ToList(); - - // Call Land on all components that implement the interface - foreach (var comp in comps) - { - comp.HandSelected(new HandSelectedEventArgs(user)); - } - } - - /// - /// Calls HandDeselected on all components that implement the IHandDeselected interface - /// on an item entity on a hand that has just been deselected. - /// - public void HandDeselectedInteraction(EntityUid user, EntityUid item) - { - var handDeselectedMsg = new HandDeselectedEvent(user, item); - RaiseLocalEvent(item, handDeselectedMsg); - if (handDeselectedMsg.Handled) - return; - - var comps = AllComps(item).ToList(); - - // Call Land on all components that implement the interface - foreach (var comp in comps) - { - comp.HandDeselected(new HandDeselectedEventArgs(user)); - } - } #endregion /// @@ -891,8 +809,6 @@ namespace Content.Shared.Interaction return true; } - - #endregion } /// diff --git a/Content.Shared/Item/ItemSystem.cs b/Content.Shared/Item/ItemSystem.cs index 7e7b6ebc96..a712a62a60 100644 --- a/Content.Shared/Item/ItemSystem.cs +++ b/Content.Shared/Item/ItemSystem.cs @@ -57,7 +57,7 @@ namespace Content.Shared.Item return; Verb verb = new(); - verb.Act = () => args.Hands.TryPutInActiveHandOrAny(args.Target); + verb.Act = () => args.Hands.PutInHand(args.Target); verb.IconTexture = "/Textures/Interface/VerbIcons/pickup.svg.192dpi.png"; // if the item already in a container (that is not the same as the user's), then change the text. diff --git a/Content.Shared/Item/SharedItemComponent.cs b/Content.Shared/Item/SharedItemComponent.cs index 18ffef708a..6b330f932b 100644 --- a/Content.Shared/Item/SharedItemComponent.cs +++ b/Content.Shared/Item/SharedItemComponent.cs @@ -1,10 +1,12 @@ using System; using Content.Shared.ActionBlocker; +using Content.Shared.Hands; using Content.Shared.Hands.Components; using Content.Shared.Interaction; using Content.Shared.Interaction.Helpers; using Content.Shared.Inventory; using Content.Shared.Sound; +using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; @@ -123,7 +125,7 @@ namespace Content.Shared.Item if (!CanPickup(user)) return false; - if (!_entMan.TryGetComponent(user, out SharedHandsComponent? hands)) + if (!_entMan.TryGetComponent(user, out SharedHandsComponent hands)) return false; var activeHand = hands.ActiveHand; @@ -131,15 +133,27 @@ namespace Content.Shared.Item if (activeHand == null) return false; - hands.TryPickupEntityToActiveHand(Owner); + hands.TryPickupEntityToActiveHand(Owner, animateUser: true); return true; } - protected virtual void OnEquippedPrefixChange() { } + private void OnEquippedPrefixChange() + { + if (Owner.TryGetContainer(out var container)) + EntitySystem.Get().UpdateHandVisualizer(container.Owner); + } - public virtual void RemovedFromSlot() { } + public void RemovedFromSlot() + { + if (_entMan.TryGetComponent(Owner, out SharedSpriteComponent component)) + component.Visible = true; + } - public virtual void EquippedToSlot() { } + public virtual void EquippedToSlot() + { + if (_entMan.TryGetComponent(Owner, out SharedSpriteComponent component)) + component.Visible = false; + } } [Serializable, NetSerializable] diff --git a/Content.Shared/Verbs/SharedVerbSystem.cs b/Content.Shared/Verbs/SharedVerbSystem.cs index fcc1aea26b..b8c172433a 100644 --- a/Content.Shared/Verbs/SharedVerbSystem.cs +++ b/Content.Shared/Verbs/SharedVerbSystem.cs @@ -67,14 +67,13 @@ namespace Content.Shared.Verbs EntityUid? @using = null; if (TryComp(user, out SharedHandsComponent? hands) && (force || _actionBlockerSystem.CanUse(user))) { - // TODO Hands remove nullable (#5634) - hands.TryGetActiveHeldEntity(out var nonNullableUsing); - @using = nonNullableUsing; + hands.TryGetActiveHeldEntity(out @using); // Check whether the "Held" entity is a virtual pull entity. If yes, set that as the entity being "Used". // This allows you to do things like buckle a dragged person onto a surgery table, without click-dragging // their sprite. - if (@using != null && TryComp(@using, out HandVirtualItemComponent? pull)) + + if (TryComp(@using, out HandVirtualItemComponent? pull)) { @using = pull.BlockingEntity; }