Add changing the amount of hands on the GUI depending on your body parts (#1406)

* Multiple hands in gui first pass

* Remove IHandsComponent interface

* Create hand class and more hand textures

* Refactor ServerHandsComponent to use a single list of hands

* Seal SharedHand

* Fix picked up items not showing on top of the hand buttons

* Remove HandsGui buttons and panels dictionaries

* Fix items in hands rendering

* Fix wrong hand container comparison

* Fix not updating the location of duplicate hands

* Change ClientHandsComponent to use a SortedList instead of a dictionary

* More merge conflict fixes

* Change SortedList to List

* Fix hand button order

* Add item tooltip for more than 2 hands and updating when removing hands

* Add add hand and remove hand command

* Merge conflict fixes

* Remove nullable reference type from ContainerSlot

* Fix texture errors

* Fix error when reaching 0 hands

* Fix error when swapping hands with no hands

* Merged remove hand methods

* Fix item panel texture errors

* Merge conflict fixes

* Fix addhand and removehand command descriptions

* Add properly displaying tooltips for 2 hands

* Make hand indexes and locations consistent across the client and server

* Add dropping held entity if a hand is removed

* Change hand location to be calculated by index

* Made different hand gui updates more consistent

* Remove human body yml testing changes

* Sanitize addhand and removehand commands

* Merge conflict fixes

* Remove testing changes

* Revert body system changes

* Add missing imports

* Remove obsolete hands parameter in yml files

* Fix broken import

* Fix startup error and adding and removing hands on the same tick

* Make hand container id use an uint

In case someone gets more than 2 billion hands

* Rename hand component files

* Make hands state use an array
This commit is contained in:
DrSmugleaf
2020-07-25 15:11:16 +02:00
committed by GitHub
parent 3a4ad42c80
commit 4b4e83d2bf
74 changed files with 1106 additions and 558 deletions

View File

@@ -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;

View File

@@ -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<string, IEntity> _hands = new Dictionary<string, IEntity>();
[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<string>(),
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));
}
}
}

View File

@@ -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<Hand> _hands = new List<Hand>();
[ViewVariables] public IReadOnlyList<Hand> 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; }
}
}

View File

@@ -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))]

View File

@@ -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<IPlayerManager>().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<IPlayerManager>().LocalPlayer.ControlledEntity;
if (controlledEntity.TryGetComponent(out IHandsComponent hands))
if (controlledEntity.TryGetComponent(out HandsComponent hands))
{
StorageEntity.SendNetworkMessage(new InsertEntityMessage());
}

View File

@@ -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
{
@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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; }
}
}

View File

@@ -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);
}
}
/// <summary>
/// Gets the hands component controling this gui, returns true if successful and false if failure
/// Adds a new hand to this control
/// </summary>
/// <param name="hand">The hand to add to this control</param>
/// <param name="buttonLocation">
/// The actual location of the button. The right hand is drawn
/// on the LEFT of the screen.
/// </param>
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);
}
}
/// <summary>
/// Gets the hands component controlling this gui
/// </summary>
/// <param name="hands"></param>
/// <returns></returns>
private bool TryGetHands(out IHandsComponent hands)
/// <returns>true if successful and false if failure</returns>
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);
// TODO: Remove button on remove hand
ActiveHandRect.Parent.RemoveChild(ActiveHandRect);
var parent = hands.ActiveIndex == HandNameLeft ? _leftButton : _rightButton;
parent.AddChild(ActiveHandRect);
ActiveHandRect.SetPositionInParent(1);
if (left != _leftHand)
var hands = component.Hands.OrderByDescending(x => x.Location).ToArray();
for (var i = 0; i < hands.Length; i++)
{
_leftHand = left;
_itemSlotManager.SetItemSlot(_leftButton, _leftHand);
}
var hand = hands[i];
if (right != _rightHand)
if (hand.Button == null)
{
_rightHand = right;
_itemSlotManager.SetItemSlot(_rightButton, _rightHand);
}
AddHand(hand, hand.Location);
}
private void HandKeyBindDown(GUIBoundKeyEventArgs args, string handIndex)
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 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();
}
}
}

View File

@@ -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; }

View File

@@ -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)
/// <summary>
/// Creates a new instance of <see cref="ItemStatusPanel"/>
/// based on whether or not it is being created for the right
/// or left hand.
/// </summary>
/// <param name="location">
/// The location of the hand that this panel is for
/// </param>
/// <returns>the new <see cref="ItemStatusPanel"/> instance</returns>
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<IItemStatus>())
foreach (var statusComponent in _entity!.GetAllComponents<IItemStatus>())
{
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));
}
}
}

View File

@@ -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;

View File

@@ -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
/// <returns></returns>
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;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -1,4 +1,5 @@
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.GUI;
using JetBrains.Annotations;
using Robust.Shared.Interfaces.GameObjects;

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<string, ContainerSlot> _hands = new Dictionary<string, ContainerSlot>();
[ViewVariables] private List<string> _orderedHands = new List<string>();
[ViewVariables] private readonly List<Hand> _hands = new List<Hand>();
// 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<string>(0),
hands => hands.ForEach(AddHand),
() => _orderedHands);
serializer.DataField(ref _activeIndex, "defaultHand", _orderedHands.LastOrDefault());
base.Startup();
ActiveHand = _hands.LastOrDefault()?.Name;
}
public IEnumerable<ItemComponent> 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<ItemComponent>();
yield return hand.Entity.GetComponent<ItemComponent>();
}
}
}
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<ItemComponent>();
return _hands.FirstOrDefault(hand => hand.Name == name);
}
public ItemComponent GetActiveHand => GetHand(ActiveIndex);
public ItemComponent? GetItem(string handName)
{
return GetHand(handName)?.Entity?.GetComponent<ItemComponent>();
}
public ItemComponent? GetActiveHand => ActiveHand == null
? null
: GetItem(ActiveHand);
/// <summary>
/// Enumerates over the hand keys, returning the active hand first.
/// </summary>
public IEnumerable<string> 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<ItemComponent>();
var item = hand.Entity.GetComponent<ItemComponent>();
if (!inventorySlot.Remove(inventorySlot.ContainedEntity))
if (!hand.Container.Remove(hand.Entity))
{
return false;
}
if (doMobChecks && !_entitySystemManager.GetEntitySystem<InteractionSystem>().TryDroppedInteraction(Owner, item.Owner))
return false;
if (ContainerHelpers.TryGetContainer(Owner, out var container) &&
!container.Insert(item.Owner))
if (doMobChecks &&
!_entitySystemManager.GetEntitySystem<InteractionSystem>().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<ItemComponent>();
var item = hand.Entity.GetComponent<ItemComponent>();
if (doMobChecks && !_entitySystemManager.GetEntitySystem<InteractionSystem>().TryDroppedInteraction(Owner, item.Owner))
return false;
if (!inventorySlot.Remove(inventorySlot.ContainedEntity))
if (doMobChecks &&
!_entitySystemManager.GetEntitySystem<InteractionSystem>().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<ItemComponent>();
var item = hand.Entity.GetComponent<ItemComponent>();
if (doMobChecks && !_entitySystemManager.GetEntitySystem<InteractionSystem>().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
/// <summary>
/// Checks whether an item can be dropped from the specified slot.
/// </summary>
/// <param name="slot">The slot to check for.</param>
/// <param name="name">The slot to check for.</param>
/// <returns>
/// True if there is an item in the slot and it can be dropped, false otherwise.
/// </returns>
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<ContainerSlot>(Name + "_" + index, Owner);
_hands[index] = slot;
if (!_orderedHands.Contains(index))
{
_orderedHands.Add(index);
}
var container = ContainerManagerComponent.Create<ContainerSlot>($"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);
}
/// <summary>
/// Get the name of the slot passed to the inventory component.
/// </summary>
private string HandSlotName(string index) => $"_hand_{index}";
public override ComponentState GetComponentState()
{
var dict = new Dictionary<string, EntityUid>(_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<InteractionSystem>();
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);
}
}
}

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<IHandsComponent>();
hands.PutInHand(this, hands.ActiveIndex, fallback: false);
hands.PutInHand(this, hands.ActiveHand, false);
return true;
}

View File

@@ -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;

View File

@@ -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<HandsComponent>().GetActiveHand.Owner;
var itemEntity = eventArgs.User.GetComponent<HandsComponent>().GetActiveHand?.Owner;
if(itemEntity.TryGetComponent<PourableComponent>(out var attackPourable))
if (itemEntity == null)
{
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You have no active hand!"));
return false;
}
if (itemEntity.TryGetComponent<PourableComponent>(out var attackPourable))
{
if (!itemEntity.TryGetComponent<SolutionComponent>(out var attackSolution)
|| !attackSolution.CanPourOut)

View File

@@ -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;

View File

@@ -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<ItemComponent>());
_count--;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<InteractionSystem>();
var interactionSystem = Get<InteractionSystem>();
var oldItem = handsComp.GetActiveHand;
@@ -97,12 +101,16 @@ 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<SharedInteractionSystem>().UnobstructedRayLength(ent.Transform.MapPosition, newCoords.ToMap(_mapManager), ignoredEnt: ent);
var rayLength = Get<SharedInteractionSystem>().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)
{

View File

@@ -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;

View File

@@ -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<BodyManagerComponent>();
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<BodyManagerComponent>();
var hand = manager.PartDictionary.First(x => x.Value.PartType == BodyPartType.Hand);
manager.DisconnectBodyPart(hand.Value, true);
}
}
}

View File

@@ -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;
}
/// <summary>
@@ -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<string> 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<string> 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<IBodyPartAdded>().ToArray())
{
component.BodyPartAdded(argsAdded);
}
}
private void BodyPartRemoved(BodyPart part, string slotName)
{
var args = new BodyPartRemovedEventArgs(part, slotName);
foreach (var component in Owner.GetAllComponents<IBodyPartRemoved>())
{
component.BodyPartRemoved(args);
}
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using Content.Server.BodySystem;
namespace Content.Server.Interfaces.GameObjects.Components.Interaction
{
/// <summary>
/// This interface gives components behavior when a body part
/// is added to their owning entity.
/// </summary>
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; }
}
/// <summary>
/// This interface gives components behavior when a body part
/// is removed from their owning entity.
/// </summary>
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; }
}
}

View File

@@ -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
{
/// <summary>
/// The hand index of the currently active hand.
/// The hand name of the currently active hand.
/// </summary>
string ActiveIndex { get; set; }
string ActiveHand { get; set; }
/// <summary>
/// Enumerates over every held item.
@@ -24,9 +24,9 @@ namespace Content.Server.Interfaces.GameObjects
/// <summary>
/// Gets the item held by a hand.
/// </summary>
/// <param name="index">The index of the hand to get.</param>
/// <param name="handName">The name of the hand to get.</param>
/// <returns>The item in the held, null if no item is held</returns>
ItemComponent GetHand(string index);
ItemComponent GetItem(string handName);
/// <summary>
/// Gets item held by the current active hand
@@ -44,7 +44,7 @@ namespace Content.Server.Interfaces.GameObjects
/// Puts an item into a specific hand.
/// </summary>
/// <param name="item">The item to put in the hand.</param>
/// <param name="index">The index of the hand to put the item into.</param>
/// <param name="index">The name of the hand to put the item into.</param>
/// <param name="fallback">
/// If true and the provided hand is full, the method will fall back to <see cref="PutInHand(ItemComponent)" />
/// </param>
@@ -62,20 +62,22 @@ namespace Content.Server.Interfaces.GameObjects
/// Checks to see if an item can be put in the specified hand.
/// </summary>
/// <param name="item">The item to check for.</param>
/// <param name="index">The index for the hand to check for.</param>
/// <param name="index">The name for the hand to check for.</param>
/// <returns>True if the item can be inserted, false otherwise.</returns>
bool CanPutInHand(ItemComponent item, string index);
/// <summary>
/// Finds the hand slot holding the specified entity, if any.
/// </summary>
/// <param name="entity">
/// The entity to look for in our hands.
/// <param name="entity">The entity to look for in our hands.</param>
/// <param name="handName">
/// The name of the hand slot if the entity is indeed held,
/// <see langword="null" /> otherwise.
/// </param>
/// <returns>
/// The index of the hand slot if the entity is indeed held, <see langword="null" /> otherwise.
/// true if the entity is held, false otherwise
/// </returns>
string FindHand(IEntity entity);
bool TryHand(IEntity entity, out string handName);
/// <summary>
/// Drops the item contained in the slot to the same position as our entity.
@@ -135,7 +137,7 @@ namespace Content.Server.Interfaces.GameObjects
/// </summary>
/// <param name="slot">The slot of which to drop the entity.</param>
/// <param name="targetContainer">The container to drop into.</param>
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop(IEntity)"/> for the mob or not.</param>
/// <returns>True on success, false if something was blocked (insertion or removal).</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if dry-run checks reported OK to remove and insert,
@@ -167,20 +169,20 @@ namespace Content.Server.Interfaces.GameObjects
/// <summary>
/// Checks whether the item in the specified hand can be dropped.
/// </summary>
/// <param name="index">The hand to check for.</param>
/// <param name="name">The hand to check for.</param>
/// <returns>
/// True if the item can be dropped, false if the hand is empty or the item in the hand cannot be dropped.
/// </returns>
bool CanDrop(string index);
bool CanDrop(string name);
/// <summary>
/// Adds a new hand to this hands component.
/// </summary>
/// <param name="index">The name of the hand to add.</param>
/// <param name="name">The name of the hand to add.</param>
/// <exception cref="InvalidOperationException">
/// Thrown if a hand with specified name already exists.
/// </exception>
void AddHand(string index);
void AddHand(string name);
/// <summary>
/// Removes a hand from this hands component.
@@ -188,15 +190,15 @@ namespace Content.Server.Interfaces.GameObjects
/// <remarks>
/// If the hand contains an item, the item is dropped.
/// </remarks>
/// <param name="index">The name of the hand to remove.</param>
void RemoveHand(string index);
/// <param name="name">The name of the hand to remove.</param>
void RemoveHand(string name);
/// <summary>
/// Checks whether a hand with the specified name exists.
/// </summary>
/// <param name="index">The hand name to check.</param>
/// <param name="name">The hand name to check.</param>
/// <returns>True if the hand exists, false otherwise.</returns>
bool HasHand(string index);
bool HasHand(string name);
void HandleSlotModifiedMaybe(ContainerModifiedMessage message);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<string, EntityUid> Hands;
public readonly SharedHand[] Hands;
public readonly string ActiveIndex;
public HandsComponentState(Dictionary<string, EntityUid> 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
}
}

View File

@@ -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

View File

@@ -10,9 +10,6 @@
- type: AiController
logic: Mimic
- type: Hands
hands:
- left
- right
- type: MovementSpeedModifier
- type: InteractionOutline
- type: Sprite

View File

@@ -10,9 +10,6 @@
- type: AiController
logic: Xeno
- type: Hands
hands:
- left
- right
- type: MovementSpeedModifier
- type: InteractionOutline
- type: Sprite

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB