diff --git a/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs b/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs index 75f8ca183b..be2d6957e0 100644 --- a/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs +++ b/Content.Client/GameObjects/Components/Clothing/ClothingComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.GameObjects; +using Content.Client.GameObjects.Components.Items; +using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.Components.Items; using Robust.Client.Graphics; diff --git a/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs b/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs deleted file mode 100644 index 301f600838..0000000000 --- a/Content.Client/GameObjects/Components/Items/ClientHandsComponent.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Content.Client.Interfaces.GameObjects; -using Content.Client.UserInterface; -using Content.Shared.GameObjects; -using Robust.Client.GameObjects; -using Robust.Client.Interfaces.GameObjects.Components; -using Robust.Shared.GameObjects; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Client.GameObjects -{ - [RegisterComponent] - [ComponentReference(typeof(IHandsComponent))] - public class HandsComponent : SharedHandsComponent, IHandsComponent - { - private HandsGui _gui; - -#pragma warning disable 649 - [Dependency] private readonly IGameHud _gameHud; -#pragma warning restore 649 - - [ViewVariables] private readonly Dictionary _hands = new Dictionary(); - - [ViewVariables] public string ActiveIndex { get; private set; } - - [ViewVariables] private ISpriteComponent _sprite; - - [ViewVariables] public IEntity ActiveHand => GetEntity(ActiveIndex); - - public override void OnRemove() - { - base.OnRemove(); - - _gui?.Dispose(); - } - - public override void Initialize() - { - base.Initialize(); - - if (Owner.TryGetComponent(out _sprite)) - { - foreach (var slot in _hands.Keys) - { - _sprite.LayerMapReserveBlank($"hand-{slot}"); - _setHand(slot, _hands[slot]); - } - } - } - - public IEntity GetEntity(string index) - { - if (!string.IsNullOrEmpty(index) && _hands.TryGetValue(index, out var entity)) - { - return entity; - } - - return null; - } - - public override void HandleComponentState(ComponentState curState, ComponentState nextState) - { - if (curState == null) - return; - - var cast = (HandsComponentState) curState; - foreach (var (slot, uid) in cast.Hands) - { - IEntity entity = null; - try - { - entity = Owner.EntityManager.GetEntity(uid); - } - catch - { - // Nothing. - } - - _hands[slot] = entity; - _setHand(slot, entity); - } - - foreach (var slot in _hands.Keys.ToList()) - { - if (!cast.Hands.ContainsKey(slot)) - { - _hands[slot] = null; - _setHand(slot, null); - } - } - - ActiveIndex = cast.ActiveIndex; - - _gui?.UpdateHandIcons(); - RefreshInHands(); - } - - private void _setHand(string hand, IEntity entity) - { - if (_sprite == null) - { - return; - } - - if (entity == null) - { - _sprite.LayerSetVisible($"hand-{hand}", false); - return; - } - - SetInHands(hand, entity); - } - - private void SetInHands(string hand, IEntity entity) - { - if (entity == null) - { - _sprite.LayerSetVisible($"hand-{hand}", false); - - return; - } - - if (!entity.TryGetComponent(out ItemComponent item)) return; - var maybeInhands = item.GetInHandStateInfo(hand); - if (!maybeInhands.HasValue) - { - _sprite.LayerSetVisible($"hand-{hand}", false); - } - else - { - var (rsi, state) = maybeInhands.Value; - _sprite.LayerSetVisible($"hand-{hand}", true); - _sprite.LayerSetState($"hand-{hand}", state, rsi); - } - } - - public void RefreshInHands() - { - if (!Initialized) return; - - foreach (var (hand, entity) in _hands) - { - SetInHands(hand, entity); - } - } - - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - - serializer.DataReadWriteFunction( - "hands", - new List(), - hands => hands.ForEach(slot => _hands.Add(slot, null)), - () => _hands.Keys.ToList()); - - serializer.DataField(this, x => ActiveIndex, "defaultHand", _hands.Keys.LastOrDefault()); - } - - public override void HandleMessage(ComponentMessage message, IComponent component) - { - base.HandleMessage(message, component); - - switch (message) - { - case PlayerAttachedMsg _: - if (_gui == null) - { - _gui = new HandsGui(); - } - else - { - _gui.Parent?.RemoveChild(_gui); - } - - _gameHud.HandsContainer.AddChild(_gui); - _gui.UpdateHandIcons(); - break; - - case PlayerDetachedMsg _: - _gui.Parent?.RemoveChild(_gui); - break; - } - } - - public void SendChangeHand(string index) - { - SendNetworkMessage(new ClientChangedHandMsg(index)); - } - - public void AttackByInHand(string index) - { - SendNetworkMessage(new ClientAttackByInHandMsg(index)); - } - - public void UseActiveHand() - { - if (GetEntity(ActiveIndex) != null) - { - SendNetworkMessage(new UseInHandMsg()); - } - } - - public void ActivateItemInHand(string handIndex) - { - if (GetEntity(handIndex) == null) - return; - SendNetworkMessage(new ActivateInHandMsg(handIndex)); - } - } -} diff --git a/Content.Client/GameObjects/Components/Items/HandsComponent.cs b/Content.Client/GameObjects/Components/Items/HandsComponent.cs new file mode 100644 index 0000000000..8d0d3bf7de --- /dev/null +++ b/Content.Client/GameObjects/Components/Items/HandsComponent.cs @@ -0,0 +1,256 @@ +#nullable enable +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Client.UserInterface; +using Content.Shared.GameObjects.Components.Items; +using Robust.Client.GameObjects; +using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.ViewVariables; + +namespace Content.Client.GameObjects.Components.Items +{ + [RegisterComponent] + public class HandsComponent : SharedHandsComponent + { + private HandsGui? _gui; + +#pragma warning disable 649 + [Dependency] private readonly IGameHud _gameHud = default!; +#pragma warning restore 649 + + private readonly List _hands = new List(); + + [ViewVariables] public IReadOnlyList Hands => _hands; + + [ViewVariables] public string? ActiveIndex { get; private set; } + + [ViewVariables] private ISpriteComponent? _sprite; + + [ViewVariables] public IEntity? ActiveHand => GetEntity(ActiveIndex); + + private void AddHand(Hand hand) + { + _hands.Insert(hand.Index, hand); + } + + public Hand? GetHand(string? name) + { + return Hands.FirstOrDefault(hand => hand.Name == name); + } + + private bool TryHand(string name, [MaybeNullWhen(false)] out Hand hand) + { + return (hand = GetHand(name)) != null; + } + + public IEntity? GetEntity(string? handName) + { + if (handName == null) + { + return null; + } + + return GetHand(handName)?.Entity; + } + + public override void OnRemove() + { + base.OnRemove(); + + _gui?.Dispose(); + } + + public override void Initialize() + { + base.Initialize(); + + if (Owner.TryGetComponent(out _sprite)) + { + foreach (var hand in _hands) + { + _sprite.LayerMapReserveBlank($"hand-{hand.Name}"); + UpdateHandSprites(hand); + } + } + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + if (curState == null) + { + return; + } + + var cast = (HandsComponentState) curState; + foreach (var sharedHand in cast.Hands) + { + if (!TryHand(sharedHand.Name, out var hand)) + { + hand = new Hand(sharedHand, Owner.EntityManager); + AddHand(hand); + } + else + { + hand.Location = sharedHand.Location; + + hand.Entity = sharedHand.EntityUid.HasValue + ? Owner.EntityManager.GetEntity(sharedHand.EntityUid.Value) + : null; + } + + UpdateHandSprites(hand); + } + + foreach (var currentHand in _hands.ToList()) + { + if (cast.Hands.All(newHand => newHand.Name != currentHand.Name)) + { + _hands.Remove(currentHand); + _gui?.RemoveHand(currentHand); + HideHand(currentHand); + } + } + + ActiveIndex = cast.ActiveIndex; + + _gui?.UpdateHandIcons(); + RefreshInHands(); + } + + private void HideHand(Hand hand) + { + _sprite?.LayerSetVisible($"hand-{hand.Name}", false); + } + + private void UpdateHandSprites(Hand hand) + { + if (_sprite == null) + { + return; + } + + var entity = hand.Entity; + var name = hand.Name; + + if (entity == null) + { + _sprite.LayerSetVisible($"hand-{name}", false); + return; + } + + if (!entity.TryGetComponent(out ItemComponent item)) return; + + var maybeInHands = item.GetInHandStateInfo(name); + + if (!maybeInHands.HasValue) + { + _sprite.LayerSetVisible($"hand-{name}", false); + } + else + { + var (rsi, state) = maybeInHands.Value; + _sprite.LayerSetVisible($"hand-{name}", true); + _sprite.LayerSetState($"hand-{name}", state, rsi); + } + } + + public void RefreshInHands() + { + if (!Initialized) return; + + foreach (var hand in _hands) + { + UpdateHandSprites(hand); + } + } + + protected override void Startup() + { + ActiveIndex = _hands.LastOrDefault()?.Name; + } + + public override void HandleMessage(ComponentMessage message, IComponent? component) + { + base.HandleMessage(message, component); + + switch (message) + { + case PlayerAttachedMsg _: + if (_gui == null) + { + _gui = new HandsGui(); + } + else + { + _gui.Parent?.RemoveChild(_gui); + } + + _gameHud.HandsContainer.AddChild(_gui); + _gui.UpdateHandIcons(); + break; + + case PlayerDetachedMsg _: + _gui?.Parent?.RemoveChild(_gui); + break; + } + } + + public void SendChangeHand(string index) + { + SendNetworkMessage(new ClientChangedHandMsg(index)); + } + + public void AttackByInHand(string index) + { + SendNetworkMessage(new ClientAttackByInHandMsg(index)); + } + + public void UseActiveHand() + { + if (GetEntity(ActiveIndex) != null) + { + SendNetworkMessage(new UseInHandMsg()); + } + } + + public void ActivateItemInHand(string handIndex) + { + if (GetEntity(handIndex) == null) + { + return; + } + + SendNetworkMessage(new ActivateInHandMsg(handIndex)); + } + } + + public class Hand + { + // TODO: Separate into server hand and client hand + public Hand(SharedHand hand, IEntityManager manager, HandButton? button = null) + { + Index = hand.Index; + Name = hand.Name; + Location = hand.Location; + Button = button; + + if (!hand.EntityUid.HasValue) + { + return; + } + + manager.TryGetEntity(hand.EntityUid.Value, out var entity); + Entity = entity; + } + + public int Index { get; } + public string Name { get; } + public HandLocation Location { get; set; } + public IEntity? Entity { get; set; } + public HandButton? Button { get; set; } + } +} diff --git a/Content.Client/GameObjects/Components/Items/ItemComponent.cs b/Content.Client/GameObjects/Components/Items/ItemComponent.cs index 54f798df07..1dd476db1f 100644 --- a/Content.Client/GameObjects/Components/Items/ItemComponent.cs +++ b/Content.Client/GameObjects/Components/Items/ItemComponent.cs @@ -14,7 +14,7 @@ using Robust.Shared.Serialization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -namespace Content.Client.GameObjects +namespace Content.Client.GameObjects.Components.Items { [RegisterComponent] [ComponentReference(typeof(IItemComponent))] diff --git a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs index 4fc955ede6..affff8473d 100644 --- a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs +++ b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using Content.Client.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Storage; -using Content.Client.Interfaces.GameObjects; using Content.Client.Interfaces.GameObjects.Components.Interaction; using Robust.Client.Graphics.Drawing; using Robust.Client.Interfaces.GameObjects.Components; @@ -137,7 +137,7 @@ namespace Content.Client.GameObjects.Components.Storage { var controlledEntity = IoCManager.Resolve().LocalPlayer.ControlledEntity; - if (controlledEntity.TryGetComponent(out IHandsComponent hands)) + if (controlledEntity.TryGetComponent(out HandsComponent hands)) { StorageEntity.SendNetworkMessage(new InsertEntityMessage()); } @@ -250,7 +250,7 @@ namespace Content.Client.GameObjects.Components.Storage { var controlledEntity = IoCManager.Resolve().LocalPlayer.ControlledEntity; - if (controlledEntity.TryGetComponent(out IHandsComponent hands)) + if (controlledEntity.TryGetComponent(out HandsComponent hands)) { StorageEntity.SendNetworkMessage(new InsertEntityMessage()); } diff --git a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs index 21f61e1606..ed3537fae7 100644 --- a/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs +++ b/Content.Client/GameObjects/EntitySystems/RangedWeaponSystem.cs @@ -1,6 +1,6 @@ using System; +using Content.Client.GameObjects.Components.Items; using Content.Client.GameObjects.Components.Weapons.Ranged; -using Content.Client.Interfaces.GameObjects; using Content.Shared.GameObjects.Components.Weapons.Ranged; using Robust.Client.GameObjects.EntitySystems; using Robust.Client.Interfaces.Graphics.ClientEye; @@ -11,7 +11,6 @@ using Robust.Shared.Input; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; -using Robust.Shared.Log; namespace Content.Client.GameObjects.EntitySystems { @@ -48,7 +47,7 @@ namespace Content.Client.GameObjects.EntitySystems { return; } - + var state = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use); if (!_combatModeSystem.IsInCombatMode() || state != BoundKeyState.Down) { @@ -58,7 +57,7 @@ namespace Content.Client.GameObjects.EntitySystems } var entity = _playerManager.LocalPlayer.ControlledEntity; - if (entity == null || !entity.TryGetComponent(out IHandsComponent hands)) + if (entity == null || !entity.TryGetComponent(out HandsComponent hands)) { return; } diff --git a/Content.Client/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Client/Interfaces/GameObjects/Components/Items/IHandsComponent.cs deleted file mode 100644 index 0915f66599..0000000000 --- a/Content.Client/Interfaces/GameObjects/Components/Items/IHandsComponent.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Robust.Shared.Interfaces.GameObjects; - -namespace Content.Client.Interfaces.GameObjects -{ - // HYPER SIMPLE HANDS API CLIENT SIDE. - // To allow for showing the HUD, mostly. - public interface IHandsComponent - { - IEntity GetEntity(string index); - string ActiveIndex { get; } - IEntity ActiveHand { get; } - - void SendChangeHand(string index); - void AttackByInHand(string index); - void UseActiveHand(); - void ActivateItemInHand(string handIndex); - void RefreshInHands(); - } -} diff --git a/Content.Client/UserInterface/HandButton.cs b/Content.Client/UserInterface/HandButton.cs new file mode 100644 index 0000000000..d249cda3bd --- /dev/null +++ b/Content.Client/UserInterface/HandButton.cs @@ -0,0 +1,15 @@ +using Content.Shared.GameObjects.Components.Items; +using Robust.Client.Graphics; + +namespace Content.Client.UserInterface +{ + public class HandButton : ItemSlotButton + { + public HandButton(Texture texture, Texture storageTexture, HandLocation location) : base(texture, storageTexture) + { + Location = location; + } + + public HandLocation Location { get; } + } +} diff --git a/Content.Client/UserInterface/HandsGui.cs b/Content.Client/UserInterface/HandsGui.cs index b77bf04560..28301e9e9d 100644 --- a/Content.Client/UserInterface/HandsGui.cs +++ b/Content.Client/UserInterface/HandsGui.cs @@ -1,14 +1,15 @@ -using Content.Client.GameObjects; -using Content.Client.Interfaces.GameObjects; +using System; +using System.Linq; +using Content.Client.GameObjects.Components.Items; using Content.Client.Utility; +using Content.Shared.GameObjects.Components.Items; using Content.Shared.Input; -using Robust.Client.Interfaces.GameObjects.Components; +using Robust.Client.Graphics; using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; -using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Timing; @@ -16,67 +17,140 @@ namespace Content.Client.UserInterface { public class HandsGui : Control { - private const string HandNameLeft = "left"; - private const string HandNameRight = "right"; - #pragma warning disable 0649 [Dependency] private readonly IPlayerManager _playerManager; [Dependency] private readonly IResourceCache _resourceCache; [Dependency] private readonly IItemSlotManager _itemSlotManager; #pragma warning restore 0649 - private IEntity _leftHand; - private IEntity _rightHand; + private readonly TextureRect _activeHandRect; - private readonly TextureRect ActiveHandRect; + private readonly Texture _leftHandTexture; + private readonly Texture _middleHandTexture; + private readonly Texture _rightHandTexture; - private readonly ItemSlotButton _leftButton; - private readonly ItemSlotButton _rightButton; + private readonly ItemStatusPanel _leftPanel; + private readonly ItemStatusPanel _topPanel; + private readonly ItemStatusPanel _rightPanel; - private readonly ItemStatusPanel _rightStatusPanel; - private readonly ItemStatusPanel _leftStatusPanel; + private readonly HBoxContainer _guiContainer; + private readonly VBoxContainer _handsColumn; + private readonly HBoxContainer _handsContainer; + + private int _lastHands; public HandsGui() { IoCManager.InjectDependencies(this); - var textureHandLeft = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_l.png"); - var textureHandRight = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_r.png"); - var textureHandActive = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_active.png"); - var storageTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/back.png"); - - _rightStatusPanel = new ItemStatusPanel(true); - _leftStatusPanel = new ItemStatusPanel(false); - - _leftButton = new ItemSlotButton(textureHandLeft, storageTexture); - _rightButton = new ItemSlotButton(textureHandRight, storageTexture); - var hBox = new HBoxContainer + AddChild(_guiContainer = new HBoxContainer { SeparationOverride = 0, - Children = {_rightStatusPanel, _rightButton, _leftButton, _leftStatusPanel} - }; + Children = + { + (_rightPanel = ItemStatusPanel.FromSide(HandLocation.Right)), + (_handsColumn = new VBoxContainer + { + Children = + { + (_topPanel = ItemStatusPanel.FromSide(HandLocation.Middle)), + (_handsContainer = new HBoxContainer {SeparationOverride = 0}) + } + }), + (_leftPanel = ItemStatusPanel.FromSide(HandLocation.Left)) + } + }); - AddChild(hBox); - - _leftButton.OnPressed += args => HandKeyBindDown(args, HandNameLeft); - _leftButton.OnStoragePressed += args => _OnStoragePressed(args, HandNameLeft); - _rightButton.OnPressed += args => HandKeyBindDown(args, HandNameRight); - _rightButton.OnStoragePressed += args => _OnStoragePressed(args, HandNameRight); + var textureHandActive = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_active.png"); // Active hand - _leftButton.AddChild(ActiveHandRect = new TextureRect + _activeHandRect = new TextureRect { Texture = textureHandActive, TextureScale = (2, 2) - }); + }; + + _leftHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_l.png"); + _middleHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_middle.png"); + _rightHandTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/hand_r.png"); + } + + private ItemStatusPanel GetItemPanel(Hand hand) + { + return hand.Location switch + { + HandLocation.Left => _rightPanel, + HandLocation.Middle => _topPanel, + HandLocation.Right => _leftPanel, + _ => throw new IndexOutOfRangeException() + }; + } + + private Texture HandTexture(HandLocation location) + { + switch (location) + { + case HandLocation.Left: + return _leftHandTexture; + case HandLocation.Middle: + return _middleHandTexture; + case HandLocation.Right: + return _rightHandTexture; + default: + throw new ArgumentOutOfRangeException(nameof(location), location, null); + } } /// - /// Gets the hands component controling this gui, returns true if successful and false if failure + /// Adds a new hand to this control + /// + /// The hand to add to this control + /// + /// The actual location of the button. The right hand is drawn + /// on the LEFT of the screen. + /// + private void AddHand(Hand hand, HandLocation buttonLocation) + { + var buttonTexture = HandTexture(buttonLocation); + var storageTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/back.png"); + var button = new HandButton(buttonTexture, storageTexture, buttonLocation); + var slot = hand.Name; + + button.OnPressed += args => HandKeyBindDown(args, slot); + button.OnStoragePressed += args => _OnStoragePressed(args, slot); + + _handsContainer.AddChild(button); + + if (_activeHandRect.Parent == null) + { + button.AddChild(_activeHandRect); + _activeHandRect.SetPositionInParent(1); + } + + hand.Button = button; + } + + public void RemoveHand(Hand hand) + { + var button = hand.Button; + + if (button != null) + { + if (button.Children.Contains(_activeHandRect)) + { + button.RemoveChild(_activeHandRect); + } + + _handsContainer.RemoveChild(button); + } + } + + /// + /// Gets the hands component controlling this gui /// /// - /// - private bool TryGetHands(out IHandsComponent hands) + /// true if successful and false if failure + private bool TryGetHands(out HandsComponent hands) { hands = default; @@ -93,50 +167,63 @@ namespace Content.Client.UserInterface UpdateDraw(); - if (!TryGetHands(out var hands)) + if (!TryGetHands(out var component)) + { return; - - var left = hands.GetEntity(HandNameLeft); - var right = hands.GetEntity(HandNameRight); - - ActiveHandRect.Parent.RemoveChild(ActiveHandRect); - var parent = hands.ActiveIndex == HandNameLeft ? _leftButton : _rightButton; - parent.AddChild(ActiveHandRect); - ActiveHandRect.SetPositionInParent(1); - - if (left != _leftHand) - { - _leftHand = left; - _itemSlotManager.SetItemSlot(_leftButton, _leftHand); } - if (right != _rightHand) + // TODO: Remove button on remove hand + + var hands = component.Hands.OrderByDescending(x => x.Location).ToArray(); + for (var i = 0; i < hands.Length; i++) { - _rightHand = right; - _itemSlotManager.SetItemSlot(_rightButton, _rightHand); + var hand = hands[i]; + + if (hand.Button == null) + { + AddHand(hand, hand.Location); + } + + hand.Button!.Button.Texture = HandTexture(hand.Location); + hand.Button!.SetPositionInParent(i); + _itemSlotManager.SetItemSlot(hand.Button, hand.Entity); } + + _activeHandRect.Parent?.RemoveChild(_activeHandRect); + component.GetHand(component.ActiveIndex)?.Button?.AddChild(_activeHandRect); + + if (hands.Length > 0) + { + _activeHandRect.SetPositionInParent(1); + } + + _leftPanel.SetPositionFirst(); + _rightPanel.SetPositionLast(); } - private void HandKeyBindDown(GUIBoundKeyEventArgs args, string handIndex) + private void HandKeyBindDown(GUIBoundKeyEventArgs args, string slotName) { if (!TryGetHands(out var hands)) + { return; + } if (args.Function == ContentKeyFunctions.MouseMiddle) { - hands.SendChangeHand(handIndex); + hands.SendChangeHand(slotName); args.Handle(); return; } - var entity = hands.GetEntity(handIndex); + var entity = hands.GetEntity(slotName); if (entity == null) { - if (args.Function == EngineKeyFunctions.UIClick && hands.ActiveIndex != handIndex) + if (args.Function == EngineKeyFunctions.UIClick && hands.ActiveIndex != slotName) { - hands.SendChangeHand(handIndex); + hands.SendChangeHand(slotName); args.Handle(); } + return; } @@ -148,37 +235,131 @@ namespace Content.Client.UserInterface if (args.Function == EngineKeyFunctions.UIClick) { - if (hands.ActiveIndex == handIndex) + if (hands.ActiveIndex == slotName) { hands.UseActiveHand(); } else { - hands.AttackByInHand(handIndex); + hands.AttackByInHand(slotName); } + args.Handle(); - return; } } private void _OnStoragePressed(GUIBoundKeyEventArgs args, string handIndex) { - if (args.Function != EngineKeyFunctions.UIClick) - return; - if (!TryGetHands(out var hands)) + if (args.Function != EngineKeyFunctions.UIClick || !TryGetHands(out var hands)) + { return; + } + hands.ActivateItemInHand(handIndex); } + private void UpdatePanels() + { + if (!TryGetHands(out var component)) + { + return; + } + + foreach (var hand in component.Hands) + { + _itemSlotManager.UpdateCooldown(hand.Button, hand.Entity); + } + + switch (component.Hands.Count) + { + case var n when n == 0 && _lastHands != 0: + _guiContainer.Visible = false; + + _topPanel.Update(null); + _leftPanel.Update(null); + _rightPanel.Update(null); + + break; + case 1: + if (_lastHands != 1) + { + _guiContainer.Visible = true; + + _topPanel.Update(null); + _topPanel.Visible = false; + + _leftPanel.Update(null); + _leftPanel.Visible = false; + + _rightPanel.Visible = true; + + if (!_guiContainer.Children.Contains(_rightPanel)) + { + _rightPanel.AddChild(_rightPanel); + _rightPanel.SetPositionFirst(); + } + } + + _rightPanel.Update(component.Hands[0].Entity); + + break; + case 2: + if (_lastHands != 2) + { + _guiContainer.Visible = true; + _topPanel.Update(null); + _topPanel.Visible = false; + + _leftPanel.Visible = true; + _rightPanel.Visible = true; + + if (_handsColumn.Children.Contains(_topPanel)) + { + _handsColumn.RemoveChild(_topPanel); + } + } + + _leftPanel.Update(component.Hands[0].Entity); + _rightPanel.Update(component.Hands[1].Entity); + + // Order is left, right + foreach (var hand in component.Hands) + { + var tooltip = GetItemPanel(hand); + tooltip.Update(hand.Entity); + } + + break; + case var n when n > 2: + if (_lastHands <= 2) + { + _guiContainer.Visible = true; + + _topPanel.Visible = true; + _leftPanel.Visible = false; + _rightPanel.Visible = false; + + if (!_handsColumn.Children.Contains(_topPanel)) + { + _handsColumn.AddChild(_topPanel); + _topPanel.SetPositionFirst(); + } + } + + _topPanel.Update(component.ActiveHand); + _leftPanel.Update(null); + _rightPanel.Update(null); + + break; + } + + _lastHands = component.Hands.Count; + } + protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); - - _itemSlotManager.UpdateCooldown(_leftButton, _leftHand); - _itemSlotManager.UpdateCooldown(_rightButton, _rightHand); - - _rightStatusPanel.Update(_rightHand); - _leftStatusPanel.Update(_leftHand); + UpdatePanels(); } } } diff --git a/Content.Client/UserInterface/ItemSlotButton.cs b/Content.Client/UserInterface/ItemSlotButton.cs index 97f88efaae..df70a8cdaa 100644 --- a/Content.Client/UserInterface/ItemSlotButton.cs +++ b/Content.Client/UserInterface/ItemSlotButton.cs @@ -1,15 +1,14 @@ using System; -using Content.Client.UserInterface; -using Content.Shared.Input; +using Content.Shared.GameObjects.Components.Items; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.Maths; -namespace Content.Client.GameObjects +namespace Content.Client.UserInterface { - public sealed class ItemSlotButton : MarginContainer + public class ItemSlotButton : MarginContainer { public TextureRect Button { get; } public SpriteView SpriteView { get; } diff --git a/Content.Client/UserInterface/ItemStatusPanel.cs b/Content.Client/UserInterface/ItemStatusPanel.cs index 4bd99c446f..67db77ae56 100644 --- a/Content.Client/UserInterface/ItemStatusPanel.cs +++ b/Content.Client/UserInterface/ItemStatusPanel.cs @@ -1,7 +1,11 @@ +#nullable enable +using System; using System.Collections.Generic; using Content.Client.GameObjects.Components; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; +using Content.Shared.GameObjects.Components.Items; +using Robust.Client.Graphics; using Robust.Client.Graphics.Drawing; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; @@ -26,22 +30,17 @@ namespace Content.Client.UserInterface private readonly PanelContainer _panel; [ViewVariables] - private IEntity _entity; + private IEntity? _entity; - public ItemStatusPanel(bool isRightHand) + public ItemStatusPanel(Texture texture, StyleBox.Margin margin) { - // isRightHand means on the LEFT of the screen. - // Keep that in mind. var panel = new StyleBoxTexture { - Texture = ResC.GetTexture(isRightHand - ? "/Textures/Interface/Nano/item_status_right.svg.96dpi.png" - : "/Textures/Interface/Nano/item_status_left.svg.96dpi.png") + Texture = texture }; panel.SetContentMarginOverride(StyleBox.Margin.Vertical, 4); panel.SetContentMarginOverride(StyleBox.Margin.Horizontal, 6); - panel.SetPatchMargin((isRightHand ? StyleBox.Margin.Left : StyleBox.Margin.Right) | StyleBox.Margin.Top, - 13); + panel.SetPatchMargin(margin, 13); AddChild(_panel = new PanelContainer { @@ -67,7 +66,42 @@ namespace Content.Client.UserInterface SizeFlagsVertical = SizeFlags.ShrinkEnd; } - public void Update(IEntity entity) + /// + /// Creates a new instance of + /// based on whether or not it is being created for the right + /// or left hand. + /// + /// + /// The location of the hand that this panel is for + /// + /// the new instance + public static ItemStatusPanel FromSide(HandLocation location) + { + string texture; + StyleBox.Margin margin; + + switch (location) + { + case HandLocation.Left: + texture = "/Textures/Interface/Nano/item_status_right.svg.96dpi.png"; + margin = StyleBox.Margin.Left | StyleBox.Margin.Top; + break; + case HandLocation.Middle: + texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png"; + margin = StyleBox.Margin.Right | StyleBox.Margin.Top; + break; + case HandLocation.Right: + texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png"; + margin = StyleBox.Margin.Right | StyleBox.Margin.Top; + break; + default: + throw new ArgumentOutOfRangeException(nameof(location), location, null); + } + + return new ItemStatusPanel(ResC.GetTexture(texture), margin); + } + + public void Update(IEntity? entity) { if (entity == null) { @@ -105,7 +139,7 @@ namespace Content.Client.UserInterface ClearOldStatus(); - foreach (var statusComponent in _entity.GetAllComponents()) + foreach (var statusComponent in _entity!.GetAllComponents()) { var control = statusComponent.MakeControl(); _statusContents.AddChild(control); @@ -114,9 +148,10 @@ namespace Content.Client.UserInterface } } + // TODO: Depending on if its a two-hand panel or not protected override Vector2 CalculateMinimumSize() { - return Vector2.ComponentMax(base.CalculateMinimumSize(), (150, 00)); + return Vector2.ComponentMax(base.CalculateMinimumSize(), (150, 0)); } } } diff --git a/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs b/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs index 207710d94b..9f47dff826 100644 --- a/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs +++ b/Content.Server/AI/Operators/Combat/Melee/SwingMeleeWeaponOperator.cs @@ -1,4 +1,5 @@ using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Weapon.Melee; using Content.Server.GameObjects.EntitySystems; diff --git a/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs b/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs index e65dc890cf..1e2450d791 100644 --- a/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/DropEntityOperator.cs @@ -1,4 +1,5 @@ using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Log; @@ -21,7 +22,8 @@ namespace Content.Server.AI.Operators.Inventory /// public override Outcome Execute(float frameTime) { - if (!_owner.TryGetComponent(out HandsComponent handsComponent) || handsComponent.FindHand(_entity) == null) + if (!_owner.TryGetComponent(out HandsComponent handsComponent) || + !handsComponent.TryHand(_entity, out _)) { return Outcome.Failed; } diff --git a/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs b/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs index 4966ca172e..dd3c45bd46 100644 --- a/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs +++ b/Content.Server/AI/Operators/Inventory/DropHandItemsOperator.cs @@ -1,4 +1,5 @@ using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using Robust.Shared.Interfaces.GameObjects; namespace Content.Server.AI.Operators.Inventory diff --git a/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs b/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs index 3eba859abc..64eafaffd2 100644 --- a/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/EquipEntityOperator.cs @@ -1,4 +1,5 @@ using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using Robust.Shared.Interfaces.GameObjects; namespace Content.Server.AI.Operators.Inventory @@ -22,9 +23,9 @@ namespace Content.Server.AI.Operators.Inventory // TODO: If in clothing then click on it foreach (var hand in handsComponent.ActivePriorityEnumerable()) { - if (handsComponent.GetHand(hand)?.Owner == _entity) + if (handsComponent.GetItem(hand)?.Owner == _entity) { - handsComponent.ActiveIndex = hand; + handsComponent.ActiveHand = hand; return Outcome.Success; } } diff --git a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs index a2998d4aaa..69061a6382 100644 --- a/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs +++ b/Content.Server/AI/Operators/Inventory/PickupEntityOperator.cs @@ -1,5 +1,5 @@ -using Content.Server.GameObjects; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.Utility; using Robust.Shared.Containers; @@ -41,11 +41,11 @@ namespace Content.Server.AI.Operators.Inventory foreach (var hand in handsComponent.ActivePriorityEnumerable()) { - if (handsComponent.GetHand(hand) == null) + if (handsComponent.GetItem(hand) == null) { - if (handsComponent.ActiveIndex != hand) + if (handsComponent.ActiveHand != hand) { - handsComponent.ActiveIndex = hand; + handsComponent.ActiveHand = hand; } emptyHands = true; diff --git a/Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs b/Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs index aaaab852f0..e8e667270f 100644 --- a/Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs +++ b/Content.Server/AI/Operators/Inventory/UseItemInHandsOperator.cs @@ -1,5 +1,5 @@ -using Content.Server.GameObjects; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; using Robust.Shared.Interfaces.GameObjects; namespace Content.Server.AI.Operators.Inventory @@ -38,8 +38,8 @@ namespace Content.Server.AI.Operators.Inventory foreach (var slot in handsComponent.ActivePriorityEnumerable()) { - if (handsComponent.GetHand(slot) != itemComponent) continue; - handsComponent.ActiveIndex = slot; + if (handsComponent.GetItem(slot) != itemComponent) continue; + handsComponent.ActiveHand = slot; handsComponent.ActivateItem(); return Outcome.Success; } diff --git a/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs b/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs index ba968bfe25..b379f46e18 100644 --- a/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs +++ b/Content.Server/AI/Utility/Considerations/Hands/FreeHandCon.cs @@ -1,6 +1,7 @@ using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; namespace Content.Server.AI.Utility.Considerations.Hands { @@ -21,7 +22,7 @@ namespace Content.Server.AI.Utility.Considerations.Hands foreach (var hand in handsComponent.ActivePriorityEnumerable()) { handCount++; - if (handsComponent.GetHand(hand) == null) + if (handsComponent.GetItem(hand) == null) { freeCount += 1; } diff --git a/Content.Server/AI/Utility/Considerations/Hands/TargetInOurHandsCon.cs b/Content.Server/AI/Utility/Considerations/Hands/TargetInOurHandsCon.cs index 72958d9d73..25df8fc548 100644 --- a/Content.Server/AI/Utility/Considerations/Hands/TargetInOurHandsCon.cs +++ b/Content.Server/AI/Utility/Considerations/Hands/TargetInOurHandsCon.cs @@ -1,7 +1,7 @@ using Content.Server.AI.WorldState; using Content.Server.AI.WorldState.States; -using Content.Server.GameObjects; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; namespace Content.Server.AI.Utility.Considerations.Hands { diff --git a/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs b/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs index 92a7d9735b..5fcec1f468 100644 --- a/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs +++ b/Content.Server/AI/WorldState/States/Hands/AnyFreeHandState.cs @@ -1,4 +1,5 @@ using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using JetBrains.Annotations; namespace Content.Server.AI.WorldState.States.Hands @@ -16,7 +17,7 @@ namespace Content.Server.AI.WorldState.States.Hands foreach (var hand in handsComponent.ActivePriorityEnumerable()) { - if (handsComponent.GetHand(hand) == null) + if (handsComponent.GetItem(hand) == null) { return true; } diff --git a/Content.Server/AI/WorldState/States/Hands/FreeHands.cs b/Content.Server/AI/WorldState/States/Hands/FreeHands.cs index 7a31d8b00b..92c846f672 100644 --- a/Content.Server/AI/WorldState/States/Hands/FreeHands.cs +++ b/Content.Server/AI/WorldState/States/Hands/FreeHands.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using JetBrains.Annotations; namespace Content.Server.AI.WorldState.States.Hands @@ -20,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Hands foreach (var hand in handsComponent.ActivePriorityEnumerable()) { - if (handsComponent.GetHand(hand) == null) + if (handsComponent.GetItem(hand) == null) { result.Add(hand); } diff --git a/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs b/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs index 3e4f227454..ea53b121a9 100644 --- a/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs +++ b/Content.Server/AI/WorldState/States/Hands/HandItemsState.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using JetBrains.Annotations; using Robust.Shared.Interfaces.GameObjects; @@ -19,7 +20,7 @@ namespace Content.Server.AI.WorldState.States.Hands foreach (var hand in handsComponent.ActivePriorityEnumerable()) { - var item = handsComponent.GetHand(hand); + var item = handsComponent.GetItem(hand); if (item != null) { diff --git a/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs b/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs index 190df49bc1..f7784368b7 100644 --- a/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs +++ b/Content.Server/AI/WorldState/States/Inventory/EquippedEntityState.cs @@ -1,4 +1,5 @@ using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using JetBrains.Annotations; using Robust.Shared.Interfaces.GameObjects; diff --git a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs b/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs index 347dc0402e..26ce5f30af 100644 --- a/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs +++ b/Content.Server/AI/WorldState/States/Inventory/InventoryState.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Content.Server.GameObjects; +using Content.Server.GameObjects.Components.GUI; using JetBrains.Annotations; using Robust.Shared.Interfaces.GameObjects; diff --git a/Content.Server/Chat/ChatCommands.cs b/Content.Server/Chat/ChatCommands.cs index 5ff3828f58..eed35957da 100644 --- a/Content.Server/Chat/ChatCommands.cs +++ b/Content.Server/Chat/ChatCommands.cs @@ -4,7 +4,6 @@ using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; using Content.Server.Players; using Content.Shared.GameObjects; -using Robust.Server.Console; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; using Robust.Shared.Enums; @@ -13,6 +12,7 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using System.Linq; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; namespace Content.Server.Chat { diff --git a/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs b/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs index 9fffb5f77b..194c4e9b65 100644 --- a/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs +++ b/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs @@ -5,6 +5,7 @@ using System.Linq; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.Components.Inventory; using JetBrains.Annotations; using Robust.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs index 100ad449f4..9a799c7716 100644 --- a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using Content.Server.Interfaces; -using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.Access; using Content.Shared.GameObjects.Components.Access; using Content.Shared.Interfaces.GameObjects.Components; @@ -132,7 +132,7 @@ namespace Content.Server.GameObjects.Components.Access { return; } - if(!hands.Drop(hands.ActiveIndex, container)) + if(!hands.Drop(hands.ActiveHand, container)) { _notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You can't let go of the ID card!")); return; diff --git a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs index 275fe52859..573a61dab8 100644 --- a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs +++ b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Strap; using Content.Server.GameObjects.EntitySystems; diff --git a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs index 18d285307c..2f7caaf156 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ChemMasterComponent.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; @@ -19,6 +20,7 @@ using Robust.Shared.ViewVariables; using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects.Systems; using Content.Server.GameObjects.Components.Power.ApcNetComponents; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.Interfaces.Random; using Robust.Shared.Maths; diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index 60998b2d5f..f9a698e717 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -1,8 +1,8 @@ using System; using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Server.Interfaces; -using Content.Server.Interfaces.GameObjects; using Content.Shared.Chemistry; using Content.Shared.GameObjects.Components.Chemistry; using Content.Shared.GameObjects.EntitySystems; @@ -21,6 +21,7 @@ using Robust.Server.GameObjects.EntitySystems; using Robust.Shared.GameObjects.Systems; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Shared.Interfaces.GameObjects.Components; +using Content.Server.Interfaces.GameObjects.Components.Items; namespace Content.Server.GameObjects.Components.Chemistry { diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs index 70c04e49e8..ed4d9df3f6 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -17,6 +17,7 @@ using Robust.Shared.Utility; using Robust.Shared.ViewVariables; using System.Collections.Generic; using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Shared.GameObjects.EntitySystems; using Robust.Shared.GameObjects.Systems; using Content.Server.GameObjects.EntitySystems.Click; diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index 6f3cd3612c..dd232f2097 100644 --- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Content.Server.GameObjects.Components.Access; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.Interfaces.GameObjects; using Content.Shared.GameObjects.Components.Doors; diff --git a/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs similarity index 59% rename from Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs rename to Content.Server/GameObjects/Components/GUI/HandsComponent.cs index b0b39cd326..d124e7e293 100644 --- a/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/HandsComponent.cs @@ -1,10 +1,13 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using Content.Server.GameObjects.Components; +using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.GameObjects.Components.Items; using Content.Server.GameObjects.EntitySystems.Click; -using Content.Server.Interfaces.GameObjects; -using Content.Shared.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Interaction; +using Content.Shared.BodySystem; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystemMessages; @@ -18,72 +21,67 @@ using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Players; -using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; -namespace Content.Server.GameObjects +namespace Content.Server.GameObjects.Components.GUI { [RegisterComponent] [ComponentReference(typeof(IHandsComponent))] - public class HandsComponent : SharedHandsComponent, IHandsComponent + public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved { #pragma warning disable 649 - [Dependency] private readonly IEntitySystemManager _entitySystemManager; + [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; #pragma warning restore 649 - private string _activeIndex; + private string? _activeHand; + private uint _nextHand; [ViewVariables(VVAccess.ReadWrite)] - public string ActiveIndex + public string? ActiveHand { - get => _activeIndex; + get => _activeHand; set { - if (!_hands.ContainsKey(value)) + if (value != null && GetHand(value) == null) { throw new ArgumentException($"No hand '{value}'"); } - _activeIndex = value; + _activeHand = value; Dirty(); } } - [ViewVariables] private readonly Dictionary _hands = new Dictionary(); - [ViewVariables] private List _orderedHands = new List(); + [ViewVariables] private readonly List _hands = new List(); // Mostly arbitrary. public const float PickupRange = 2; - [ViewVariables] public int Count => _orderedHands.Count; + [ViewVariables] public int Count => _hands.Count; - public override void ExposeData(ObjectSerializer serializer) + // TODO: This does not serialize what objects are held. + protected override void Startup() { - base.ExposeData(serializer); - - serializer.DataReadWriteFunction("hands", - new List(0), - hands => hands.ForEach(AddHand), - () => _orderedHands); - serializer.DataField(ref _activeIndex, "defaultHand", _orderedHands.LastOrDefault()); + base.Startup(); + ActiveHand = _hands.LastOrDefault()?.Name; } public IEnumerable GetAllHeldItems() { - foreach (var slot in _hands.Values) + foreach (var hand in _hands) { - if (slot.ContainedEntity != null) + if (hand.Entity != null) { - yield return slot.ContainedEntity.GetComponent(); + yield return hand.Entity.GetComponent(); } } } public bool IsHolding(IEntity entity) { - foreach (var slot in _hands.Values) + foreach (var hand in _hands) { - if (slot.ContainedEntity == entity) + if (hand.Entity == entity) { return true; } @@ -91,28 +89,38 @@ namespace Content.Server.GameObjects return false; } - public ItemComponent GetHand(string index) + private Hand? GetHand(string name) { - var slot = _hands[index]; - return slot.ContainedEntity?.GetComponent(); + return _hands.FirstOrDefault(hand => hand.Name == name); } - public ItemComponent GetActiveHand => GetHand(ActiveIndex); + public ItemComponent? GetItem(string handName) + { + return GetHand(handName)?.Entity?.GetComponent(); + } + + public ItemComponent? GetActiveHand => ActiveHand == null + ? null + : GetItem(ActiveHand); /// /// Enumerates over the hand keys, returning the active hand first. /// public IEnumerable ActivePriorityEnumerable() { - yield return ActiveIndex; - foreach (var hand in _hands.Keys) + if (ActiveHand != null) { - if (hand == ActiveIndex) + yield return ActiveHand; + } + + foreach (var hand in _hands) + { + if (hand.Name == ActiveHand) { continue; } - yield return hand; + yield return hand.Name; } } @@ -120,7 +128,7 @@ namespace Content.Server.GameObjects { foreach (var hand in ActivePriorityEnumerable()) { - if (PutInHand(item, hand, fallback: false)) + if (PutInHand(item, hand, false)) { return true; } @@ -131,14 +139,14 @@ namespace Content.Server.GameObjects public bool PutInHand(ItemComponent item, string index, bool fallback = true) { - if (!CanPutInHand(item, index)) + var hand = GetHand(index); + if (!CanPutInHand(item, index) || hand == null) { return fallback && PutInHand(item); } - var slot = _hands[index]; Dirty(); - var success = slot.Insert(item.Owner); + var success = hand.Container.Insert(item.Owner); if (success) { item.Owner.Transform.LocalPosition = Vector2.Zero; @@ -152,14 +160,16 @@ namespace Content.Server.GameObjects public void PutInHandOrDrop(ItemComponent item) { if (!PutInHand(item)) + { item.Owner.Transform.GridPosition = Owner.Transform.GridPosition; + } } public bool CanPutInHand(ItemComponent item) { - foreach (var hand in ActivePriorityEnumerable()) + foreach (var handName in ActivePriorityEnumerable()) { - if (CanPutInHand(item, hand)) + if (CanPutInHand(item, handName)) { return true; } @@ -170,43 +180,42 @@ namespace Content.Server.GameObjects public bool CanPutInHand(ItemComponent item, string index) { - var slot = _hands[index]; - return slot.CanInsert(item.Owner); + return GetHand(index)?.Container.CanInsert(item.Owner) == true; } - public string FindHand(IEntity entity) + public bool TryHand(IEntity entity, [MaybeNullWhen(false)] out string handName) { - foreach (var (index, slot) in _hands) + handName = null; + + foreach (var hand in _hands) { - if (slot.ContainedEntity == entity) + if (hand.Entity == entity) { - return index; + handName = hand.Name; + return true; } } - return null; + return false; } public bool Drop(string slot, GridCoordinates coords, bool doMobChecks = true) { - if (!CanDrop(slot)) + var hand = GetHand(slot); + if (!CanDrop(slot) || hand?.Entity == null) { return false; } - var inventorySlot = _hands[slot]; - var item = inventorySlot.ContainedEntity.GetComponent(); + var item = hand.Entity.GetComponent(); - if (!inventorySlot.Remove(inventorySlot.ContainedEntity)) + if (!hand.Container.Remove(hand.Entity)) { return false; } - if (doMobChecks && !_entitySystemManager.GetEntitySystem().TryDroppedInteraction(Owner, item.Owner)) - return false; - - if (ContainerHelpers.TryGetContainer(Owner, out var container) && - !container.Insert(item.Owner)) + if (doMobChecks && + !_entitySystemManager.GetEntitySystem().TryDroppedInteraction(Owner, item.Owner)) { return false; } @@ -214,6 +223,11 @@ namespace Content.Server.GameObjects item.RemovedFromSlot(); item.Owner.Transform.GridPosition = coords; + if (ContainerHelpers.TryGetContainer(Owner, out var container)) + { + container.Insert(item.Owner); + } + Dirty(); return true; } @@ -225,8 +239,7 @@ namespace Content.Server.GameObjects throw new ArgumentNullException(nameof(entity)); } - var slot = FindHand(entity); - if (slot == null) + if (!TryHand(entity, out var slot)) { throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity)); } @@ -236,24 +249,21 @@ namespace Content.Server.GameObjects public bool Drop(string slot, bool doMobChecks = true) { - if (!CanDrop(slot)) + var hand = GetHand(slot); + if (!CanDrop(slot) || hand?.Entity == null) { return false; } - var inventorySlot = _hands[slot]; - var item = inventorySlot.ContainedEntity.GetComponent(); + var item = hand.Entity.GetComponent(); - if (doMobChecks && !_entitySystemManager.GetEntitySystem().TryDroppedInteraction(Owner, item.Owner)) - return false; - - if (!inventorySlot.Remove(inventorySlot.ContainedEntity)) + if (doMobChecks && + !_entitySystemManager.GetEntitySystem().TryDroppedInteraction(Owner, item.Owner)) { return false; } - if (ContainerHelpers.TryGetContainer(Owner, out var container) && - !container.Insert(item.Owner)) + if (!hand.Container.Remove(hand.Entity)) { return false; } @@ -266,6 +276,11 @@ namespace Content.Server.GameObjects spriteComponent.RenderOrder = item.Owner.EntityManager.CurrentTick.Value; } + if (ContainerHelpers.TryGetContainer(Owner, out var container)) + { + container.Insert(item.Owner); + } + Dirty(); return true; } @@ -277,8 +292,7 @@ namespace Content.Server.GameObjects throw new ArgumentNullException(nameof(entity)); } - var slot = FindHand(entity); - if (slot == null) + if (!TryHand(entity, out var slot)) { throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity)); } @@ -298,31 +312,30 @@ namespace Content.Server.GameObjects throw new ArgumentNullException(nameof(targetContainer)); } - if (!CanDrop(slot)) + var hand = GetHand(slot); + if (!CanDrop(slot) || hand?.Entity == null) { return false; } - - var inventorySlot = _hands[slot]; - var item = inventorySlot.ContainedEntity.GetComponent(); + var item = hand.Entity.GetComponent(); if (doMobChecks && !_entitySystemManager.GetEntitySystem().TryDroppedInteraction(Owner, item.Owner)) { return false; } - if (!inventorySlot.CanRemove(inventorySlot.ContainedEntity)) + if (!hand.Container.CanRemove(hand.Entity)) { return false; } - if (!targetContainer.CanInsert(inventorySlot.ContainedEntity)) + if (!targetContainer.CanInsert(hand.Entity)) { return false; } - if (!inventorySlot.Remove(inventorySlot.ContainedEntity)) + if (!hand.Container.Remove(hand.Entity)) { throw new InvalidOperationException(); } @@ -345,8 +358,7 @@ namespace Content.Server.GameObjects throw new ArgumentNullException(nameof(entity)); } - var slot = FindHand(entity); - if (slot == null) + if (!TryHand(entity, out var slot)) { throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity)); } @@ -357,94 +369,103 @@ namespace Content.Server.GameObjects /// /// Checks whether an item can be dropped from the specified slot. /// - /// The slot to check for. + /// The slot to check for. /// /// True if there is an item in the slot and it can be dropped, false otherwise. /// - public bool CanDrop(string slot) + public bool CanDrop(string name) { - var inventorySlot = _hands[slot]; - - if (ContainerHelpers.TryGetContainer(Owner, out var container) && - !container.CanInsert(inventorySlot.ContainedEntity)) + var hand = GetHand(name); + if (hand?.Entity == null) { return false; } - return inventorySlot.CanRemove(inventorySlot.ContainedEntity); + return hand.Container.CanRemove(hand.Entity); } - public void AddHand(string index) + public void AddHand(string name) { - if (HasHand(index)) + if (HasHand(name)) { - throw new InvalidOperationException($"Hand '{index}' already exists."); + throw new InvalidOperationException($"Hand '{name}' already exists."); } - var slot = ContainerManagerComponent.Create(Name + "_" + index, Owner); - _hands[index] = slot; - if (!_orderedHands.Contains(index)) - { - _orderedHands.Add(index); - } + var container = ContainerManagerComponent.Create($"hand {_nextHand++}", Owner); + var hand = new Hand(name, container); + + _hands.Add(hand); + + ActiveHand ??= name; - ActiveIndex ??= index; Dirty(); } - public void RemoveHand(string index) + public void RemoveHand(string name) { - if (!HasHand(index)) + var hand = GetHand(name); + if (hand == null) { - throw new InvalidOperationException($"Hand '{index}' does not exist."); + throw new InvalidOperationException($"Hand '{name}' does not exist."); } - _hands[index].Shutdown(); //TODO verify this - _hands.Remove(index); - _orderedHands.Remove(index); + Drop(hand.Name, false); + hand!.Dispose(); + _hands.Remove(hand); - if (index == ActiveIndex) + if (name == ActiveHand) { - _activeIndex = _orderedHands.Count == 0 ? null : _orderedHands[0]; + _activeHand = _hands.FirstOrDefault()?.Name; } Dirty(); } - public bool HasHand(string index) + public bool HasHand(string name) { - return _hands.ContainsKey(index); + return _hands.Any(hand => hand.Name == name); } - /// - /// Get the name of the slot passed to the inventory component. - /// - private string HandSlotName(string index) => $"_hand_{index}"; - public override ComponentState GetComponentState() { - var dict = new Dictionary(_hands.Count); - foreach (var hand in _hands) + var hands = new SharedHand[_hands.Count]; + + for (var i = 0; i < _hands.Count; i++) { - if (hand.Value.ContainedEntity != null) - { - dict[hand.Key] = hand.Value.ContainedEntity.Uid; - } + var location = i == 0 + ? HandLocation.Right + : i == _hands.Count - 1 + ? HandLocation.Left + : HandLocation.Middle; + + var hand = _hands[i].ToShared(i, location); + hands[i] = hand; } - return new HandsComponentState(dict, ActiveIndex); + return new HandsComponentState(hands, ActiveHand); } public void SwapHands() { - var index = _orderedHands.FindIndex(x => x == ActiveIndex); + if (ActiveHand == null) + { + return; + } + + var hand = GetHand(ActiveHand); + if (hand == null) + { + throw new InvalidOperationException($"No hand found with name {ActiveHand}"); + } + + var index = _hands.IndexOf(hand); index++; - if (index >= _orderedHands.Count) + if (index == _hands.Count) { index = 0; } - ActiveIndex = _orderedHands[index]; + ActiveHand = _hands[index].Name; } public void ActivateItem() @@ -469,7 +490,7 @@ namespace Content.Server.GameObjects return false; } - public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null) + public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null) { base.HandleNetworkMessage(message, channel, session); @@ -485,15 +506,19 @@ namespace Content.Server.GameObjects var playerEntity = session.AttachedEntity; if (playerEntity == Owner && HasHand(msg.Index)) - ActiveIndex = msg.Index; + { + ActiveHand = msg.Index; + } + break; } case ClientAttackByInHandMsg msg: { - if (!_hands.TryGetValue(msg.Index, out var slot)) + var hand = GetHand(msg.Index); + if (hand == null) { - Logger.WarningS("go.comp.hands", "Got a ClientAttackByInHandMsg with invalid hand index '{0}'", + Logger.WarningS("go.comp.hands", "Got a ClientAttackByInHandMsg with invalid hand name '{0}'", msg.Index); return; } @@ -501,19 +526,22 @@ namespace Content.Server.GameObjects var playerEntity = session.AttachedEntity; var used = GetActiveHand?.Owner; - if (playerEntity == Owner && slot.ContainedEntity != null) + if (playerEntity == Owner && hand.Entity != null) { var interactionSystem = _entitySystemManager.GetEntitySystem(); if (used != null) { - interactionSystem.Interaction(Owner, used, slot.ContainedEntity, + interactionSystem.Interaction(Owner, used, hand.Entity, GridCoordinates.InvalidGrid); } else { - var entity = slot.ContainedEntity; + var entity = hand.Entity; if (!Drop(entity)) + { break; + } + interactionSystem.Interaction(Owner, entity); } } @@ -521,7 +549,7 @@ namespace Content.Server.GameObjects break; } - case UseInHandMsg msg: + case UseInHandMsg _: { var playerEntity = session.AttachedEntity; var used = GetActiveHand?.Owner; @@ -538,7 +566,7 @@ namespace Content.Server.GameObjects case ActivateInHandMsg msg: { var playerEntity = session.AttachedEntity; - var used = GetHand(msg.Index)?.Owner; + var used = GetItem(msg.Index)?.Owner; if (playerEntity == Owner && used != null) { @@ -552,9 +580,9 @@ namespace Content.Server.GameObjects public void HandleSlotModifiedMaybe(ContainerModifiedMessage message) { - foreach (var container in _hands.Values) + foreach (var hand in _hands) { - if (container != message.Container) + if (hand.Container != message.Container) { continue; } @@ -571,5 +599,48 @@ namespace Content.Server.GameObjects return; } } + + void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs eventArgs) + { + if (eventArgs.Part.PartType != BodyPartType.Hand) + { + return; + } + + AddHand(eventArgs.SlotName); + } + + void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs eventArgs) + { + if (eventArgs.Part.PartType != BodyPartType.Hand) + { + return; + } + + RemoveHand(eventArgs.SlotName); + } + } + + public class Hand : IDisposable + { + public Hand(string name, ContainerSlot container) + { + Name = name; + Container = container; + } + + public string Name { get; } + public IEntity? Entity => Container.ContainedEntity; + public ContainerSlot Container { get; } + + public void Dispose() + { + Container.Shutdown(); // TODO verify this + } + + public SharedHand ToShared(int index, HandLocation location) + { + return new SharedHand(index, Name, Entity?.Uid, location); + } } } diff --git a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs index 50fae0fd49..819d22bfd3 100644 --- a/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/InventoryComponent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; using Content.Shared.GameObjects.Components.Inventory; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.GameObjects.EntitySystems.Click; @@ -326,7 +327,7 @@ namespace Content.Server.GameObjects var activeHand = hands.GetActiveHand; if (activeHand != null && activeHand.Owner.TryGetComponent(out ItemComponent clothing)) { - hands.Drop(hands.ActiveIndex); + hands.Drop(hands.ActiveHand); if (!Equip(msg.Inventoryslot, clothing, out var reason)) { hands.PutInHand(clothing); diff --git a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs index 843b397f1b..143b3e340c 100644 --- a/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs +++ b/Content.Server/GameObjects/Components/Interactable/HandheldLightComponent.cs @@ -1,10 +1,12 @@ -using Content.Server.GameObjects.Components.Power; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.EntitySystems.Click; -using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; +using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.EntitySystems; @@ -16,7 +18,6 @@ using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -using Content.Shared.Interfaces.GameObjects.Components; namespace Content.Server.GameObjects.Components.Interactable { diff --git a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs index 6f24406712..290b25279a 100644 --- a/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Clothing/ClothingComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces; using Content.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs index 04c103c2bf..bc4e71573b 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces.GameObjects.Components.Interaction; diff --git a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs index 00f1221cec..ea6b5d93c1 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ItemComponent.cs @@ -1,6 +1,8 @@ -using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Throw; using Content.Server.Utility; using Content.Shared.GameObjects; @@ -100,7 +102,7 @@ namespace Content.Server.GameObjects.Components if (!CanPickup(eventArgs.User)) return false; var hands = eventArgs.User.GetComponent(); - hands.PutInHand(this, hands.ActiveIndex, fallback: false); + hands.PutInHand(this, hands.ActiveHand, false); return true; } diff --git a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs index ecdfb4c5f6..18ce9920e4 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/ServerStorageComponent.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Server.Interfaces.GameObjects; using Content.Server.Interfaces.GameObjects.Components.Interaction; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Utility; using Content.Shared.GameObjects.Components.Storage; using Content.Shared.GameObjects.EntitySystems; diff --git a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs index 5c1e00d6f2..4acc5f5954 100644 --- a/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/MicrowaveComponent.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; using System.Linq; +using Content.Server.BodySystem; using Content.Server.Interfaces.GameObjects.Components.Interaction; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.ViewVariables; using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.GameObjects.Components.GUI; using Content.Shared.Chemistry; using Robust.Shared.Serialization; using Robust.Shared.Interfaces.GameObjects; @@ -22,10 +24,10 @@ using Content.Server.Interfaces; using Robust.Shared.Audio; using Content.Server.Interfaces.GameObjects; using Content.Server.Interfaces.Chat; -using Content.Server.BodySystem; using Content.Shared.BodySystem; using Robust.Shared.GameObjects.Systems; using Content.Server.GameObjects.Components.Power.ApcNetComponents; +using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; namespace Content.Server.GameObjects.Components.Kitchen @@ -211,9 +213,15 @@ namespace Content.Server.GameObjects.Components.Kitchen return false; } - var itemEntity = eventArgs.User.GetComponent().GetActiveHand.Owner; + var itemEntity = eventArgs.User.GetComponent().GetActiveHand?.Owner; - if(itemEntity.TryGetComponent(out var attackPourable)) + if (itemEntity == null) + { + eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You have no active hand!")); + return false; + } + + if (itemEntity.TryGetComponent(out var attackPourable)) { if (!itemEntity.TryGetComponent(out var attackSolution) || !attackSolution.CanPourOut) diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs index 8f86b72bd3..9d6681c776 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Content.Server.GameObjects.Components.Chemistry; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Utensil; using Content.Server.Utility; using Content.Shared.Chemistry; diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs index 5953616ed2..c7735cc2b4 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodContainerComponent.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Shared.GameObjects.Components.Nutrition; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; @@ -54,8 +55,11 @@ namespace Content.Server.GameObjects.Components.Nutrition bool IUse.UseEntity(UseEntityEventArgs eventArgs) { + if (!eventArgs.User.TryGetComponent(out HandsComponent handsComponent)) + { + return false; + } - var hands = eventArgs.User.TryGetComponent(out HandsComponent handsComponent); var itemToSpawn = _entityManager.SpawnEntity(GetRandomPrototype(), Owner.Transform.GridPosition); handsComponent.PutInHandOrDrop(itemToSpawn.GetComponent()); _count--; diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index 0e5329fb23..3edd592e37 100644 --- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs +++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Access; +using Content.Server.GameObjects.Components.GUI; using Content.Server.Interfaces; using Content.Server.Interfaces.PDA; using Content.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs b/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs index ac9c35784c..3ac23365fe 100644 --- a/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs +++ b/Content.Server/GameObjects/Components/PlaceableSurfaceComponent.cs @@ -1,4 +1,5 @@ -using Content.Shared.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; +using Content.Shared.GameObjects.Components; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Shared.GameObjects; using Robust.Shared.Serialization; @@ -17,6 +18,7 @@ namespace Content.Server.GameObjects.Components serializer.DataField(ref _isPlaceable, "IsPlaceable", true); } + public bool InteractUsing(InteractUsingEventArgs eventArgs) { if (!IsPlaceable) diff --git a/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs b/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs index cad93e0261..3e3f30afda 100644 --- a/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs +++ b/Content.Server/GameObjects/Components/PottedPlantHideComponent.cs @@ -1,5 +1,6 @@ -using Content.Server.GameObjects.Components.Items.Storage; -using Content.Server.Interfaces.GameObjects; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.Audio; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs index 6866c80277..70be130114 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/BaseCharger.cs @@ -1,4 +1,5 @@ using System; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; using Content.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs index 6c213f186e..01bf741a8a 100644 --- a/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs +++ b/Content.Server/GameObjects/Components/Power/ApcNetComponents/PowerReceiverUsers/PoweredLightComponent.cs @@ -1,4 +1,5 @@ using System; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Power.ApcNetComponents; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs index 2040bbf35b..952593c3e2 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/StunbatonComponent.cs @@ -1,8 +1,9 @@ using System.Collections.Generic; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.EntitySystems.Click; -using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.Audio; using Content.Shared.GameObjects; using Content.Shared.GameObjects.EntitySystems; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs index 2e0d0572cf..f39e8101bd 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/AmmoBoxComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs index f9bf026ab1..c299fcfcbe 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/RangedMagazineComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.Interfaces; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs index b1d7d239b8..3e8e86e2e7 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Ammunition/SpeedLoaderComponent.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; using Content.Shared.GameObjects.Components.Weapons.Ranged.Barrels; using Content.Shared.Interfaces; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs index 556223aad8..88a887b6de 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerBatteryBarrelComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Power; using Content.Server.GameObjects.Components.Projectiles; using Content.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs index 6fc4efeb96..ff56ec7f76 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/Barrels/ServerMagazineBarrelComponent.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition; using Content.Server.GameObjects.EntitySystems.Click; using Content.Shared.GameObjects; diff --git a/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs index 4b62106aa7..c66447333d 100644 --- a/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Ranged/ServerRangedWeaponComponent.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels; using Content.Shared.GameObjects.Components.Weapons.Ranged; diff --git a/Content.Server/GameObjects/Components/WiresComponent.cs b/Content.Server/GameObjects/Components/WiresComponent.cs index 47efb6ba21..f39d297b6d 100644 --- a/Content.Server/GameObjects/Components/WiresComponent.cs +++ b/Content.Server/GameObjects/Components/WiresComponent.cs @@ -8,6 +8,7 @@ using Content.Server.GameObjects.EntitySystems.Click; using Content.Server.Interfaces.GameObjects.Components.Interaction; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.Components; using Content.Shared.GameObjects.Components.Interactable; using Content.Shared.GameObjects.EntitySystems; diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 03f6772ff2..857ef12576 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -2,7 +2,7 @@ using System.Linq; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Timing; -using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Server.Utility; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.EntitySystemMessages; diff --git a/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs b/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs index 43a4e7145f..2498cd48a5 100644 --- a/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ConstructionSystem.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Content.Server.GameObjects.Components; using Content.Server.GameObjects.Components.Construction; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Stack; using Content.Server.GameObjects.EntitySystems.Click; diff --git a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs index 85b96fdf39..94cdd828ff 100644 --- a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.GameObjects.Components.Stack; +using Content.Server.Interfaces; using Content.Server.Throw; using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.Input; @@ -15,6 +16,8 @@ using Robust.Shared.Localization; using Robust.Shared.Map; using Robust.Shared.Players; using System; +using Content.Server.GameObjects.Components.GUI; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.GameObjects.EntitySystems; using Content.Server.GameObjects; using Content.Server.GameObjects.Components; @@ -74,11 +77,10 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction var ent = session.AttachedEntity; - if (ent == null || !ent.IsValid()) - return false; - - if (!ent.TryGetComponent(out T comp)) + if (ent == null || !ent.IsValid() || !ent.TryGetComponent(out T comp)) + { return false; + } component = comp; return true; @@ -87,9 +89,11 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction private static void HandleSwapHands(ICommonSession session) { if (!TryGetAttachedComponent(session as IPlayerSession, out HandsComponent handsComp)) + { return; + } - var interactionSystem = EntitySystem.Get(); + var interactionSystem = Get(); var oldItem = handsComp.GetActiveHand; @@ -97,11 +101,15 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction var newItem = handsComp.GetActiveHand; - if(oldItem != null) + if (oldItem != null) + { interactionSystem.HandDeselectedInteraction(handsComp.Owner, oldItem.Owner); + } - if(newItem != null) + if (newItem != null) + { interactionSystem.HandSelectedInteraction(handsComp.Owner, newItem.Owner); + } } private bool HandleDrop(ICommonSession session, GridCoordinates coords, EntityUid uid) @@ -119,11 +127,11 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction var entCoords = ent.Transform.GridPosition.Position; var entToDesiredDropCoords = coords.Position - entCoords; - var targetLength = Math.Min(entToDesiredDropCoords.Length, InteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error + var targetLength = Math.Min(entToDesiredDropCoords.Length, SharedInteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error var newCoords = new GridCoordinates((entToDesiredDropCoords.Normalized * targetLength) + entCoords, coords.GridID); - var rayLength = EntitySystem.Get().UnobstructedRayLength(ent.Transform.MapPosition, newCoords.ToMap(_mapManager), ignoredEnt: ent); + var rayLength = Get().UnobstructedRayLength(ent.Transform.MapPosition, newCoords.ToMap(_mapManager), ignoredEnt: ent); - handsComp.Drop(handsComp.ActiveIndex, new GridCoordinates(entCoords + (entToDesiredDropCoords.Normalized * rayLength), coords.GridID)); + handsComp.Drop(handsComp.ActiveHand, new GridCoordinates(entCoords + (entToDesiredDropCoords.Normalized * rayLength), coords.GridID)); return true; } @@ -146,10 +154,10 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction if (!plyEnt.TryGetComponent(out HandsComponent handsComp)) return false; - if (!handsComp.CanDrop(handsComp.ActiveIndex)) + if (!handsComp.CanDrop(handsComp.ActiveHand)) return false; - var throwEnt = handsComp.GetHand(handsComp.ActiveIndex).Owner; + var throwEnt = handsComp.GetItem(handsComp.ActiveHand).Owner; if (!handsComp.ThrowItem()) return false; @@ -157,7 +165,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction // throw the item, split off from a stack if it's meant to be thrown individually if (!throwEnt.TryGetComponent(out StackComponent stackComp) || stackComp.Count < 2 || !stackComp.ThrowIndividually) { - handsComp.Drop(handsComp.ActiveIndex); + handsComp.Drop(handsComp.ActiveHand); } else { @@ -201,7 +209,7 @@ namespace Content.Server.Interfaces.GameObjects.Components.Interaction return; } - var heldItem = handsComp.GetHand(handsComp.ActiveIndex)?.Owner; + var heldItem = handsComp.GetItem(handsComp.ActiveHand)?.Owner; if (heldItem != null) { diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index b2441863c6..35d6509e9f 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Content.Server.GameObjects; using Content.Server.GameObjects.Components; using Content.Server.GameObjects.Components.Access; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Markers; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.Components.Observer; diff --git a/Content.Server/GameTicking/GameTickerCommands.cs b/Content.Server/GameTicking/GameTickerCommands.cs index 7d45bb1370..170da5fdd6 100644 --- a/Content.Server/GameTicking/GameTickerCommands.cs +++ b/Content.Server/GameTicking/GameTickerCommands.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; +using Content.Server.BodySystem; using Content.Server.Interfaces.GameTicking; using Content.Server.Players; +using Content.Shared.BodySystem; using Content.Shared.Jobs; using Robust.Server.Interfaces.Console; using Robust.Server.Interfaces.Player; @@ -321,4 +324,56 @@ namespace Content.Server.GameTicking shell.SendText(player, $"Created unloaded map from file {args[1]} with id {args[0]}. Use \"savebp 4 foo.yml\" to save it."); } } + + class AddHandCommand : IClientCommand + { + public string Command => "addhand"; + public string Description => "Adds a hand to your entity."; + public string Help => $"Usage: {Command}"; + + public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + { + if (player == null) + { + shell.SendText(player, "Only a player can run this command."); + return; + } + + if (player.AttachedEntity == null) + { + shell.SendText(player, "You have no entity."); + return; + } + + var manager = player.AttachedEntity.GetComponent(); + var hand = manager.PartDictionary.First(x => x.Key == string.Join(" ", args)); + manager.InstallBodyPart(hand.Value, hand.Key + new Random()); + } + } + + class RemoveHandCommand : IClientCommand + { + public string Command => "removehand"; + public string Description => "Removes a hand from your entity."; + public string Help => $"Usage: {Command}"; + + public void Execute(IConsoleShell shell, IPlayerSession player, string[] args) + { + if (player == null) + { + shell.SendText(player, "Only a player can run this command."); + return; + } + + if (player.AttachedEntity == null) + { + shell.SendText(player, "You have no entity."); + return; + } + + var manager = player.AttachedEntity.GetComponent(); + var hand = manager.PartDictionary.First(x => x.Value.PartType == BodyPartType.Hand); + manager.DisconnectBodyPart(hand.Value, true); + } + } } diff --git a/Content.Server/Health/BodySystem/BodyManagerComponent.cs b/Content.Server/Health/BodySystem/BodyManagerComponent.cs index 0ced485cfb..4533870859 100644 --- a/Content.Server/Health/BodySystem/BodyManagerComponent.cs +++ b/Content.Server/Health/BodySystem/BodyManagerComponent.cs @@ -8,6 +8,7 @@ using Content.Shared.BodySystem; using Robust.Shared.ViewVariables; using Robust.Shared.Interfaces.GameObjects; using System.Linq; +using Content.Server.Interfaces.GameObjects.Components.Interaction; namespace Content.Server.BodySystem { @@ -178,8 +179,7 @@ namespace Content.Server.BodySystem { ///////// Server-specific stuff ///////// - public override void ExposeData(ObjectSerializer serializer) - { + public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); serializer.DataReadWriteFunction( @@ -225,8 +225,16 @@ namespace Content.Server.BodySystem { if (!_prototypeManager.TryIndex(partID, out BodyPartPrototype newPartData)) { //Get the BodyPartPrototype corresponding to the BodyPart ID we grabbed. throw new InvalidOperationException("BodyPart prototype with ID " + partID + " could not be found!"); } - _partDictionary.Remove(slotName); //Try and remove an existing limb if that exists. - _partDictionary.Add(slotName, new BodyPart(newPartData)); //Add a new BodyPart with the BodyPartPrototype as a baseline to our BodyComponent. + + //Try and remove an existing limb if that exists. + if (_partDictionary.Remove(slotName, out var removedPart)) + { + BodyPartRemoved(removedPart, slotName); + } + + var addedPart = new BodyPart(newPartData); + _partDictionary.Add(slotName, addedPart); //Add a new BodyPart with the BodyPartPrototype as a baseline to our BodyComponent. + BodyPartAdded(addedPart, slotName); } } @@ -264,6 +272,8 @@ namespace Content.Server.BodySystem { if (TryGetBodyPart(slotName, out BodyPart result)) //And that nothing is in it return false; _partDictionary.Add(slotName, part); + BodyPartAdded(part, slotName); + return true; } /// @@ -316,7 +326,11 @@ namespace Content.Server.BodySystem { return; if (part != null) { string slotName = _partDictionary.FirstOrDefault(x => x.Value == part).Key; - _partDictionary.Remove(slotName); + if (_partDictionary.Remove(slotName, out var partRemoved)) + { + BodyPartRemoved(partRemoved, slotName); + } + if (TryGetBodyPartConnections(slotName, out List connections)) //Call disconnect on all limbs that were hanging off this limb. { foreach (string connectionName in connections) //This loop is an unoptimized travesty. TODO: optimize to be less shit @@ -340,7 +354,11 @@ namespace Content.Server.BodySystem { if (!TryGetBodyPart(name, out BodyPart part)) return; if (part != null) { - _partDictionary.Remove(name); + if (_partDictionary.Remove(name, out var partRemoved)) + { + BodyPartRemoved(partRemoved, name); + } + if (TryGetBodyPartConnections(name, out List connections)) { foreach (string connectionName in connections) { if (TryGetBodyPart(connectionName, out BodyPart result) && !ConnectedToCenterPart(result)) { @@ -355,5 +373,24 @@ namespace Content.Server.BodySystem { } } + private void BodyPartAdded(BodyPart part, string slotName) + { + var argsAdded = new BodyPartAddedEventArgs(part, slotName); + + foreach (var component in Owner.GetAllComponents().ToArray()) + { + component.BodyPartAdded(argsAdded); + } + } + + private void BodyPartRemoved(BodyPart part, string slotName) + { + var args = new BodyPartRemovedEventArgs(part, slotName); + + foreach (var component in Owner.GetAllComponents()) + { + component.BodyPartRemoved(args); + } + } } } diff --git a/Content.Server/Interfaces/GameObjects/Components/Interaction/IBodyPartAdded.cs b/Content.Server/Interfaces/GameObjects/Components/Interaction/IBodyPartAdded.cs new file mode 100644 index 0000000000..2a2a9b4ce8 --- /dev/null +++ b/Content.Server/Interfaces/GameObjects/Components/Interaction/IBodyPartAdded.cs @@ -0,0 +1,49 @@ +using System; +using Content.Server.BodySystem; + +namespace Content.Server.Interfaces.GameObjects.Components.Interaction +{ + /// + /// This interface gives components behavior when a body part + /// is added to their owning entity. + /// + public interface IBodyPartAdded + { + void BodyPartAdded(BodyPartAddedEventArgs eventArgs); + } + + public class BodyPartAddedEventArgs : EventArgs + { + public BodyPartAddedEventArgs(BodyPart part, string slotName) + { + Part = part; + SlotName = slotName; + } + + public BodyPart Part { get; } + + public string SlotName { get; } + } + + /// + /// This interface gives components behavior when a body part + /// is removed from their owning entity. + /// + public interface IBodyPartRemoved + { + void BodyPartRemoved(BodyPartRemovedEventArgs eventArgs); + } + + public class BodyPartRemovedEventArgs : EventArgs + { + public BodyPartRemovedEventArgs(BodyPart part, string slotName) + { + Part = part; + SlotName = slotName; + } + + public BodyPart Part { get; } + + public string SlotName { get; } + } +} diff --git a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs index b335f50f7c..1719ab7afc 100644 --- a/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs +++ b/Content.Server/Interfaces/GameObjects/Components/Items/IHandsComponent.cs @@ -7,14 +7,14 @@ using Robust.Server.GameObjects.EntitySystemMessages; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Map; -namespace Content.Server.Interfaces.GameObjects +namespace Content.Server.Interfaces.GameObjects.Components.Items { public interface IHandsComponent : IComponent { /// - /// The hand index of the currently active hand. + /// The hand name of the currently active hand. /// - string ActiveIndex { get; set; } + string ActiveHand { get; set; } /// /// Enumerates over every held item. @@ -24,9 +24,9 @@ namespace Content.Server.Interfaces.GameObjects /// /// Gets the item held by a hand. /// - /// The index of the hand to get. + /// The name of the hand to get. /// The item in the held, null if no item is held - ItemComponent GetHand(string index); + ItemComponent GetItem(string handName); /// /// Gets item held by the current active hand @@ -44,7 +44,7 @@ namespace Content.Server.Interfaces.GameObjects /// Puts an item into a specific hand. /// /// The item to put in the hand. - /// The index of the hand to put the item into. + /// The name of the hand to put the item into. /// /// If true and the provided hand is full, the method will fall back to /// @@ -62,20 +62,22 @@ namespace Content.Server.Interfaces.GameObjects /// Checks to see if an item can be put in the specified hand. /// /// The item to check for. - /// The index for the hand to check for. + /// The name for the hand to check for. /// True if the item can be inserted, false otherwise. bool CanPutInHand(ItemComponent item, string index); /// /// Finds the hand slot holding the specified entity, if any. /// - /// - /// The entity to look for in our hands. + /// The entity to look for in our hands. + /// + /// The name of the hand slot if the entity is indeed held, + /// otherwise. /// /// - /// The index of the hand slot if the entity is indeed held, otherwise. + /// true if the entity is held, false otherwise /// - string FindHand(IEntity entity); + bool TryHand(IEntity entity, out string handName); /// /// Drops the item contained in the slot to the same position as our entity. @@ -135,7 +137,7 @@ namespace Content.Server.Interfaces.GameObjects /// /// The slot of which to drop the entity. /// The container to drop into. - /// Whether to check the for the mob or not. + /// Whether to check the for the mob or not. /// True on success, false if something was blocked (insertion or removal). /// /// Thrown if dry-run checks reported OK to remove and insert, @@ -167,20 +169,20 @@ namespace Content.Server.Interfaces.GameObjects /// /// Checks whether the item in the specified hand can be dropped. /// - /// The hand to check for. + /// The hand to check for. /// /// True if the item can be dropped, false if the hand is empty or the item in the hand cannot be dropped. /// - bool CanDrop(string index); + bool CanDrop(string name); /// /// Adds a new hand to this hands component. /// - /// The name of the hand to add. + /// The name of the hand to add. /// /// Thrown if a hand with specified name already exists. /// - void AddHand(string index); + void AddHand(string name); /// /// Removes a hand from this hands component. @@ -188,15 +190,15 @@ namespace Content.Server.Interfaces.GameObjects /// /// If the hand contains an item, the item is dropped. /// - /// The name of the hand to remove. - void RemoveHand(string index); + /// The name of the hand to remove. + void RemoveHand(string name); /// /// Checks whether a hand with the specified name exists. /// - /// The hand name to check. + /// The hand name to check. /// True if the hand exists, false otherwise. - bool HasHand(string index); + bool HasHand(string name); void HandleSlotModifiedMaybe(ContainerModifiedMessage message); } diff --git a/Content.Server/Mobs/StandingStateHelper.cs b/Content.Server/Mobs/StandingStateHelper.cs index 4bd0d2ae1d..6a43352fa8 100644 --- a/Content.Server/Mobs/StandingStateHelper.cs +++ b/Content.Server/Mobs/StandingStateHelper.cs @@ -1,4 +1,5 @@ using Content.Server.Interfaces.GameObjects; +using Content.Server.Interfaces.GameObjects.Components.Items; using Content.Shared.Audio; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystems; diff --git a/Content.Server/PDA/PDAUplinkManager.cs b/Content.Server/PDA/PDAUplinkManager.cs index b627b92f67..53b9aa2e05 100644 --- a/Content.Server/PDA/PDAUplinkManager.cs +++ b/Content.Server/PDA/PDAUplinkManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; using Content.Server.Interfaces.PDA; using Content.Shared.GameObjects.Components.PDA; diff --git a/Content.Server/Sandbox/SandboxManager.cs b/Content.Server/Sandbox/SandboxManager.cs index a0cdebba04..e695a517c3 100644 --- a/Content.Server/Sandbox/SandboxManager.cs +++ b/Content.Server/Sandbox/SandboxManager.cs @@ -1,5 +1,6 @@ using Content.Server.GameObjects; using Content.Server.GameObjects.Components; +using Content.Server.GameObjects.Components.GUI; using Content.Server.GameTicking; using Content.Server.Interfaces.GameTicking; using Content.Shared.Sandbox; diff --git a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs index dc3604eae6..e445c587a7 100644 --- a/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs +++ b/Content.Shared/GameObjects/Components/Items/SharedHandsComponent.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Serialization; -namespace Content.Shared.GameObjects +namespace Content.Shared.GameObjects.Components.Items { public abstract class SharedHandsComponent : Component { @@ -11,14 +13,31 @@ namespace Content.Shared.GameObjects public sealed override uint? NetID => ContentNetIDs.HANDS; } + [Serializable, NetSerializable] + public sealed class SharedHand + { + public readonly int Index; + public readonly string Name; + public readonly EntityUid? EntityUid; + public readonly HandLocation Location; + + public SharedHand(int index, string name, EntityUid? entityUid, HandLocation location) + { + Index = index; + Name = name; + EntityUid = entityUid; + Location = location; + } + } + // The IDs of the items get synced over the network. [Serializable, NetSerializable] public class HandsComponentState : ComponentState { - public readonly Dictionary Hands; + public readonly SharedHand[] Hands; public readonly string ActiveIndex; - public HandsComponentState(Dictionary hands, string activeIndex) : base(ContentNetIDs.HANDS) + public HandsComponentState(SharedHand[] hands, string activeIndex) : base(ContentNetIDs.HANDS) { Hands = hands; ActiveIndex = activeIndex; @@ -75,4 +94,11 @@ namespace Content.Shared.GameObjects Index = index; } } + + public enum HandLocation : byte + { + Left, + Middle, + Right + } } diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index 53c2f38ae9..6ff7550a31 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -86,6 +86,8 @@ - deleteewc - asay - mapping + - addhand + - removehand CanViewVar: true CanAdminPlace: true @@ -159,6 +161,8 @@ - sudo - asay - mapping + - addhand + - removehand CanViewVar: true CanAdminPlace: true CanScript: true diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml index da6be8b7e5..1b3fecdafb 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml @@ -10,9 +10,6 @@ - type: AiController logic: Mimic - type: Hands - hands: - - left - - right - type: MovementSpeedModifier - type: InteractionOutline - type: Sprite diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 269894370a..5120c73756 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -10,9 +10,6 @@ - type: AiController logic: Xeno - type: Hands - hands: - - left - - right - type: MovementSpeedModifier - type: InteractionOutline - type: Sprite diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index a342c1f408..08a73057fd 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -10,9 +10,6 @@ components: - type: Flashable - type: Hands - hands: - - left - - right - type: MovementSpeedModifier - type: Hunger - type: Thirst @@ -150,10 +147,6 @@ description: A dummy human meant to be used in character setup components: - type: Hands - hands: - - left - - right - - type: Inventory - type: Sprite netsync: false diff --git a/Resources/Textures/Interface/Inventory/hand_l_no_letter.png b/Resources/Textures/Interface/Inventory/hand_l_no_letter.png new file mode 100644 index 0000000000..fbf564c9d9 Binary files /dev/null and b/Resources/Textures/Interface/Inventory/hand_l_no_letter.png differ diff --git a/Resources/Textures/Interface/Inventory/hand_middle.png b/Resources/Textures/Interface/Inventory/hand_middle.png new file mode 100644 index 0000000000..41284017af Binary files /dev/null and b/Resources/Textures/Interface/Inventory/hand_middle.png differ diff --git a/Resources/Textures/Interface/Inventory/hand_r_no_letter.png b/Resources/Textures/Interface/Inventory/hand_r_no_letter.png new file mode 100644 index 0000000000..82412561fb Binary files /dev/null and b/Resources/Textures/Interface/Inventory/hand_r_no_letter.png differ