HandsSystem Refactor (#38438)
* checkpoint * pt 2 * pt... i forgot * pt 4 * patch * More test fixes * optimization!!! * the REAL hand system * fix RetractableItemActionSystem.cs oversight * the review * test * remove test usage of body prototype * Update Content.IntegrationTests/Tests/Interaction/InteractionTest.cs Co-authored-by: Tayrtahn <tayrtahn@gmail.com> * hellcode * hellcode 2 * Minor cleanup * test * Chasing the last of the bugs * changes --------- Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
This commit is contained in:
@@ -16,7 +16,6 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Hands.Systems
|
||||
@@ -27,16 +26,13 @@ namespace Content.Client.Hands.Systems
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly StrippableSystem _stripSys = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
[Dependency] private readonly ExamineSystem _examine = default!;
|
||||
[Dependency] private readonly DisplacementMapSystem _displacement = default!;
|
||||
|
||||
public event Action<string, HandLocation>? OnPlayerAddHand;
|
||||
public event Action<string>? OnPlayerRemoveHand;
|
||||
public event Action<string?>? OnPlayerSetActiveHand;
|
||||
public event Action<HandsComponent>? OnPlayerHandsAdded;
|
||||
public event Action<Entity<HandsComponent>>? OnPlayerHandsAdded;
|
||||
public event Action? OnPlayerHandsRemoved;
|
||||
public event Action<string, EntityUid>? OnPlayerItemAdded;
|
||||
public event Action<string, EntityUid>? OnPlayerItemRemoved;
|
||||
@@ -58,67 +54,28 @@ namespace Content.Client.Hands.Systems
|
||||
}
|
||||
|
||||
#region StateHandling
|
||||
private void HandleComponentState(EntityUid uid, HandsComponent component, ref ComponentHandleState args)
|
||||
private void HandleComponentState(Entity<HandsComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not HandsComponentState state)
|
||||
return;
|
||||
|
||||
var handsModified = component.Hands.Count != state.Hands.Count;
|
||||
// we need to check that, even if we have the same amount, that the individual hands didn't change.
|
||||
if (!handsModified)
|
||||
var newHands = state.Hands.Keys.Except(ent.Comp.Hands.Keys); // hands that were added between states
|
||||
var oldHands = ent.Comp.Hands.Keys.Except(state.Hands.Keys); // hands that were removed between states
|
||||
|
||||
foreach (var handId in oldHands)
|
||||
{
|
||||
foreach (var hand in component.Hands.Values)
|
||||
{
|
||||
if (state.Hands.Contains(hand))
|
||||
continue;
|
||||
handsModified = true;
|
||||
break;
|
||||
}
|
||||
RemoveHand(ent.AsNullable(), handId);
|
||||
}
|
||||
|
||||
var manager = EnsureComp<ContainerManagerComponent>(uid);
|
||||
|
||||
if (handsModified)
|
||||
foreach (var handId in state.SortedHands.Intersect(newHands))
|
||||
{
|
||||
List<Hand> addedHands = new();
|
||||
foreach (var hand in state.Hands)
|
||||
{
|
||||
if (component.Hands.ContainsKey(hand.Name))
|
||||
continue;
|
||||
|
||||
var container = _containerSystem.EnsureContainer<ContainerSlot>(uid, hand.Name, manager);
|
||||
var newHand = new Hand(hand.Name, hand.Location, container);
|
||||
component.Hands.Add(hand.Name, newHand);
|
||||
addedHands.Add(newHand);
|
||||
AddHand(ent.AsNullable(), handId, state.Hands[handId]);
|
||||
}
|
||||
ent.Comp.SortedHands = new (state.SortedHands);
|
||||
|
||||
foreach (var name in component.Hands.Keys)
|
||||
{
|
||||
if (!state.HandNames.Contains(name))
|
||||
{
|
||||
RemoveHand(uid, name, component);
|
||||
}
|
||||
}
|
||||
SetActiveHand(ent.AsNullable(), state.ActiveHandId);
|
||||
|
||||
component.SortedHands.Clear();
|
||||
component.SortedHands.AddRange(state.HandNames);
|
||||
var sorted = addedHands.OrderBy(hand => component.SortedHands.IndexOf(hand.Name));
|
||||
|
||||
foreach (var hand in sorted)
|
||||
{
|
||||
AddHand(uid, hand, component);
|
||||
}
|
||||
}
|
||||
|
||||
_stripSys.UpdateUi(uid);
|
||||
|
||||
if (component.ActiveHand == null && state.ActiveHand == null)
|
||||
return; //edge case
|
||||
|
||||
if (component.ActiveHand != null && state.ActiveHand != component.ActiveHand.Name)
|
||||
{
|
||||
SetActiveHand(uid, component.Hands[state.ActiveHand!], component);
|
||||
}
|
||||
_stripSys.UpdateUi(ent);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -129,47 +86,52 @@ namespace Content.Client.Hands.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
OnPlayerHandsAdded?.Invoke(hands);
|
||||
OnPlayerHandsAdded?.Invoke(hands.Value);
|
||||
}
|
||||
|
||||
public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true)
|
||||
public override void DoDrop(Entity<HandsComponent?> ent,
|
||||
string handId,
|
||||
bool doDropInteraction = true,
|
||||
bool log = true)
|
||||
{
|
||||
base.DoDrop(uid, hand, doDropInteraction, hands, log);
|
||||
base.DoDrop(ent, handId, doDropInteraction, log);
|
||||
|
||||
if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
|
||||
if (TryGetHeldItem(ent, handId, out var held) && TryComp(held, out SpriteComponent? sprite))
|
||||
sprite.RenderOrder = EntityManager.CurrentTick.Value;
|
||||
}
|
||||
|
||||
public EntityUid? GetActiveHandEntity()
|
||||
{
|
||||
return TryGetPlayerHands(out var hands) ? hands.ActiveHandEntity : null;
|
||||
return TryGetPlayerHands(out var hands) ? GetActiveItem(hands.Value.AsNullable()) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the hands component of the local player
|
||||
/// </summary>
|
||||
public bool TryGetPlayerHands([NotNullWhen(true)] out HandsComponent? hands)
|
||||
public bool TryGetPlayerHands([NotNullWhen(true)] out Entity<HandsComponent>? hands)
|
||||
{
|
||||
var player = _playerManager.LocalEntity;
|
||||
hands = null;
|
||||
return player != null && TryComp(player.Value, out hands);
|
||||
if (player == null || !TryComp<HandsComponent>(player.Value, out var handsComp))
|
||||
return false;
|
||||
|
||||
hands = (player.Value, handsComp);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a user clicked on their hands GUI
|
||||
/// </summary>
|
||||
public void UIHandClick(HandsComponent hands, string handName)
|
||||
public void UIHandClick(Entity<HandsComponent> ent, string handName)
|
||||
{
|
||||
if (!hands.Hands.TryGetValue(handName, out var pressedHand))
|
||||
var hands = ent.Comp;
|
||||
if (hands.ActiveHandId == null)
|
||||
return;
|
||||
|
||||
if (hands.ActiveHand == null)
|
||||
return;
|
||||
var pressedEntity = GetHeldItem(ent.AsNullable(), handName);
|
||||
var activeEntity = GetActiveItem(ent.AsNullable());
|
||||
|
||||
var pressedEntity = pressedHand.HeldEntity;
|
||||
var activeEntity = hands.ActiveHand.HeldEntity;
|
||||
|
||||
if (pressedHand == hands.ActiveHand && activeEntity != null)
|
||||
if (handName == hands.ActiveHandId && activeEntity != null)
|
||||
{
|
||||
// use item in hand
|
||||
// it will always be attack_self() in my heart.
|
||||
@@ -177,24 +139,24 @@ namespace Content.Client.Hands.Systems
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressedHand != hands.ActiveHand && pressedEntity == null)
|
||||
if (handName != hands.ActiveHandId && pressedEntity == null)
|
||||
{
|
||||
// change active hand
|
||||
EntityManager.RaisePredictiveEvent(new RequestSetHandEvent(handName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity != null)
|
||||
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity != null)
|
||||
{
|
||||
// use active item on held item
|
||||
EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(pressedHand.Name));
|
||||
EntityManager.RaisePredictiveEvent(new RequestHandInteractUsingEvent(handName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressedHand != hands.ActiveHand && pressedEntity != null && activeEntity == null)
|
||||
if (handName != hands.ActiveHandId && pressedEntity != null && activeEntity == null)
|
||||
{
|
||||
// move the item to the active hand
|
||||
EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(pressedHand.Name));
|
||||
EntityManager.RaisePredictiveEvent(new RequestMoveHandItemEvent(handName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,13 +172,12 @@ namespace Content.Client.Hands.Systems
|
||||
public void UIInventoryExamine(string handName)
|
||||
{
|
||||
if (!TryGetPlayerHands(out var hands) ||
|
||||
!hands.Hands.TryGetValue(handName, out var hand) ||
|
||||
hand.HeldEntity is not { Valid: true } entity)
|
||||
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_examine.DoExamine(entity);
|
||||
_examine.DoExamine(heldEntity.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -226,13 +187,12 @@ namespace Content.Client.Hands.Systems
|
||||
public void UIHandOpenContextMenu(string handName)
|
||||
{
|
||||
if (!TryGetPlayerHands(out var hands) ||
|
||||
!hands.Hands.TryGetValue(handName, out var hand) ||
|
||||
hand.HeldEntity is not { Valid: true } entity)
|
||||
!TryGetHeldItem(hands.Value.AsNullable(), handName, out var heldEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(entity);
|
||||
_ui.GetUIController<VerbMenuUIController>().OpenVerbMenu(heldEntity.Value);
|
||||
}
|
||||
|
||||
public void UIHandAltActivateItem(string handName)
|
||||
@@ -246,60 +206,67 @@ namespace Content.Client.Hands.Systems
|
||||
{
|
||||
base.HandleEntityInserted(uid, hands, args);
|
||||
|
||||
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
|
||||
if (!hands.Hands.ContainsKey(args.Container.ID))
|
||||
return;
|
||||
UpdateHandVisuals(uid, args.Entity, hand);
|
||||
|
||||
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
|
||||
_stripSys.UpdateUi(uid);
|
||||
|
||||
if (uid != _playerManager.LocalEntity)
|
||||
return;
|
||||
|
||||
OnPlayerItemAdded?.Invoke(hand.Name, args.Entity);
|
||||
OnPlayerItemAdded?.Invoke(args.Container.ID, args.Entity);
|
||||
|
||||
if (HasComp<VirtualItemComponent>(args.Entity))
|
||||
OnPlayerHandBlocked?.Invoke(hand.Name);
|
||||
OnPlayerHandBlocked?.Invoke(args.Container.ID);
|
||||
}
|
||||
|
||||
protected override void HandleEntityRemoved(EntityUid uid, HandsComponent hands, EntRemovedFromContainerMessage args)
|
||||
{
|
||||
base.HandleEntityRemoved(uid, hands, args);
|
||||
|
||||
if (!hands.Hands.TryGetValue(args.Container.ID, out var hand))
|
||||
if (!hands.Hands.ContainsKey(args.Container.ID))
|
||||
return;
|
||||
UpdateHandVisuals(uid, args.Entity, hand);
|
||||
|
||||
UpdateHandVisuals(uid, args.Entity, args.Container.ID);
|
||||
_stripSys.UpdateUi(uid);
|
||||
|
||||
if (uid != _playerManager.LocalEntity)
|
||||
return;
|
||||
|
||||
OnPlayerItemRemoved?.Invoke(hand.Name, args.Entity);
|
||||
OnPlayerItemRemoved?.Invoke(args.Container.ID, args.Entity);
|
||||
|
||||
if (HasComp<VirtualItemComponent>(args.Entity))
|
||||
OnPlayerHandUnblocked?.Invoke(hand.Name);
|
||||
OnPlayerHandUnblocked?.Invoke(args.Container.ID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the players sprite with new in-hand visuals.
|
||||
/// </summary>
|
||||
private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsComponent? handComp = null, SpriteComponent? sprite = null)
|
||||
private void UpdateHandVisuals(Entity<HandsComponent?, SpriteComponent?> ent, EntityUid held, string handId)
|
||||
{
|
||||
if (!Resolve(uid, ref handComp, ref sprite, false))
|
||||
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false))
|
||||
return;
|
||||
var handComp = ent.Comp1;
|
||||
var sprite = ent.Comp2;
|
||||
|
||||
if (!TryGetHand((ent, handComp), handId, out var hand))
|
||||
return;
|
||||
|
||||
// visual update might involve changes to the entity's effective sprite -> need to update hands GUI.
|
||||
if (uid == _playerManager.LocalEntity)
|
||||
OnPlayerItemAdded?.Invoke(hand.Name, held);
|
||||
if (ent == _playerManager.LocalEntity)
|
||||
OnPlayerItemAdded?.Invoke(handId, held);
|
||||
|
||||
if (!handComp.ShowInHands)
|
||||
return;
|
||||
|
||||
// Remove old layers. We could also just set them to invisible, but as items may add arbitrary layers, this
|
||||
// may eventually bloat the player with lots of layers.
|
||||
if (handComp.RevealedLayers.TryGetValue(hand.Location, out var revealedLayers))
|
||||
if (handComp.RevealedLayers.TryGetValue(hand.Value.Location, out var revealedLayers))
|
||||
{
|
||||
foreach (var key in revealedLayers)
|
||||
{
|
||||
_sprite.RemoveLayer((uid, sprite), key);
|
||||
_sprite.RemoveLayer((ent, sprite), key);
|
||||
}
|
||||
|
||||
revealedLayers.Clear();
|
||||
@@ -307,22 +274,22 @@ namespace Content.Client.Hands.Systems
|
||||
else
|
||||
{
|
||||
revealedLayers = new();
|
||||
handComp.RevealedLayers[hand.Location] = revealedLayers;
|
||||
handComp.RevealedLayers[hand.Value.Location] = revealedLayers;
|
||||
}
|
||||
|
||||
if (hand.HeldEntity == null)
|
||||
if (HandIsEmpty((ent, handComp), handId))
|
||||
{
|
||||
// the held item was removed.
|
||||
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
|
||||
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new GetInhandVisualsEvent(uid, hand.Location);
|
||||
var ev = new GetInhandVisualsEvent(ent, hand.Value.Location);
|
||||
RaiseLocalEvent(held, ev);
|
||||
|
||||
if (ev.Layers.Count == 0)
|
||||
{
|
||||
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
|
||||
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -335,7 +302,7 @@ namespace Content.Client.Hands.Systems
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = _sprite.LayerMapReserve((uid, sprite), key);
|
||||
var index = _sprite.LayerMapReserve((ent, sprite), key);
|
||||
|
||||
// In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries.
|
||||
if (layerData.RsiPath == null
|
||||
@@ -343,35 +310,34 @@ namespace Content.Client.Hands.Systems
|
||||
&& sprite[index].Rsi == null)
|
||||
{
|
||||
if (TryComp<ItemComponent>(held, out var itemComponent) && itemComponent.RsiPath != null)
|
||||
_sprite.LayerSetRsi((uid, sprite), index, new ResPath(itemComponent.RsiPath));
|
||||
_sprite.LayerSetRsi((ent, sprite), index, new ResPath(itemComponent.RsiPath));
|
||||
else if (TryComp(held, out SpriteComponent? clothingSprite))
|
||||
_sprite.LayerSetRsi((uid, sprite), index, clothingSprite.BaseRSI);
|
||||
_sprite.LayerSetRsi((ent, sprite), index, clothingSprite.BaseRSI);
|
||||
}
|
||||
|
||||
_sprite.LayerSetData((uid, sprite), index, layerData);
|
||||
_sprite.LayerSetData((ent, sprite), index, layerData);
|
||||
|
||||
// Add displacement maps
|
||||
var displacement = hand.Location switch
|
||||
var displacement = hand.Value.Location switch
|
||||
{
|
||||
HandLocation.Left => handComp.LeftHandDisplacement,
|
||||
HandLocation.Right => handComp.RightHandDisplacement,
|
||||
_ => handComp.HandDisplacement
|
||||
};
|
||||
|
||||
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (uid, sprite), index, key, out var displacementKey))
|
||||
if (displacement is not null && _displacement.TryAddDisplacement(displacement, (ent, sprite), index, key, out var displacementKey))
|
||||
revealedLayers.Add(displacementKey);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(uid, revealedLayers), true);
|
||||
RaiseLocalEvent(held, new HeldVisualsUpdatedEvent(ent, revealedLayers), true);
|
||||
}
|
||||
|
||||
private void OnVisualsChanged(EntityUid uid, HandsComponent component, VisualsChangedEvent args)
|
||||
{
|
||||
// update hands visuals if this item is in a hand (rather then inventory or other container).
|
||||
if (component.Hands.TryGetValue(args.ContainerId, out var hand))
|
||||
{
|
||||
UpdateHandVisuals(uid, GetEntity(args.Item), hand, component);
|
||||
}
|
||||
if (!component.Hands.ContainsKey(args.ContainerId))
|
||||
return;
|
||||
UpdateHandVisuals((uid, component), GetEntity(args.Item), args.ContainerId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -379,7 +345,7 @@ namespace Content.Client.Hands.Systems
|
||||
|
||||
private void HandlePlayerAttached(EntityUid uid, HandsComponent component, LocalPlayerAttachedEvent args)
|
||||
{
|
||||
OnPlayerHandsAdded?.Invoke(component);
|
||||
OnPlayerHandsAdded?.Invoke((uid, component));
|
||||
}
|
||||
|
||||
private void HandlePlayerDetached(EntityUid uid, HandsComponent component, LocalPlayerDetachedEvent args)
|
||||
@@ -390,7 +356,7 @@ namespace Content.Client.Hands.Systems
|
||||
private void OnHandsStartup(EntityUid uid, HandsComponent component, ComponentStartup args)
|
||||
{
|
||||
if (_playerManager.LocalEntity == uid)
|
||||
OnPlayerHandsAdded?.Invoke(component);
|
||||
OnPlayerHandsAdded?.Invoke((uid, component));
|
||||
}
|
||||
|
||||
private void OnHandsShutdown(EntityUid uid, HandsComponent component, ComponentShutdown args)
|
||||
@@ -400,36 +366,6 @@ namespace Content.Client.Hands.Systems
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void AddHand(EntityUid uid, Hand newHand, HandsComponent? handsComp = null)
|
||||
{
|
||||
AddHand(uid, newHand.Name, newHand.Location, handsComp);
|
||||
}
|
||||
|
||||
public override void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null)
|
||||
{
|
||||
base.AddHand(uid, handName, handLocation, handsComp);
|
||||
|
||||
if (uid == _playerManager.LocalEntity)
|
||||
OnPlayerAddHand?.Invoke(handName, handLocation);
|
||||
|
||||
if (handsComp == null)
|
||||
return;
|
||||
|
||||
if (handsComp.ActiveHand == null)
|
||||
SetActiveHand(uid, handsComp.Hands[handName], handsComp);
|
||||
}
|
||||
public override void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
|
||||
{
|
||||
if (uid == _playerManager.LocalEntity && handsComp != null &&
|
||||
handsComp.Hands.ContainsKey(handName) && uid ==
|
||||
_playerManager.LocalEntity)
|
||||
{
|
||||
OnPlayerRemoveHand?.Invoke(handName);
|
||||
}
|
||||
|
||||
base.RemoveHand(uid, handName, handsComp);
|
||||
}
|
||||
|
||||
private void OnHandActivated(Entity<HandsComponent>? ent)
|
||||
{
|
||||
if (ent is not { } hand)
|
||||
@@ -438,13 +374,7 @@ namespace Content.Client.Hands.Systems
|
||||
if (_playerManager.LocalEntity != hand.Owner)
|
||||
return;
|
||||
|
||||
if (hand.Comp.ActiveHand == null)
|
||||
{
|
||||
OnPlayerSetActiveHand?.Invoke(null);
|
||||
return;
|
||||
}
|
||||
|
||||
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHand.Name);
|
||||
OnPlayerSetActiveHand?.Invoke(hand.Comp.ActiveHandId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Client.Strip;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
@@ -34,6 +35,7 @@ namespace Content.Client.Inventory
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
private readonly ExamineSystem _examine;
|
||||
private readonly HandsSystem _hands;
|
||||
private readonly InventorySystem _inv;
|
||||
private readonly SharedCuffableSystem _cuffable;
|
||||
private readonly StrippableSystem _strippable;
|
||||
@@ -65,6 +67,7 @@ namespace Content.Client.Inventory
|
||||
public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_examine = EntMan.System<ExamineSystem>();
|
||||
_hands = EntMan.System<HandsSystem>();
|
||||
_inv = EntMan.System<InventorySystem>();
|
||||
_cuffable = EntMan.System<SharedCuffableSystem>();
|
||||
_strippable = EntMan.System<StrippableSystem>();
|
||||
@@ -120,28 +123,28 @@ namespace Content.Client.Inventory
|
||||
{
|
||||
// good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands
|
||||
// and not gui hands... which are different...
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
foreach (var (id, hand) in handsComp.Hands)
|
||||
{
|
||||
if (hand.Location != HandLocation.Right)
|
||||
continue;
|
||||
|
||||
AddHandButton(hand);
|
||||
AddHandButton((Owner, handsComp), id, hand);
|
||||
}
|
||||
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
foreach (var (id, hand) in handsComp.Hands)
|
||||
{
|
||||
if (hand.Location != HandLocation.Middle)
|
||||
continue;
|
||||
|
||||
AddHandButton(hand);
|
||||
AddHandButton((Owner, handsComp), id, hand);
|
||||
}
|
||||
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
foreach (var (id, hand) in handsComp.Hands)
|
||||
{
|
||||
if (hand.Location != HandLocation.Left)
|
||||
continue;
|
||||
|
||||
AddHandButton(hand);
|
||||
AddHandButton((Owner, handsComp), id, hand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,20 +180,21 @@ namespace Content.Client.Inventory
|
||||
_strippingMenu.SetSize = new Vector2(horizontalMenuSize, verticalMenuSize);
|
||||
}
|
||||
|
||||
private void AddHandButton(Hand hand)
|
||||
private void AddHandButton(Entity<HandsComponent> ent, string handId, Hand hand)
|
||||
{
|
||||
var button = new HandButton(hand.Name, hand.Location);
|
||||
var button = new HandButton(handId, hand.Location);
|
||||
|
||||
button.Pressed += SlotPressed;
|
||||
|
||||
if (EntMan.TryGetComponent<VirtualItemComponent>(hand.HeldEntity, out var virt))
|
||||
var heldEntity = _hands.GetHeldItem(ent.AsNullable(), handId);
|
||||
if (EntMan.TryGetComponent<VirtualItemComponent>(heldEntity, out var virt))
|
||||
{
|
||||
button.Blocked = true;
|
||||
if (EntMan.TryGetComponent<CuffableComponent>(Owner, out var cuff) && _cuffable.GetAllCuffs(cuff).Contains(virt.BlockingEntity))
|
||||
button.BlockedRect.MouseFilter = MouseFilterMode.Ignore;
|
||||
}
|
||||
|
||||
UpdateEntityIcon(button, hand.HeldEntity);
|
||||
UpdateEntityIcon(button, heldEntity);
|
||||
_strippingMenu!.HandsContainer.AddChild(button);
|
||||
LayoutContainer.SetPosition(button, new Vector2i(_handCount, 0) * (SlotControl.DefaultButtonSize + ButtonSeparation));
|
||||
_handCount++;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.RCD.Components;
|
||||
@@ -17,6 +18,7 @@ public sealed class AlignRCDConstruction : PlacementMode
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly HandsSystem _handsSystem;
|
||||
private readonly RCDSystem _rcdSystem;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
@@ -34,6 +36,7 @@ public sealed class AlignRCDConstruction : PlacementMode
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
_handsSystem = _entityManager.System<HandsSystem>();
|
||||
_rcdSystem = _entityManager.System<RCDSystem>();
|
||||
_transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
@@ -88,11 +91,9 @@ public sealed class AlignRCDConstruction : PlacementMode
|
||||
}
|
||||
|
||||
// Determine if player is carrying an RCD in their active hand
|
||||
if (!_entityManager.TryGetComponent<HandsComponent>(player, out var hands))
|
||||
if (!_handsSystem.TryGetActiveItem(player.Value, out var heldEntity))
|
||||
return false;
|
||||
|
||||
var heldEntity = hands.ActiveHand?.HeldEntity;
|
||||
|
||||
if (!_entityManager.TryGetComponent<RCDComponent>(heldEntity, out var rcd))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Hands.Systems;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.RCD;
|
||||
@@ -15,6 +16,7 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
|
||||
private string _placementMode = typeof(AlignRCDConstruction).Name;
|
||||
private Direction _placementDirection = default;
|
||||
@@ -33,12 +35,11 @@ public sealed class RCDConstructionGhostSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Determine if player is carrying an RCD in their active hand
|
||||
var player = _playerManager.LocalSession?.AttachedEntity;
|
||||
|
||||
if (!TryComp<HandsComponent>(player, out var hands))
|
||||
if (_playerManager.LocalSession?.AttachedEntity is not { } player)
|
||||
return;
|
||||
|
||||
var heldEntity = hands.ActiveHand?.HeldEntity;
|
||||
if (!_hands.TryGetActiveItem(player, out var heldEntity))
|
||||
return;
|
||||
|
||||
if (!TryComp<RCDComponent>(heldEntity, out var rcd))
|
||||
{
|
||||
|
||||
@@ -68,11 +68,15 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem
|
||||
|
||||
foreach (var hand in _hands.EnumerateHands(player.Value))
|
||||
{
|
||||
if (!scannerQuery.TryGetComponent(hand.HeldEntity, out var heldScanner) || !heldScanner.Enabled)
|
||||
if (!_hands.TryGetHeldItem(player.Value, hand, out var heldEntity))
|
||||
continue;
|
||||
|
||||
if (!scannerQuery.TryGetComponent(heldEntity, out var heldScanner) || !heldScanner.Enabled)
|
||||
continue;
|
||||
|
||||
range = MathF.Max(heldScanner.Range, range);
|
||||
canSee = true;
|
||||
break;
|
||||
}
|
||||
|
||||
inRange = new HashSet<Entity<SubFloorHideComponent>>();
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Timing;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
@@ -28,7 +29,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
private readonly Dictionary<string, int> _handContainerIndices = new();
|
||||
private readonly Dictionary<string, HandButton> _handLookup = new();
|
||||
private HandsComponent? _playerHandsComponent;
|
||||
private HandButton? _activeHand = null;
|
||||
private HandButton? _activeHand;
|
||||
|
||||
// We only have two item status controls (left and right hand),
|
||||
// but we may have more than two hands.
|
||||
@@ -38,7 +39,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
private HandButton? _statusHandLeft;
|
||||
private HandButton? _statusHandRight;
|
||||
|
||||
private int _backupSuffix = 0; //this is used when autogenerating container names if they don't have names
|
||||
private int _backupSuffix; //this is used when autogenerating container names if they don't have names
|
||||
|
||||
private HotbarGui? HandsGui => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
|
||||
|
||||
@@ -48,7 +49,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
_handsSystem.OnPlayerItemAdded += OnItemAdded;
|
||||
_handsSystem.OnPlayerItemRemoved += OnItemRemoved;
|
||||
_handsSystem.OnPlayerSetActiveHand += SetActiveHand;
|
||||
_handsSystem.OnPlayerRemoveHand += RemoveHand;
|
||||
_handsSystem.OnPlayerRemoveHand += OnRemoveHand;
|
||||
_handsSystem.OnPlayerHandsAdded += LoadPlayerHands;
|
||||
_handsSystem.OnPlayerHandsRemoved += UnloadPlayerHands;
|
||||
_handsSystem.OnPlayerHandBlocked += HandBlocked;
|
||||
@@ -61,28 +62,35 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
_handsSystem.OnPlayerItemAdded -= OnItemAdded;
|
||||
_handsSystem.OnPlayerItemRemoved -= OnItemRemoved;
|
||||
_handsSystem.OnPlayerSetActiveHand -= SetActiveHand;
|
||||
_handsSystem.OnPlayerRemoveHand -= RemoveHand;
|
||||
_handsSystem.OnPlayerRemoveHand -= OnRemoveHand;
|
||||
_handsSystem.OnPlayerHandsAdded -= LoadPlayerHands;
|
||||
_handsSystem.OnPlayerHandsRemoved -= UnloadPlayerHands;
|
||||
_handsSystem.OnPlayerHandBlocked -= HandBlocked;
|
||||
_handsSystem.OnPlayerHandUnblocked -= HandUnblocked;
|
||||
}
|
||||
|
||||
private void OnAddHand(string name, HandLocation location)
|
||||
private void OnAddHand(Entity<HandsComponent> entity, string name, HandLocation location)
|
||||
{
|
||||
if (entity.Owner != _player.LocalEntity)
|
||||
return;
|
||||
AddHand(name, location);
|
||||
}
|
||||
|
||||
private void OnRemoveHand(Entity<HandsComponent> entity, string name)
|
||||
{
|
||||
if (entity.Owner != _player.LocalEntity)
|
||||
return;
|
||||
RemoveHand(name);
|
||||
}
|
||||
|
||||
private void HandPressed(GUIBoundKeyEventArgs args, SlotControl hand)
|
||||
{
|
||||
if (_playerHandsComponent == null)
|
||||
{
|
||||
if (!_handsSystem.TryGetPlayerHands(out var hands))
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
_handsSystem.UIHandClick(_playerHandsComponent, hand.SlotName);
|
||||
_handsSystem.UIHandClick(hands.Value, hand.SlotName);
|
||||
args.Handle();
|
||||
}
|
||||
else if (args.Function == EngineKeyFunctions.UseSecondary)
|
||||
@@ -122,33 +130,33 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPlayerHands(HandsComponent handsComp)
|
||||
private void LoadPlayerHands(Entity<HandsComponent> handsComp)
|
||||
{
|
||||
DebugTools.Assert(_playerHandsComponent == null);
|
||||
if (HandsGui != null)
|
||||
HandsGui.Visible = true;
|
||||
|
||||
_playerHandsComponent = handsComp;
|
||||
foreach (var (name, hand) in handsComp.Hands)
|
||||
foreach (var (name, hand) in handsComp.Comp.Hands)
|
||||
{
|
||||
var handButton = AddHand(name, hand.Location);
|
||||
|
||||
if (_entities.TryGetComponent(hand.HeldEntity, out VirtualItemComponent? virt))
|
||||
if (_handsSystem.TryGetHeldItem(handsComp.AsNullable(), name, out var held) &&
|
||||
_entities.TryGetComponent(held, out VirtualItemComponent? virt))
|
||||
{
|
||||
handButton.SetEntity(virt.BlockingEntity);
|
||||
handButton.Blocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
handButton.SetEntity(hand.HeldEntity);
|
||||
handButton.SetEntity(held);
|
||||
handButton.Blocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
var activeHand = handsComp.ActiveHand;
|
||||
if (activeHand == null)
|
||||
if (handsComp.Comp.ActiveHandId == null)
|
||||
return;
|
||||
SetActiveHand(activeHand.Name);
|
||||
SetActiveHand(handsComp.Comp.ActiveHandId);
|
||||
}
|
||||
|
||||
private void HandBlocked(string handName)
|
||||
@@ -260,19 +268,21 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
if (HandsGui != null &&
|
||||
_playerHandsComponent != null &&
|
||||
_player.LocalSession?.AttachedEntity is { } playerEntity &&
|
||||
_handsSystem.TryGetHand(playerEntity, handName, out var hand, _playerHandsComponent))
|
||||
_handsSystem.TryGetHand((playerEntity, _playerHandsComponent), handName, out var hand))
|
||||
{
|
||||
var foldedLocation = hand.Location.GetUILocation();
|
||||
var heldEnt = _handsSystem.GetHeldItem((playerEntity, _playerHandsComponent), handName);
|
||||
|
||||
var foldedLocation = hand.Value.Location.GetUILocation();
|
||||
if (foldedLocation == HandUILocation.Left)
|
||||
{
|
||||
_statusHandLeft = handControl;
|
||||
HandsGui.UpdatePanelEntityLeft(hand.HeldEntity);
|
||||
HandsGui.UpdatePanelEntityLeft(heldEnt);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Middle or right
|
||||
_statusHandRight = handControl;
|
||||
HandsGui.UpdatePanelEntityRight(hand.HeldEntity);
|
||||
HandsGui.UpdatePanelEntityRight(heldEnt);
|
||||
}
|
||||
|
||||
HandsGui.SetHighlightHand(foldedLocation);
|
||||
@@ -292,7 +302,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
button.Pressed += HandPressed;
|
||||
|
||||
if (!_handLookup.TryAdd(handName, button))
|
||||
throw new Exception("Tried to add hand with duplicate name to UI. Name:" + handName);
|
||||
return _handLookup[handName];
|
||||
|
||||
if (HandsGui != null)
|
||||
{
|
||||
@@ -362,6 +372,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
RemoveHand(handName, out _);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
private bool RemoveHand(string handName, out HandButton? handButton)
|
||||
{
|
||||
if (!_handLookup.TryGetValue(handName, out handButton))
|
||||
@@ -377,7 +388,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
_statusHandRight = null;
|
||||
|
||||
_handLookup.Remove(handName);
|
||||
handButton.Dispose();
|
||||
handButton.Orphan();
|
||||
UpdateVisibleStatusPanels();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -329,9 +329,8 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
var player = _playerUid;
|
||||
|
||||
if (!control.MouseIsHovering ||
|
||||
_playerInventory == null ||
|
||||
!_entities.TryGetComponent<HandsComponent>(player, out var hands) ||
|
||||
hands.ActiveHandEntity is not { } held ||
|
||||
player == null ||
|
||||
!_handsSystem.TryGetActiveItem(player.Value, out var held) ||
|
||||
!_entities.TryGetComponent(held, out SpriteComponent? sprite) ||
|
||||
!_inventorySystem.TryGetSlotContainer(player.Value, control.SlotName, out var container, out var slotDef))
|
||||
{
|
||||
@@ -342,12 +341,12 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
// Set green / red overlay at 50% transparency
|
||||
var hoverEntity = _entities.SpawnEntity("hoverentity", MapCoordinates.Nullspace);
|
||||
var hoverSprite = _entities.GetComponent<SpriteComponent>(hoverEntity);
|
||||
var fits = _inventorySystem.CanEquip(player.Value, held, control.SlotName, out _, slotDef) &&
|
||||
_container.CanInsert(held, container);
|
||||
var fits = _inventorySystem.CanEquip(player.Value, held.Value, control.SlotName, out _, slotDef) &&
|
||||
_container.CanInsert(held.Value, container);
|
||||
|
||||
if (!fits && _entities.TryGetComponent<StorageComponent>(container.ContainedEntity, out var storage))
|
||||
{
|
||||
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held, out _, storage);
|
||||
fits = _entities.System<StorageSystem>().CanInsert(container.ContainedEntity.Value, held.Value, out _, storage);
|
||||
}
|
||||
else if (!fits && _entities.TryGetComponent<ItemSlotsComponent>(container.ContainedEntity, out var itemSlots))
|
||||
{
|
||||
@@ -357,14 +356,14 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
if (!slot.InsertOnInteract)
|
||||
continue;
|
||||
|
||||
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held, null, slot))
|
||||
if (!itemSlotsSys.CanInsert(container.ContainedEntity.Value, held.Value, null, slot))
|
||||
continue;
|
||||
fits = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_sprite.CopySprite((held, sprite), (hoverEntity, hoverSprite));
|
||||
_sprite.CopySprite((held.Value, sprite), (hoverEntity, hoverSprite));
|
||||
_sprite.SetColor((hoverEntity, hoverSprite), fits ? new Color(0, 255, 0, 127) : new Color(255, 0, 0, 127));
|
||||
|
||||
control.HoverSpriteView.SetEntity(hoverEntity);
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class RetractableItemActionTest : InteractionTest
|
||||
await Server.WaitAssertion(() =>
|
||||
{
|
||||
// Make sure the player's hand starts empty
|
||||
var heldItem = Hands.ActiveHandEntity;
|
||||
var heldItem = handsSystem.GetActiveItem((playerUid, Hands));
|
||||
Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) at start of test.");
|
||||
|
||||
// Inspect the action prototype to find the item it spawns
|
||||
@@ -43,14 +43,14 @@ public sealed class RetractableItemActionTest : InteractionTest
|
||||
var actionEnt = actionsSystem.GetAction(actionUid);
|
||||
|
||||
// Make sure the player's hand is still empty
|
||||
heldItem = Hands.ActiveHandEntity;
|
||||
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
|
||||
Assert.That(heldItem, Is.Null, $"Player is holding an item ({SEntMan.ToPrettyString(heldItem)}) after adding action.");
|
||||
|
||||
// Activate the arm blade
|
||||
actionsSystem.PerformAction(ToServer(Player), actionEnt!.Value);
|
||||
|
||||
// Make sure the player is now holding the expected item
|
||||
heldItem = Hands.ActiveHandEntity;
|
||||
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
|
||||
Assert.That(heldItem, Is.Not.Null, $"Expected player to be holding {spawnedProtoId} but was holding nothing.");
|
||||
AssertPrototype(spawnedProtoId, SEntMan.GetNetEntity(heldItem));
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class RetractableItemActionTest : InteractionTest
|
||||
actionsSystem.PerformAction(ToServer(Player), actionEnt.Value);
|
||||
|
||||
// Make sure the player's hand is empty again
|
||||
heldItem = Hands.ActiveHandEntity;
|
||||
heldItem = handsSystem.GetActiveItem((playerUid, Hands));
|
||||
Assert.That(heldItem, Is.Null, $"Player is still holding an item ({SEntMan.ToPrettyString(heldItem)}) after second use.");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,9 +293,9 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
Assert.That(buckle.Buckled);
|
||||
|
||||
// With items in all hands
|
||||
foreach (var hand in hands.Hands.Values)
|
||||
foreach (var hand in hands.Hands.Keys)
|
||||
{
|
||||
Assert.That(hand.HeldEntity, Is.Not.Null);
|
||||
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Not.Null);
|
||||
}
|
||||
|
||||
var bodySystem = entityManager.System<BodySystem>();
|
||||
@@ -316,9 +316,9 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
Assert.That(buckle.Buckled);
|
||||
|
||||
// Now with no item in any hand
|
||||
foreach (var hand in hands.Hands.Values)
|
||||
foreach (var hand in hands.Hands.Keys)
|
||||
{
|
||||
Assert.That(hand.HeldEntity, Is.Null);
|
||||
Assert.That(handsSys.GetHeldItem((human, hands), hand), Is.Null);
|
||||
}
|
||||
|
||||
buckleSystem.Unbuckle(human, human);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Client.Chemistry.UI;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Server.Chemistry.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Chemistry;
|
||||
@@ -19,7 +18,7 @@ public sealed class DispenserTest : InteractionTest
|
||||
|
||||
// Insert beaker
|
||||
await InteractUsing("Beaker");
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
|
||||
// Open BUI
|
||||
await Interact();
|
||||
@@ -29,18 +28,18 @@ public sealed class DispenserTest : InteractionTest
|
||||
await SendBui(ReagentDispenserUiKey.Key, ev);
|
||||
|
||||
// Beaker is back in the player's hands
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Not.Null);
|
||||
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
|
||||
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
|
||||
|
||||
// Re-insert the beaker
|
||||
await Interact();
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
|
||||
// Re-eject using the button directly instead of sending a BUI event. This test is really just a test of the
|
||||
// bui/window helper methods.
|
||||
await ClickControl<ReagentDispenserWindow>(nameof(ReagentDispenserWindow.EjectButton));
|
||||
await RunTicks(5);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Not.Null);
|
||||
AssertPrototype("Beaker", SEntMan.GetNetEntity(Hands.ActiveHandEntity));
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Not.Null);
|
||||
AssertPrototype("Beaker", SEntMan.GetNetEntity(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ public sealed class SuicideCommandTests
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var item = entManager.SpawnEntity("SharpTestObject", transformSystem.GetMapCoordinates(player));
|
||||
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
|
||||
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
|
||||
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
|
||||
Assert.That(executionComponent, Is.Not.EqualTo(null));
|
||||
});
|
||||
@@ -342,7 +342,7 @@ public sealed class SuicideCommandTests
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var item = entManager.SpawnEntity("MixedDamageTestObject", transformSystem.GetMapCoordinates(player));
|
||||
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHand!));
|
||||
Assert.That(handsSystem.TryPickup(player, item, handsComponent.ActiveHandId!));
|
||||
entManager.TryGetComponent<ExecutionComponent>(item, out var executionComponent);
|
||||
Assert.That(executionComponent, Is.Not.EqualTo(null));
|
||||
});
|
||||
|
||||
@@ -13,10 +13,10 @@ public sealed class WallConstruction : InteractionTest
|
||||
{
|
||||
await StartConstruction(Wall);
|
||||
await InteractUsing(Steel, 2);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
ClientAssertPrototype(Girder, Target);
|
||||
await InteractUsing(Steel, 2);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
AssertPrototype(WallSolid);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,20 +53,20 @@ public sealed class HandTests
|
||||
var xform = entMan.GetComponent<TransformComponent>(player);
|
||||
item = entMan.SpawnEntity("Crowbar", tSys.GetMapCoordinates(player, xform: xform));
|
||||
hands = entMan.GetComponent<HandsComponent>(player);
|
||||
sys.TryPickup(player, item, hands.ActiveHand!);
|
||||
sys.TryPickup(player, item, hands.ActiveHandId!);
|
||||
});
|
||||
|
||||
// run ticks here is important, as errors may happen within the container system's frame update methods.
|
||||
await pair.RunTicksSync(5);
|
||||
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
|
||||
Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item));
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
sys.TryDrop(player, item, null!);
|
||||
sys.TryDrop(player, item);
|
||||
});
|
||||
|
||||
await pair.RunTicksSync(5);
|
||||
Assert.That(hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(sys.GetActiveItem((player, hands)), Is.Null);
|
||||
|
||||
await server.WaitPost(() => mapSystem.DeleteMap(data.MapId));
|
||||
await pair.CleanReturnAsync();
|
||||
@@ -105,10 +105,10 @@ public sealed class HandTests
|
||||
player = playerMan.Sessions.First().AttachedEntity!.Value;
|
||||
tSys.PlaceNextTo(player, item);
|
||||
hands = entMan.GetComponent<HandsComponent>(player);
|
||||
sys.TryPickup(player, item, hands.ActiveHand!);
|
||||
sys.TryPickup(player, item, hands.ActiveHandId!);
|
||||
});
|
||||
await pair.RunTicksSync(5);
|
||||
Assert.That(hands.ActiveHandEntity, Is.EqualTo(item));
|
||||
Assert.That(sys.GetActiveItem((player, hands)), Is.EqualTo(item));
|
||||
|
||||
// Open then close the box to place the player, who is holding the crowbar, inside of it
|
||||
var storage = server.System<EntityStorageSystem>();
|
||||
@@ -125,12 +125,12 @@ public sealed class HandTests
|
||||
// with the item not being in the player's hands
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
sys.TryDrop(player, item, null!);
|
||||
sys.TryDrop(player, item);
|
||||
});
|
||||
await pair.RunTicksSync(5);
|
||||
var xform = entMan.GetComponent<TransformComponent>(player);
|
||||
var itemXform = entMan.GetComponent<TransformComponent>(item);
|
||||
Assert.That(hands.ActiveHandEntity, Is.Not.EqualTo(item));
|
||||
Assert.That(sys.GetActiveItem((player, hands)), Is.Not.EqualTo(item));
|
||||
Assert.That(containerSystem.IsInSameOrNoContainer((player, xform), (item, itemXform)));
|
||||
|
||||
await server.WaitPost(() => mapSystem.DeleteMap(map.MapId));
|
||||
|
||||
@@ -120,18 +120,18 @@ public abstract partial class InteractionTest
|
||||
/// </summary>
|
||||
protected async Task DeleteHeldEntity()
|
||||
{
|
||||
if (Hands.ActiveHandEntity is { } held)
|
||||
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is { } held)
|
||||
{
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), null, false, true, Hands));
|
||||
Assert.That(HandSys.TryDrop((SEntMan.GetEntity(Player), Hands), null, false, true));
|
||||
SEntMan.DeleteEntity(held);
|
||||
SLogger.Debug($"Deleting held entity");
|
||||
});
|
||||
}
|
||||
|
||||
await RunTicks(1);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -152,7 +152,7 @@ public abstract partial class InteractionTest
|
||||
/// <param name="enableToggleable">Whether or not to automatically enable any toggleable items</param>
|
||||
protected async Task<NetEntity> PlaceInHands(EntitySpecifier entity, bool enableToggleable = true)
|
||||
{
|
||||
if (Hands.ActiveHand == null)
|
||||
if (Hands.ActiveHandId == null)
|
||||
{
|
||||
Assert.Fail("No active hand");
|
||||
return default;
|
||||
@@ -169,7 +169,7 @@ public abstract partial class InteractionTest
|
||||
{
|
||||
var playerEnt = SEntMan.GetEntity(Player);
|
||||
|
||||
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHand, false, false, Hands));
|
||||
Assert.That(HandSys.TryPickup(playerEnt, item, Hands.ActiveHandId, false, false, false, Hands));
|
||||
|
||||
// turn on welders
|
||||
if (enableToggleable && SEntMan.TryGetComponent(item, out itemToggle) && !itemToggle.Activated)
|
||||
@@ -179,7 +179,7 @@ public abstract partial class InteractionTest
|
||||
});
|
||||
|
||||
await RunTicks(1);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(item));
|
||||
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(item));
|
||||
if (enableToggleable && itemToggle != null)
|
||||
Assert.That(itemToggle.Activated);
|
||||
|
||||
@@ -193,7 +193,7 @@ public abstract partial class InteractionTest
|
||||
{
|
||||
entity ??= Target;
|
||||
|
||||
if (Hands.ActiveHand == null)
|
||||
if (Hands.ActiveHandId == null)
|
||||
{
|
||||
Assert.Fail("No active hand");
|
||||
return;
|
||||
@@ -212,11 +212,11 @@ public abstract partial class InteractionTest
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
Assert.That(HandSys.TryPickup(SEntMan.GetEntity(Player), uid.Value, Hands.ActiveHand, false, false, Hands, item));
|
||||
Assert.That(HandSys.TryPickup(ToServer(Player), uid.Value, Hands.ActiveHandId, false, false, false, Hands, item));
|
||||
});
|
||||
|
||||
await RunTicks(1);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.EqualTo(uid));
|
||||
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.EqualTo(uid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -224,7 +224,7 @@ public abstract partial class InteractionTest
|
||||
/// </summary>
|
||||
protected async Task Drop()
|
||||
{
|
||||
if (Hands.ActiveHandEntity == null)
|
||||
if (HandSys.GetActiveItem((ToServer(Player), Hands)) == null)
|
||||
{
|
||||
Assert.Fail("Not holding any entity to drop");
|
||||
return;
|
||||
@@ -232,11 +232,11 @@ public abstract partial class InteractionTest
|
||||
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
Assert.That(HandSys.TryDrop(SEntMan.GetEntity(Player), handsComp: Hands));
|
||||
Assert.That(HandSys.TryDrop((ToServer(Player), Hands)));
|
||||
});
|
||||
|
||||
await RunTicks(1);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((ToServer(Player), Hands)), Is.Null);
|
||||
}
|
||||
|
||||
#region Interact
|
||||
@@ -246,7 +246,7 @@ public abstract partial class InteractionTest
|
||||
/// </summary>
|
||||
protected async Task UseInHand()
|
||||
{
|
||||
if (Hands.ActiveHandEntity is not { } target)
|
||||
if (HandSys.GetActiveItem((ToServer(Player), Hands)) is not { } target)
|
||||
{
|
||||
Assert.Fail("Not holding any entity");
|
||||
return;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Construction;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Stack;
|
||||
using Content.Server.Tools;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
@@ -135,10 +132,13 @@ public abstract partial class InteractionTest
|
||||
- type: entity
|
||||
id: InteractionTestMob
|
||||
components:
|
||||
- type: Body
|
||||
prototype: Aghost
|
||||
- type: DoAfter
|
||||
- type: Hands
|
||||
hands:
|
||||
hand_right: # only one hand, so that they do not accidentally pick up deconstruction products
|
||||
location: Right
|
||||
sortedHands:
|
||||
- hand_right
|
||||
- type: ComplexInteraction
|
||||
- type: MindContainer
|
||||
- type: Stripping
|
||||
@@ -230,20 +230,6 @@ public abstract partial class InteractionTest
|
||||
SEntMan.DeleteEntity(old.Value);
|
||||
});
|
||||
|
||||
// Ensure that the player only has one hand, so that they do not accidentally pick up deconstruction products
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
// I lost an hour of my life trying to track down how the hell interaction tests were breaking
|
||||
// so greatz to this. Just make your own body prototype!
|
||||
var bodySystem = SEntMan.System<BodySystem>();
|
||||
var hands = bodySystem.GetBodyChildrenOfType(SEntMan.GetEntity(Player), BodyPartType.Hand).ToArray();
|
||||
|
||||
for (var i = 1; i < hands.Length; i++)
|
||||
{
|
||||
SEntMan.DeleteEntity(hands[i].Id);
|
||||
}
|
||||
});
|
||||
|
||||
// Change UI state to in-game.
|
||||
var state = Client.ResolveDependency<IStateManager>();
|
||||
await Client.WaitPost(() => state.RequestStateChange<GameplayState>());
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class TileConstructionTests : InteractionTest
|
||||
await SetTile(null);
|
||||
await InteractUsing(Rod);
|
||||
await AssertTile(Lattice);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
await InteractUsing(Cut);
|
||||
await AssertTile(null);
|
||||
await AssertEntityLookup((Rod, 1));
|
||||
@@ -49,7 +49,7 @@ public sealed class TileConstructionTests : InteractionTest
|
||||
AssertGridCount(1);
|
||||
|
||||
// Cut lattice
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
await InteractUsing(Cut);
|
||||
await AssertTile(null);
|
||||
AssertGridCount(0);
|
||||
@@ -83,13 +83,13 @@ public sealed class TileConstructionTests : InteractionTest
|
||||
|
||||
// Lattice -> Plating
|
||||
await InteractUsing(FloorItem);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
await AssertTile(Plating);
|
||||
AssertGridCount(1);
|
||||
|
||||
// Plating -> Tile
|
||||
await InteractUsing(FloorItem);
|
||||
Assert.That(Hands.ActiveHandEntity, Is.Null);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
await AssertTile(Floor);
|
||||
AssertGridCount(1);
|
||||
|
||||
|
||||
@@ -42,13 +42,12 @@ public sealed class StripAllCommand : LocalizedEntityCommands
|
||||
|
||||
if (EntityManager.TryGetComponent<HandsComponent>(targetEntity, out var hands))
|
||||
{
|
||||
foreach (var hand in _handsSystem.EnumerateHands(targetEntity.Value, hands))
|
||||
foreach (var hand in _handsSystem.EnumerateHands((targetEntity.Value, hands)))
|
||||
{
|
||||
_handsSystem.TryDrop(targetEntity.Value,
|
||||
_handsSystem.TryDrop((targetEntity.Value, hands),
|
||||
hand,
|
||||
checkActionBlocker: false,
|
||||
doDropInteraction: false,
|
||||
handsComp: hands);
|
||||
doDropInteraction: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,9 +433,9 @@ public sealed class AdminSystem : EntitySystem
|
||||
|
||||
if (TryComp(entity, out HandsComponent? hands))
|
||||
{
|
||||
foreach (var hand in _hands.EnumerateHands(entity, hands))
|
||||
foreach (var hand in _hands.EnumerateHands((entity, hands)))
|
||||
{
|
||||
_hands.TryDrop(entity, hand, checkActionBlocker: false, doDropInteraction: false, handsComp: hands);
|
||||
_hands.TryDrop((entity, hands), hand, checkActionBlocker: false, doDropInteraction: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -820,7 +820,7 @@ public sealed partial class AdminVerbSystem
|
||||
}
|
||||
else if (TryComp<HandsComponent>(target, out var hands))
|
||||
{
|
||||
foreach (var held in _handsSystem.EnumerateHeld(target, hands))
|
||||
foreach (var held in _handsSystem.EnumerateHeld((target, hands)))
|
||||
{
|
||||
if (HasComp<AccessComponent>(held))
|
||||
{
|
||||
|
||||
@@ -97,8 +97,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
|
||||
EntityUid? entity = null;
|
||||
if (args.Type == CryostorageRemoveItemBuiMessage.RemovalType.Hand)
|
||||
{
|
||||
if (_hands.TryGetHand(cryoContained, args.Key, out var hand))
|
||||
entity = hand.HeldEntity;
|
||||
entity = _hands.GetHeldItem(cryoContained, args.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -320,10 +319,10 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
|
||||
|
||||
foreach (var hand in _hands.EnumerateHands(uid))
|
||||
{
|
||||
if (hand.HeldEntity == null)
|
||||
if (!_hands.TryGetHeldItem(uid, hand, out var heldEntity))
|
||||
continue;
|
||||
|
||||
data.HeldItems.Add(hand.Name, Name(hand.HeldEntity.Value));
|
||||
data.HeldItems.Add(hand, Name(heldEntity.Value));
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Botany.Components;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Kitchen.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
@@ -37,6 +38,7 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
[Dependency] private readonly MutationSystem _mutation = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
@@ -706,9 +708,9 @@ public sealed class PlantHolderSystem : EntitySystem
|
||||
|
||||
if (component.Harvest && !component.Dead)
|
||||
{
|
||||
if (TryComp<HandsComponent>(user, out var hands))
|
||||
if (_hands.TryGetActiveItem(user, out var activeItem))
|
||||
{
|
||||
if (!_botany.CanHarvest(component.Seed, hands.ActiveHandEntity))
|
||||
if (!_botany.CanHarvest(component.Seed, activeItem))
|
||||
{
|
||||
_popup.PopupCursor(Loc.GetString("plant-holder-component-ligneous-cant-harvest-message"), user);
|
||||
return false;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Item;
|
||||
@@ -22,6 +22,7 @@ public sealed class SuicideSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookupSystem = default!;
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
@@ -116,10 +117,9 @@ public sealed class SuicideSystem : EntitySystem
|
||||
var suicideByEnvironmentEvent = new SuicideByEnvironmentEvent(victim);
|
||||
|
||||
// Try to suicide by raising an event on the held item
|
||||
if (EntityManager.TryGetComponent(victim, out HandsComponent? handsComponent)
|
||||
&& handsComponent.ActiveHandEntity is { } item)
|
||||
if (_hands.TryGetActiveItem(victim.Owner, out var item))
|
||||
{
|
||||
RaiseLocalEvent(item, suicideByEnvironmentEvent);
|
||||
RaiseLocalEvent(item.Value, suicideByEnvironmentEvent);
|
||||
if (suicideByEnvironmentEvent.Handled)
|
||||
{
|
||||
args.Handled = suicideByEnvironmentEvent.Handled;
|
||||
|
||||
@@ -471,7 +471,7 @@ namespace Content.Server.Construction
|
||||
}
|
||||
|
||||
if (!_actionBlocker.CanInteract(user, null)
|
||||
|| !EntityManager.TryGetComponent(user, out HandsComponent? hands) || hands.ActiveHandEntity == null)
|
||||
|| !EntityManager.TryGetComponent(user, out HandsComponent? hands) || _handsSystem.GetActiveItem((user, hands)) == null)
|
||||
{
|
||||
Cleanup();
|
||||
return;
|
||||
@@ -496,7 +496,7 @@ namespace Content.Server.Construction
|
||||
|
||||
var valid = false;
|
||||
|
||||
if (hands.ActiveHandEntity is not {Valid: true} holding)
|
||||
if (_handsSystem.GetActiveItem((user, hands)) is not {Valid: true} holding)
|
||||
{
|
||||
Cleanup();
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Server.Inventory;
|
||||
using Content.Server.Stack;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Shared.ActionBlocker;
|
||||
@@ -10,9 +9,7 @@ using Content.Shared.Explosion;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Movement.Pulling.Components;
|
||||
using Content.Shared.Movement.Pulling.Events;
|
||||
using Content.Shared.Movement.Pulling.Systems;
|
||||
using Content.Shared.Stacks;
|
||||
using Content.Shared.Standing;
|
||||
@@ -24,7 +21,6 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Hands.Systems
|
||||
{
|
||||
@@ -87,10 +83,9 @@ namespace Content.Server.Hands.Systems
|
||||
if (ent.Comp.DisableExplosionRecursion)
|
||||
return;
|
||||
|
||||
foreach (var hand in ent.Comp.Hands.Values)
|
||||
foreach (var held in EnumerateHeld(ent.AsNullable()))
|
||||
{
|
||||
if (hand.HeldEntity is { } uid)
|
||||
args.Contents.Add(uid);
|
||||
args.Contents.Add(held);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +107,7 @@ namespace Content.Server.Hands.Systems
|
||||
args.Handled = true; // no shove/stun.
|
||||
}
|
||||
|
||||
private void HandleBodyPartAdded(EntityUid uid, HandsComponent component, ref BodyPartAddedEvent args)
|
||||
private void HandleBodyPartAdded(Entity<HandsComponent> ent, ref BodyPartAddedEvent args)
|
||||
{
|
||||
if (args.Part.Comp.PartType != BodyPartType.Hand)
|
||||
return;
|
||||
@@ -127,7 +122,7 @@ namespace Content.Server.Hands.Systems
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(args.Part.Comp.Symmetry))
|
||||
};
|
||||
|
||||
AddHand(uid, args.Slot, location);
|
||||
AddHand(ent.AsNullable(), args.Slot, location);
|
||||
}
|
||||
|
||||
private void HandleBodyPartRemoved(EntityUid uid, HandsComponent component, ref BodyPartRemovedEvent args)
|
||||
@@ -155,8 +150,8 @@ namespace Content.Server.Hands.Systems
|
||||
{
|
||||
if (ContainerSystem.IsEntityInContainer(player) ||
|
||||
!TryComp(player, out HandsComponent? hands) ||
|
||||
hands.ActiveHandEntity is not { } throwEnt ||
|
||||
!_actionBlockerSystem.CanThrow(player, throwEnt))
|
||||
!TryGetActiveItem((player, hands), out var throwEnt) ||
|
||||
!_actionBlockerSystem.CanThrow(player, throwEnt.Value))
|
||||
return false;
|
||||
|
||||
if (_timing.CurTime < hands.NextThrowTime)
|
||||
@@ -165,7 +160,7 @@ namespace Content.Server.Hands.Systems
|
||||
|
||||
if (EntityManager.TryGetComponent(throwEnt, out StackComponent? stack) && stack.Count > 1 && stack.ThrowIndividually)
|
||||
{
|
||||
var splitStack = _stackSystem.Split(throwEnt, 1, EntityManager.GetComponent<TransformComponent>(player).Coordinates, stack);
|
||||
var splitStack = _stackSystem.Split(throwEnt.Value, 1, EntityManager.GetComponent<TransformComponent>(player).Coordinates, stack);
|
||||
|
||||
if (splitStack is not {Valid: true})
|
||||
return false;
|
||||
@@ -185,14 +180,14 @@ namespace Content.Server.Hands.Systems
|
||||
|
||||
// Let other systems change the thrown entity (useful for virtual items)
|
||||
// or the throw strength.
|
||||
var ev = new BeforeThrowEvent(throwEnt, direction, throwSpeed, player);
|
||||
var ev = new BeforeThrowEvent(throwEnt.Value, direction, throwSpeed, player);
|
||||
RaiseLocalEvent(player, ref ev);
|
||||
|
||||
if (ev.Cancelled)
|
||||
return true;
|
||||
|
||||
// This can grief the above event so we raise it afterwards
|
||||
if (IsHolding(player, throwEnt, out _, hands) && !TryDrop(player, throwEnt, handsComp: hands))
|
||||
if (IsHolding((player, hands), throwEnt, out _) && !TryDrop(player, throwEnt.Value))
|
||||
return false;
|
||||
|
||||
_throwingSystem.TryThrow(ev.ItemUid, ev.Direction, ev.ThrowSpeed, ev.PlayerUid, compensateFriction: !HasComp<LandAtCursorComponent>(ev.ItemUid));
|
||||
@@ -207,20 +202,20 @@ namespace Content.Server.Hands.Systems
|
||||
var spreadMaxAngle = Angle.FromDegrees(DropHeldItemsSpread);
|
||||
|
||||
var fellEvent = new FellDownEvent(entity);
|
||||
RaiseLocalEvent(entity, fellEvent, false);
|
||||
RaiseLocalEvent(entity, fellEvent);
|
||||
|
||||
foreach (var hand in entity.Comp.Hands.Values)
|
||||
foreach (var hand in entity.Comp.Hands.Keys)
|
||||
{
|
||||
if (hand.HeldEntity is not EntityUid held)
|
||||
if (!TryGetHeldItem(entity.AsNullable(), hand, out var heldEntity))
|
||||
continue;
|
||||
|
||||
var throwAttempt = new FellDownThrowAttemptEvent(entity);
|
||||
RaiseLocalEvent(hand.HeldEntity.Value, ref throwAttempt);
|
||||
RaiseLocalEvent(heldEntity.Value, ref throwAttempt);
|
||||
|
||||
if (throwAttempt.Cancelled)
|
||||
continue;
|
||||
|
||||
if (!TryDrop(entity, hand, null, checkActionBlocker: false, handsComp: entity.Comp))
|
||||
if (!TryDrop(entity.AsNullable(), hand, checkActionBlocker: false))
|
||||
continue;
|
||||
|
||||
// Rotate the item's throw vector a bit for each item
|
||||
@@ -231,12 +226,12 @@ namespace Content.Server.Hands.Systems
|
||||
itemVelocity *= _random.NextFloat(1f);
|
||||
// Heavier objects don't get thrown as far
|
||||
// If the item doesn't have a physics component, it isn't going to get thrown anyway, but we'll assume infinite mass
|
||||
itemVelocity *= _physicsQuery.TryComp(held, out var heldPhysics) ? heldPhysics.InvMass : 0;
|
||||
itemVelocity *= _physicsQuery.TryComp(heldEntity, out var heldPhysics) ? heldPhysics.InvMass : 0;
|
||||
// Throw at half the holder's intentional throw speed and
|
||||
// vary the speed a little to make it look more interesting
|
||||
var throwSpeed = entity.Comp.BaseThrowspeed * _random.NextFloat(0.45f, 0.55f);
|
||||
|
||||
_throwingSystem.TryThrow(held,
|
||||
_throwingSystem.TryThrow(heldEntity.Value,
|
||||
itemVelocity,
|
||||
throwSpeed,
|
||||
entity,
|
||||
|
||||
@@ -43,7 +43,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
|
||||
if (!TryComp<HandsComponent>(hitEntity, out var hands))
|
||||
continue;
|
||||
|
||||
if (!_hands.IsHolding(hitEntity, uid, out _, hands) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands))
|
||||
if (!_hands.IsHolding((hitEntity, hands), uid, out _) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("hot-potato-passed",
|
||||
("from", args.User), ("to", hitEntity)), uid, PopupType.Medium);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.NPC.HTN.Preconditions;
|
||||
@@ -18,14 +18,18 @@ public sealed partial class ActiveHandComponentPrecondition : HTNPrecondition
|
||||
|
||||
public override bool IsMet(NPCBlackboard blackboard)
|
||||
{
|
||||
if (!blackboard.TryGetValue<Hand>(NPCBlackboard.ActiveHand, out var hand, _entManager) || hand.HeldEntity == null)
|
||||
if (!blackboard.TryGetValue<EntityUid>(NPCBlackboard.Owner, out var owner, _entManager) ||
|
||||
!blackboard.TryGetValue<string>(NPCBlackboard.ActiveHand, out var hand, _entManager))
|
||||
{
|
||||
return Invert;
|
||||
}
|
||||
|
||||
if (!_entManager.System<HandsSystem>().TryGetHeldItem(owner, hand, out var entity))
|
||||
return Invert;
|
||||
|
||||
foreach (var comp in Components)
|
||||
{
|
||||
var hasComp = _entManager.HasComponent(hand.HeldEntity, comp.Value.Component.GetType());
|
||||
var hasComp = _entManager.HasComponent(entity, comp.Value.Component.GetType());
|
||||
|
||||
if (!hasComp ||
|
||||
Invert && hasComp)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Server.Hands.Systems;
|
||||
|
||||
namespace Content.Server.NPC.HTN.Preconditions;
|
||||
|
||||
@@ -11,11 +11,12 @@ public sealed partial class ActiveHandEntityPrecondition : HTNPrecondition
|
||||
|
||||
public override bool IsMet(NPCBlackboard blackboard)
|
||||
{
|
||||
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager))
|
||||
if (!blackboard.TryGetValue(NPCBlackboard.Owner, out EntityUid owner, _entManager) ||
|
||||
!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, _entManager))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return activeHand.HeldEntity != null;
|
||||
return !_entManager.System<HandsSystem>().HandIsEmpty(owner, activeHand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed partial class DropOperator : HTNOperator
|
||||
|
||||
public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime)
|
||||
{
|
||||
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, _entManager))
|
||||
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, _entManager))
|
||||
{
|
||||
return HTNOperatorStatus.Finished;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -152,6 +151,8 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
|
||||
value = default;
|
||||
EntityUid owner;
|
||||
|
||||
var handSys = entManager.System<HandsSystem>();
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case Access:
|
||||
@@ -168,25 +169,24 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
|
||||
case ActiveHand:
|
||||
{
|
||||
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||
hands.ActiveHand == null)
|
||||
handSys.GetActiveHand(owner) is not { } activeHand)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = hands.ActiveHand;
|
||||
value = activeHand;
|
||||
return true;
|
||||
}
|
||||
case ActiveHandFree:
|
||||
{
|
||||
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||
hands.ActiveHand == null)
|
||||
handSys.GetActiveHand(owner) is not { } activeHand)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = hands.ActiveHand.IsEmpty;
|
||||
value = handSys.HandIsEmpty((owner, hands), activeHand);
|
||||
return true;
|
||||
}
|
||||
case CanMove:
|
||||
@@ -204,16 +204,16 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
|
||||
{
|
||||
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||
hands.ActiveHand == null)
|
||||
handSys.GetActiveHand(owner) is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var handos = new List<string>();
|
||||
|
||||
foreach (var (id, hand) in hands.Hands)
|
||||
foreach (var id in hands.Hands.Keys)
|
||||
{
|
||||
if (!hand.IsEmpty)
|
||||
if (!handSys.HandIsEmpty((owner, hands), id))
|
||||
continue;
|
||||
|
||||
handos.Add(id);
|
||||
@@ -226,16 +226,16 @@ public sealed partial class NPCBlackboard : IEnumerable<KeyValuePair<string, obj
|
||||
{
|
||||
if (!TryGetValue(Owner, out owner, entManager) ||
|
||||
!entManager.TryGetComponent<HandsComponent>(owner, out var hands) ||
|
||||
hands.ActiveHand == null)
|
||||
handSys.GetActiveHand(owner) is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var handos = new List<string>();
|
||||
|
||||
foreach (var (id, hand) in hands.Hands)
|
||||
foreach (var id in hands.Hands.Keys)
|
||||
{
|
||||
if (!hand.IsEmpty)
|
||||
if (!handSys.HandIsEmpty((owner, hands), id))
|
||||
continue;
|
||||
|
||||
handos.Add(id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.NPC.Queries;
|
||||
using Content.Server.NPC.Queries.Considerations;
|
||||
using Content.Server.NPC.Queries.Curves;
|
||||
@@ -44,6 +45,7 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
[Dependency] private readonly DrinkSystem _drink = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly FoodSystem _food = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
|
||||
@@ -256,8 +258,9 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
}
|
||||
case TargetAmmoMatchesCon:
|
||||
{
|
||||
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out Hand? activeHand, EntityManager) ||
|
||||
!TryComp<BallisticAmmoProviderComponent>(activeHand.HeldEntity, out var heldGun))
|
||||
if (!blackboard.TryGetValue(NPCBlackboard.ActiveHand, out string? activeHand, EntityManager) ||
|
||||
!_hands.TryGetHeldItem(owner, activeHand, out var heldEntity) ||
|
||||
!TryComp<BallisticAmmoProviderComponent>(heldEntity, out var heldGun))
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public sealed class FTLDiskCommand : LocalizedCommands
|
||||
|
||||
if (_entManager.TryGetComponent<StorageComponent>(cdCaseUid, out var storage) && storageSystem.Insert(cdCaseUid, cdUid, out _, storageComp: storage, playSound: false))
|
||||
{
|
||||
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
|
||||
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand))
|
||||
{
|
||||
handsSystem.TryPickup(entity, cdCaseUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
|
||||
}
|
||||
@@ -161,7 +161,7 @@ public sealed class FTLDiskCommand : LocalizedCommands
|
||||
{
|
||||
_entManager.DeleteEntity(cdCaseUid); // something went wrong so just yeet the chaf
|
||||
|
||||
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
|
||||
if (_entManager.TryGetComponent<HandsComponent>(entity, out var handsComponent) && handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand))
|
||||
{
|
||||
handsSystem.TryPickup(entity, cdUid, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
|
||||
}
|
||||
|
||||
@@ -217,8 +217,8 @@ public sealed partial class BorgSystem
|
||||
|
||||
var handId = $"{uid}-item{component.HandCounter}";
|
||||
component.HandCounter++;
|
||||
_hands.AddHand(chassis, handId, HandLocation.Middle, hands);
|
||||
_hands.DoPickup(chassis, hands.Hands[handId], item, hands);
|
||||
_hands.AddHand((chassis, hands), handId, HandLocation.Middle);
|
||||
_hands.DoPickup(chassis, handId, item, hands);
|
||||
EnsureComp<UnremoveableComponent>(item);
|
||||
component.ProvidedItems.Add(handId, item);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ public sealed partial class BorgSystem
|
||||
foreach (var (hand, item) in component.ProvidedItems)
|
||||
{
|
||||
QueueDel(item);
|
||||
_hands.RemoveHand(chassis, hand, hands);
|
||||
_hands.RemoveHand(chassis, hand);
|
||||
}
|
||||
component.ProvidedItems.Clear();
|
||||
return;
|
||||
@@ -252,7 +252,7 @@ public sealed partial class BorgSystem
|
||||
RemComp<UnremoveableComponent>(item);
|
||||
_container.Insert(item, component.ProvidedContainer);
|
||||
}
|
||||
_hands.RemoveHand(chassis, handId, hands);
|
||||
_hands.RemoveHand(chassis, handId);
|
||||
}
|
||||
component.ProvidedItems.Clear();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Tabletop.Components;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -22,6 +23,7 @@ namespace Content.Server.Tabletop
|
||||
{
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly EyeSystem _eye = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly ViewSubscriberSystem _viewSubscriberSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -84,18 +86,13 @@ namespace Content.Server.Tabletop
|
||||
if (component.Session is not { } session)
|
||||
return;
|
||||
|
||||
if (hands.ActiveHand == null)
|
||||
if (!_hands.TryGetActiveItem(uid, out var handEnt))
|
||||
return;
|
||||
|
||||
if (hands.ActiveHand.HeldEntity == null)
|
||||
return;
|
||||
|
||||
var handEnt = hands.ActiveHand.HeldEntity.Value;
|
||||
|
||||
if (!TryComp<ItemComponent>(handEnt, out var item))
|
||||
return;
|
||||
|
||||
var meta = MetaData(handEnt);
|
||||
var meta = MetaData(handEnt.Value);
|
||||
var protoId = meta.EntityPrototype?.ID;
|
||||
|
||||
var hologram = Spawn(protoId, session.Position.Offset(-1, 0));
|
||||
|
||||
@@ -90,9 +90,9 @@ public sealed class InnateToolSystem : EntitySystem
|
||||
|
||||
if (TryComp<HandsComponent>(uid, out var hands))
|
||||
{
|
||||
foreach (var hand in hands.Hands)
|
||||
foreach (var hand in hands.Hands.Keys)
|
||||
{
|
||||
_sharedHandsSystem.TryDrop(uid, hand.Value, checkActionBlocker: false, handsComp: hands);
|
||||
_sharedHandsSystem.TryDrop((uid, hands), hand, checkActionBlocker: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -14,6 +14,7 @@ namespace Content.Server.Verbs
|
||||
public sealed class VerbSystem : SharedVerbSystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly IAdminManager _adminMgr = default!;
|
||||
|
||||
@@ -91,8 +92,7 @@ namespace Content.Server.Verbs
|
||||
{
|
||||
// first get the held item. again.
|
||||
EntityUid? holding = null;
|
||||
if (TryComp(user, out HandsComponent? hands) &&
|
||||
hands.ActiveHandEntity is EntityUid heldEntity)
|
||||
if (_hands.GetActiveItem(user) is { } heldEntity)
|
||||
{
|
||||
holding = heldEntity;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Hands.Systems;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Storage;
|
||||
@@ -40,24 +39,20 @@ public sealed class StorageVoiceControlSystem : EntitySystem
|
||||
if (!TryComp<StorageComponent>(ent, out var storage))
|
||||
return;
|
||||
|
||||
// Get the hands component
|
||||
if (!TryComp<HandsComponent>(args.Source, out var hands))
|
||||
return;
|
||||
|
||||
// If the player has something in their hands, try to insert it into the storage
|
||||
if (hands.ActiveHand != null && hands.ActiveHand.HeldEntity.HasValue)
|
||||
if (_hands.TryGetActiveItem(ent.Owner, out var activeItem))
|
||||
{
|
||||
// Disallow insertion and provide a reason why if the person decides to insert the item into itself
|
||||
if (ent.Owner.Equals(hands.ActiveHand.HeldEntity.Value))
|
||||
if (ent.Owner.Equals(activeItem.Value))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", hands.ActiveHand.HeldEntity.Value)), ent, args.Source);
|
||||
_popup.PopupEntity(Loc.GetString("comp-storagevoicecontrol-self-insert", ("entity", activeItem.Value)), ent, args.Source);
|
||||
return;
|
||||
}
|
||||
if (_storage.CanInsert(ent, hands.ActiveHand.HeldEntity.Value, out var failedReason))
|
||||
if (_storage.CanInsert(ent, activeItem.Value, out var failedReason))
|
||||
{
|
||||
// We adminlog before insertion, otherwise the logger will attempt to pull info on an entity that no longer is present and throw an exception
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} inserted {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control");
|
||||
_storage.Insert(ent, hands.ActiveHand.HeldEntity.Value, out _);
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.Source)} inserted {ToPrettyString(activeItem.Value)} into {ToPrettyString(ent)} via voice control");
|
||||
_storage.Insert(ent, activeItem.Value, out _);
|
||||
return;
|
||||
}
|
||||
{
|
||||
@@ -67,7 +62,7 @@ public sealed class StorageVoiceControlSystem : EntitySystem
|
||||
_popup.PopupEntity(Loc.GetString(failedReason), ent, args.Source);
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(args.Source)} failed to insert {ToPrettyString(hands.ActiveHand.HeldEntity.Value)} into {ToPrettyString(ent)} via voice control");
|
||||
$"{ToPrettyString(args.Source)} failed to insert {ToPrettyString(activeItem.Value)} into {ToPrettyString(ent)} via voice control");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -80,7 +75,7 @@ public sealed class StorageVoiceControlSystem : EntitySystem
|
||||
// E.g "go go s" would give you the screwdriver because "screwdriver" contains "s"
|
||||
if (Name(item).Contains(args.MessageWithoutPhrase))
|
||||
{
|
||||
ExtractItemFromStorage(ent, item, args.Source, hands);
|
||||
ExtractItemFromStorage(ent, item, args.Source);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -92,16 +87,14 @@ public sealed class StorageVoiceControlSystem : EntitySystem
|
||||
/// <param name="ent">The entity with the <see cref="StorageVoiceControlComponent"/></param>
|
||||
/// <param name="item">The entity to be extracted from the attached storage</param>
|
||||
/// <param name="source">The entity wearing the item</param>
|
||||
/// <param name="hands">The <see cref="HandsComponent"/> of the person wearing the item</param>
|
||||
private void ExtractItemFromStorage(Entity<StorageVoiceControlComponent> ent,
|
||||
EntityUid item,
|
||||
EntityUid source,
|
||||
HandsComponent hands)
|
||||
EntityUid source)
|
||||
{
|
||||
_container.RemoveEntity(ent, item);
|
||||
_adminLogger.Add(LogType.Action,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(source)} retrieved {ToPrettyString(item)} from {ToPrettyString(ent)} via voice control");
|
||||
_hands.TryPickup(source, item, handsComp: hands);
|
||||
_hands.TryPickup(source, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.Construction;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.GameTicking;
|
||||
@@ -24,6 +25,7 @@ public sealed class WiresSystem : SharedWiresSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
@@ -405,19 +407,13 @@ public sealed class WiresSystem : SharedWiresSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var activeHand = handsComponent.ActiveHand;
|
||||
|
||||
if (activeHand == null)
|
||||
if (!_hands.TryGetActiveItem((player, handsComponent), out var heldEntity))
|
||||
return;
|
||||
|
||||
if (activeHand.HeldEntity == null)
|
||||
if (!EntityManager.TryGetComponent(heldEntity, out ToolComponent? tool))
|
||||
return;
|
||||
|
||||
var activeHandEntity = activeHand.HeldEntity.Value;
|
||||
if (!EntityManager.TryGetComponent(activeHandEntity, out ToolComponent? tool))
|
||||
return;
|
||||
|
||||
TryDoWireAction(uid, player, activeHandEntity, args.Id, args.Action, component, tool);
|
||||
TryDoWireAction(uid, player, heldEntity.Value, args.Id, args.Action, component, tool);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, WiresComponent component, WireDoAfterEvent args)
|
||||
|
||||
@@ -3,7 +3,7 @@ using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
@@ -21,6 +21,7 @@ public abstract class SharedIdCardSystem : EntitySystem
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedAccessSystem _access = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -83,8 +84,7 @@ public abstract class SharedIdCardSystem : EntitySystem
|
||||
public bool TryFindIdCard(EntityUid uid, out Entity<IdCardComponent> idCard)
|
||||
{
|
||||
// check held item?
|
||||
if (TryComp(uid, out HandsComponent? hands) &&
|
||||
hands.ActiveHandEntity is EntityUid heldItem &&
|
||||
if (_hands.GetActiveItem(uid) is { } heldItem &&
|
||||
TryGetIdCard(heldItem, out idCard))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed partial class BlockingSystem : EntitySystem
|
||||
if (!handQuery.TryGetComponent(args.Performer, out var hands))
|
||||
return;
|
||||
|
||||
var shields = _handsSystem.EnumerateHeld(args.Performer, hands).ToArray();
|
||||
var shields = _handsSystem.EnumerateHeld((args.Performer, hands)).ToArray();
|
||||
|
||||
foreach (var shield in shields)
|
||||
{
|
||||
@@ -277,7 +277,7 @@ public sealed partial class BlockingSystem : EntitySystem
|
||||
if (!handQuery.TryGetComponent(user, out var hands))
|
||||
return;
|
||||
|
||||
var shields = _handsSystem.EnumerateHeld(user, hands).ToArray();
|
||||
var shields = _handsSystem.EnumerateHeld((user, hands)).ToArray();
|
||||
|
||||
foreach (var shield in shields)
|
||||
{
|
||||
|
||||
@@ -947,12 +947,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
|
||||
if (!entity.Comp.HeldOnly)
|
||||
return true;
|
||||
|
||||
if (TryComp(examiner, out HandsComponent? handsComp))
|
||||
{
|
||||
return Hands.IsHolding(examiner, entity, out _, handsComp);
|
||||
}
|
||||
|
||||
return true;
|
||||
return Hands.IsHolding(examiner, entity, out _);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
|
||||
|
||||
@@ -255,7 +255,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
}
|
||||
|
||||
// Drop the held item onto the floor. Return if the user cannot drop.
|
||||
if (!_handsSystem.TryDrop(args.User, args.Used, handsComp: hands))
|
||||
if (!_handsSystem.TryDrop(args.User, args.Used))
|
||||
return;
|
||||
|
||||
slots.Sort(SortEmpty);
|
||||
@@ -395,17 +395,17 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (!Resolve(user, ref hands, false))
|
||||
return false;
|
||||
|
||||
if (hands.ActiveHand?.HeldEntity is not { } held)
|
||||
if (!_handsSystem.TryGetActiveItem((uid, hands), out var held))
|
||||
return false;
|
||||
|
||||
if (!CanInsert(uid, held, user, slot))
|
||||
if (!CanInsert(uid, held.Value, user, slot))
|
||||
return false;
|
||||
|
||||
// hands.Drop(item) checks CanDrop action blocker
|
||||
if (!_handsSystem.TryDrop(user, hands.ActiveHand))
|
||||
if (!_handsSystem.TryDrop(user, hands.ActiveHandId!))
|
||||
return false;
|
||||
|
||||
Insert(uid, slot, held, user, excludeUserAudio: excludeUserAudio);
|
||||
Insert(uid, slot, held.Value, user, excludeUserAudio: excludeUserAudio);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -428,16 +428,14 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
TryComp(user, out HandsComponent? handsComp);
|
||||
|
||||
if (!TryGetAvailableSlot(ent,
|
||||
item,
|
||||
user == null ? null : (user.Value, handsComp),
|
||||
user,
|
||||
out var itemSlot,
|
||||
emptyOnly: true))
|
||||
return false;
|
||||
|
||||
if (user != null && !_handsSystem.TryDrop(user.Value, item, handsComp: handsComp))
|
||||
if (user != null && !_handsSystem.TryDrop(user.Value, item))
|
||||
return false;
|
||||
|
||||
Insert(ent, itemSlot, item, user, excludeUserAudio: excludeUserAudio);
|
||||
@@ -466,7 +464,7 @@ namespace Content.Shared.Containers.ItemSlots
|
||||
&& Resolve(user, ref user.Comp)
|
||||
&& _handsSystem.IsHolding(user, item))
|
||||
{
|
||||
if (!_handsSystem.CanDrop(user, item, user.Comp))
|
||||
if (!_handsSystem.CanDrop(user, item))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -397,6 +397,10 @@ namespace Content.Shared.Cuffs
|
||||
/// </summary>
|
||||
private void OnHandCountChanged(Entity<CuffableComponent> ent, ref HandCountChangedEvent message)
|
||||
{
|
||||
// TODO: either don't store a container ref, or make it actually nullable.
|
||||
if (ent.Comp.Container == default!)
|
||||
return;
|
||||
|
||||
var dirty = false;
|
||||
var handCount = CompOrNull<HandsComponent>(ent.Owner)?.Count ?? 0;
|
||||
|
||||
@@ -431,19 +435,19 @@ namespace Content.Shared.Cuffs
|
||||
return;
|
||||
|
||||
var freeHands = 0;
|
||||
foreach (var hand in _hands.EnumerateHands(uid, handsComponent))
|
||||
foreach (var hand in _hands.EnumerateHands((uid, handsComponent)))
|
||||
{
|
||||
if (hand.HeldEntity == null)
|
||||
if (!_hands.TryGetHeldItem((uid, handsComponent), hand, out var held))
|
||||
{
|
||||
freeHands++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this entity removable? (it might be an existing handcuff blocker)
|
||||
if (HasComp<UnremoveableComponent>(hand.HeldEntity))
|
||||
if (HasComp<UnremoveableComponent>(held))
|
||||
continue;
|
||||
|
||||
_hands.DoDrop(uid, hand, true, handsComponent);
|
||||
_hands.DoDrop(uid, hand, true);
|
||||
freeHands++;
|
||||
if (freeHands == 2)
|
||||
break;
|
||||
|
||||
@@ -141,7 +141,7 @@ public abstract class SharedDisposalUnitSystem : EntitySystem
|
||||
Category = VerbCategory.Insert,
|
||||
Act = () =>
|
||||
{
|
||||
_handsSystem.TryDropIntoContainer(args.User, args.Using.Value, component.Container, checkActionBlocker: false, args.Hands);
|
||||
_handsSystem.TryDropIntoContainer((args.User, args.Hands), args.Using.Value, component.Container, checkActionBlocker: false);
|
||||
_adminLog.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(args.User):player} inserted {ToPrettyString(args.Using.Value)} into {ToPrettyString(uid)}");
|
||||
AfterInsert(uid, component, args.Using.Value, args.User);
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
||||
return true;
|
||||
|
||||
// If the user changes which hand is active at all, interrupt the do-after
|
||||
if (args.BreakOnHandChange && hands.ActiveHand?.Name != doAfter.InitialHand)
|
||||
if (args.BreakOnHandChange && hands.ActiveHandId != doAfter.InitialHand)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -221,8 +221,8 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
||||
if (!TryComp(args.User, out HandsComponent? handsComponent))
|
||||
return false;
|
||||
|
||||
doAfter.InitialHand = handsComponent.ActiveHand?.Name;
|
||||
doAfter.InitialItem = handsComponent.ActiveHandEntity;
|
||||
doAfter.InitialHand = handsComponent.ActiveHandId;
|
||||
doAfter.InitialItem = _hands.GetActiveItem((args.User, handsComponent));
|
||||
}
|
||||
|
||||
doAfter.NetInitialItem = GetNetEntity(doAfter.InitialItem);
|
||||
|
||||
@@ -70,7 +70,7 @@ public sealed class DeployFoldableSystem : EntitySystem
|
||||
}
|
||||
|
||||
if (!TryComp(args.User, out HandsComponent? hands)
|
||||
|| !_hands.TryDrop(args.User, args.Used, targetDropLocation: args.ClickLocation, handsComp: hands))
|
||||
|| !_hands.TryDrop((args.User, hands), args.Used, targetDropLocation: args.ClickLocation))
|
||||
return;
|
||||
|
||||
if (!_foldable.TrySetFolded(ent, foldable, false))
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
|
||||
namespace Content.Shared.Hands.Components;
|
||||
|
||||
/// <summary>
|
||||
/// These helpers exist to make getting basic information out of the hands component more convenient, without
|
||||
/// needing to resolve hands system or something like that.
|
||||
/// </summary>
|
||||
public static class HandHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if any hand is free. This is a LinQ method, not a property, so
|
||||
/// cache it instead of accessing this multiple times.
|
||||
/// </summary>
|
||||
public static bool IsAnyHandFree(this HandsComponent component) => component.Hands.Values.Any(hand => hand.IsEmpty);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so
|
||||
/// cache it instead of accessing this multiple times.
|
||||
/// </summary>
|
||||
public static int CountFreeHands(this HandsComponent component) => component.Hands.Values.Count(hand => hand.IsEmpty);
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of hands that are not currently holding anything. This is a LinQ method, not a property, so
|
||||
/// cache it instead of accessing this multiple times.
|
||||
/// </summary>
|
||||
public static int CountFreeableHands(this Entity<HandsComponent> component, SharedHandsSystem system)
|
||||
{
|
||||
return system.CountFreeableHands(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache
|
||||
/// it instead of accessing this multiple times.
|
||||
/// </summary>
|
||||
public static IEnumerable<Hand> GetFreeHands(this HandsComponent component) => component.Hands.Values.Where(hand => !hand.IsEmpty);
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of hands that are currently holding nothing. This is a LinQ method, not a property, so cache
|
||||
/// it instead of accessing this multiple times.
|
||||
/// </summary>
|
||||
public static IEnumerable<string> GetFreeHandNames(this HandsComponent component) => GetFreeHands(component).Select(hand => hand.Name);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.DisplacementMap;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -13,50 +12,50 @@ public sealed partial class HandsComponent : Component
|
||||
/// <summary>
|
||||
/// The currently active hand.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Hand? ActiveHand;
|
||||
[DataField]
|
||||
public string? ActiveHandId;
|
||||
|
||||
/// <summary>
|
||||
/// The item currently held in the active hand.
|
||||
/// Dictionary relating a unique hand ID corresponding to a container slot on the attached entity to a class containing information about the Hand itself.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityUid? ActiveHandEntity => ActiveHand?.HeldEntity;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField]
|
||||
public Dictionary<string, Hand> Hands = new();
|
||||
|
||||
/// <summary>
|
||||
/// The number of hands
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int Count => Hands.Count;
|
||||
|
||||
/// <summary>
|
||||
/// List of hand-names. These are keys for <see cref="Hands"/>. The order of this list determines the order in which hands are iterated over.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> SortedHands = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true, the items in the hands won't be affected by explosions.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool DisableExplosionRecursion = false;
|
||||
public bool DisableExplosionRecursion;
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the speed at which items are thrown.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float BaseThrowspeed { get; set; } = 11f;
|
||||
public float BaseThrowspeed = 11f;
|
||||
|
||||
/// <summary>
|
||||
/// Distance after which longer throw targets stop increasing throw impulse.
|
||||
/// </summary>
|
||||
[DataField("throwRange")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float ThrowRange { get; set; } = 8f;
|
||||
[DataField]
|
||||
public float ThrowRange = 8f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to add in-hand sprites for held items. Some entities (e.g., drones) don't want these.
|
||||
/// Used by the client.
|
||||
/// </summary>
|
||||
[DataField("showInHands")]
|
||||
[DataField]
|
||||
public bool ShowInHands = true;
|
||||
|
||||
/// <summary>
|
||||
@@ -68,14 +67,13 @@ public sealed partial class HandsComponent : Component
|
||||
/// <summary>
|
||||
/// The time at which throws will be allowed again.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoPausedField]
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan NextThrowTime;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum time inbetween throws.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public TimeSpan ThrowCooldown = TimeSpan.FromSeconds(0.5f);
|
||||
|
||||
/// <summary>
|
||||
@@ -103,48 +101,37 @@ public sealed partial class HandsComponent : Component
|
||||
public bool CanBeStripped = true;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class Hand //TODO: This should definitely be a struct - Jezi
|
||||
public partial record struct Hand
|
||||
{
|
||||
[ViewVariables]
|
||||
public string Name { get; }
|
||||
[DataField]
|
||||
public HandLocation Location = HandLocation.Right;
|
||||
|
||||
[ViewVariables]
|
||||
public HandLocation Location { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The container used to hold the contents of this hand. Nullable because the client must get the containers via <see cref="ContainerManagerComponent"/>,
|
||||
/// which may not be synced with the server when the client hands are created.
|
||||
/// </summary>
|
||||
[ViewVariables, NonSerialized]
|
||||
public ContainerSlot? Container;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? HeldEntity => Container?.ContainedEntity;
|
||||
|
||||
public bool IsEmpty => HeldEntity == null;
|
||||
|
||||
public Hand(string name, HandLocation location, ContainerSlot? container = null)
|
||||
public Hand()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Hand(HandLocation location)
|
||||
{
|
||||
Name = name;
|
||||
Location = location;
|
||||
Container = container;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class HandsComponentState : ComponentState
|
||||
{
|
||||
public readonly List<Hand> Hands;
|
||||
public readonly List<string> HandNames;
|
||||
public readonly string? ActiveHand;
|
||||
public readonly Dictionary<string, Hand> Hands;
|
||||
public readonly List<string> SortedHands;
|
||||
public readonly string? ActiveHandId;
|
||||
|
||||
public HandsComponentState(HandsComponent handComp)
|
||||
{
|
||||
// cloning lists because of test networking.
|
||||
Hands = new(handComp.Hands.Values);
|
||||
HandNames = new(handComp.SortedHands);
|
||||
ActiveHand = handComp.ActiveHand?.Name;
|
||||
Hands = new(handComp.Hands);
|
||||
SortedHands = new(handComp.SortedHands);
|
||||
ActiveHandId = handComp.ActiveHandId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,17 +6,17 @@ namespace Content.Shared.Hands.EntitySystems;
|
||||
// These functions are mostly unused except for some AI operator stuff
|
||||
// Nothing stops them from being used in general. If they ever get used elsewhere, then this file probably needs to be renamed.
|
||||
|
||||
public abstract partial class SharedHandsSystem : EntitySystem
|
||||
public abstract partial class SharedHandsSystem
|
||||
{
|
||||
public bool TrySelect(EntityUid uid, EntityUid? entity, HandsComponent? handsComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (!IsHolding(uid, entity, out var hand, handsComp))
|
||||
if (!IsHolding((uid, handsComp), entity, out var hand))
|
||||
return false;
|
||||
|
||||
SetActiveHand(uid, hand, handsComp);
|
||||
SetActiveHand((uid, handsComp), hand);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,12 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
foreach (var hand in handsComp.Hands.Keys)
|
||||
{
|
||||
if (TryComp(hand.HeldEntity, out component))
|
||||
if (!TryGetHeldItem((uid, handsComp), hand, out var held))
|
||||
continue;
|
||||
|
||||
if (TryComp(held, out component))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
@@ -28,10 +29,10 @@ public abstract partial class SharedHandsSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var gotUnequipped = new GotUnequippedHandEvent(uid, args.Entity, hand);
|
||||
var gotUnequipped = new GotUnequippedHandEvent(uid, args.Entity, hand.Value);
|
||||
RaiseLocalEvent(args.Entity, gotUnequipped);
|
||||
|
||||
var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand);
|
||||
var didUnequip = new DidUnequipHandEvent(uid, args.Entity, hand.Value);
|
||||
RaiseLocalEvent(uid, didUnequip);
|
||||
|
||||
if (TryComp(args.Entity, out VirtualItemComponent? @virtual))
|
||||
@@ -47,26 +48,29 @@ public abstract partial class SharedHandsSystem
|
||||
/// <summary>
|
||||
/// Checks whether an entity can drop a given entity. Will return false if they are not holding the entity.
|
||||
/// </summary>
|
||||
public bool CanDrop(EntityUid uid, EntityUid entity, HandsComponent? handsComp = null, bool checkActionBlocker = true)
|
||||
public bool CanDrop(Entity<HandsComponent?> ent, EntityUid entity, bool checkActionBlocker = true)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!IsHolding(uid, entity, out var hand, handsComp))
|
||||
if (!IsHolding(ent, entity, out var hand))
|
||||
return false;
|
||||
|
||||
return CanDropHeld(uid, hand, checkActionBlocker);
|
||||
return CanDropHeld(ent, hand, checkActionBlocker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the contents of a hand is able to be removed from its container.
|
||||
/// </summary>
|
||||
public bool CanDropHeld(EntityUid uid, Hand hand, bool checkActionBlocker = true)
|
||||
public bool CanDropHeld(EntityUid uid, string handId, bool checkActionBlocker = true)
|
||||
{
|
||||
if (hand.Container?.ContainedEntity is not {} held)
|
||||
if (!ContainerSystem.TryGetContainer(uid, handId, out var container))
|
||||
return false;
|
||||
|
||||
if (!ContainerSystem.CanRemove(held, hand.Container))
|
||||
if (container.ContainedEntities.FirstOrNull() is not {} held)
|
||||
return false;
|
||||
|
||||
if (!ContainerSystem.CanRemove(held, container))
|
||||
return false;
|
||||
|
||||
if (checkActionBlocker && !_actionBlocker.CanDrop(uid))
|
||||
@@ -78,98 +82,100 @@ public abstract partial class SharedHandsSystem
|
||||
/// <summary>
|
||||
/// Attempts to drop the item in the currently active hand.
|
||||
/// </summary>
|
||||
public bool TryDrop(EntityUid uid, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
|
||||
public bool TryDrop(Entity<HandsComponent?> ent, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHand == null)
|
||||
if (ent.Comp.ActiveHandId == null)
|
||||
return false;
|
||||
|
||||
return TryDrop(uid, handsComp.ActiveHand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
|
||||
return TryDrop(ent, ent.Comp.ActiveHandId, targetDropLocation, checkActionBlocker, doDropInteraction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops an item at the target location.
|
||||
/// </summary>
|
||||
public bool TryDrop(EntityUid uid, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
|
||||
public bool TryDrop(Entity<HandsComponent?> ent, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!IsHolding(uid, entity, out var hand, handsComp))
|
||||
if (!IsHolding(ent, entity, out var hand))
|
||||
return false;
|
||||
|
||||
return TryDrop(uid, hand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
|
||||
return TryDrop(ent, hand, targetDropLocation, checkActionBlocker, doDropInteraction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops a hands contents at the target location.
|
||||
/// </summary>
|
||||
public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, HandsComponent? handsComp = null)
|
||||
public bool TryDrop(Entity<HandsComponent?> ent, string handId, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!CanDropHeld(uid, hand, checkActionBlocker))
|
||||
if (!CanDropHeld(ent, handId, checkActionBlocker))
|
||||
return false;
|
||||
|
||||
var entity = hand.HeldEntity!.Value;
|
||||
if (!TryGetHeldItem(ent, handId, out var entity))
|
||||
return false;
|
||||
|
||||
// if item is a fake item (like with pulling), just delete it rather than bothering with trying to drop it into the world
|
||||
if (TryComp(entity, out VirtualItemComponent? @virtual))
|
||||
_virtualSystem.DeleteVirtualItem((entity, @virtual), uid);
|
||||
_virtualSystem.DeleteVirtualItem((entity.Value, @virtual), ent);
|
||||
|
||||
if (TerminatingOrDeleted(entity))
|
||||
return true;
|
||||
|
||||
var itemXform = Transform(entity);
|
||||
var itemXform = Transform(entity.Value);
|
||||
if (itemXform.MapUid == null)
|
||||
return true;
|
||||
|
||||
var userXform = Transform(uid);
|
||||
var isInContainer = ContainerSystem.IsEntityOrParentInContainer(uid, xform: userXform);
|
||||
var userXform = Transform(ent);
|
||||
var isInContainer = ContainerSystem.IsEntityOrParentInContainer(ent, xform: userXform);
|
||||
|
||||
// if the user is in a container, drop the item inside the container
|
||||
if (isInContainer) {
|
||||
TransformSystem.DropNextTo((entity, itemXform), (uid, userXform));
|
||||
if (isInContainer)
|
||||
{
|
||||
TransformSystem.DropNextTo((entity.Value, itemXform), (ent, userXform));
|
||||
return true;
|
||||
}
|
||||
|
||||
// drop the item with heavy calculations from their hands and place it at the calculated interaction range position
|
||||
// The DoDrop is handle if there's no drop target
|
||||
DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);
|
||||
DoDrop(ent, handId, doDropInteraction: doDropInteraction);
|
||||
|
||||
// if there's no drop location stop here
|
||||
if (targetDropLocation == null)
|
||||
return true;
|
||||
|
||||
// otherwise, also move dropped item and rotate it properly according to grid/map
|
||||
var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity);
|
||||
var (itemPos, itemRot) = TransformSystem.GetWorldPositionRotation(entity.Value);
|
||||
var origin = new MapCoordinates(itemPos, itemXform.MapID);
|
||||
var target = TransformSystem.ToMapCoordinates(targetDropLocation.Value);
|
||||
TransformSystem.SetWorldPositionRotation(entity, GetFinalDropCoordinates(uid, origin, target, entity), itemRot);
|
||||
TransformSystem.SetWorldPositionRotation(entity.Value, GetFinalDropCoordinates(ent, origin, target, entity.Value), itemRot);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to move a held item from a hand into a container that is not another hand, without dropping it on the floor in-between.
|
||||
/// </summary>
|
||||
public bool TryDropIntoContainer(EntityUid uid, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true, HandsComponent? handsComp = null)
|
||||
public bool TryDropIntoContainer(Entity<HandsComponent?> ent, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!IsHolding(uid, entity, out var hand, handsComp))
|
||||
if (!IsHolding(ent, entity, out var hand))
|
||||
return false;
|
||||
|
||||
if (!CanDropHeld(uid, hand, checkActionBlocker))
|
||||
if (!CanDropHeld(ent, hand, checkActionBlocker))
|
||||
return false;
|
||||
|
||||
if (!ContainerSystem.CanInsert(entity, targetContainer))
|
||||
return false;
|
||||
|
||||
DoDrop(uid, hand, false, handsComp);
|
||||
DoDrop(ent, hand, false);
|
||||
ContainerSystem.Insert(entity, targetContainer);
|
||||
return true;
|
||||
}
|
||||
@@ -202,34 +208,38 @@ public abstract partial class SharedHandsSystem
|
||||
/// <summary>
|
||||
/// Removes the contents of a hand from its container. Assumes that the removal is allowed. In general, you should not be calling this directly.
|
||||
/// </summary>
|
||||
public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? handsComp = null, bool log = true)
|
||||
public virtual void DoDrop(Entity<HandsComponent?> ent,
|
||||
string handId,
|
||||
bool doDropInteraction = true,
|
||||
bool log = true)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (hand.Container?.ContainedEntity == null)
|
||||
if (!ContainerSystem.TryGetContainer(ent, handId, out var container))
|
||||
return;
|
||||
|
||||
var entity = hand.Container.ContainedEntity.Value;
|
||||
|
||||
if (TerminatingOrDeleted(uid) || TerminatingOrDeleted(entity))
|
||||
if (!TryGetHeldItem(ent, handId, out var entity))
|
||||
return;
|
||||
|
||||
if (!ContainerSystem.Remove(entity, hand.Container))
|
||||
if (TerminatingOrDeleted(ent) || TerminatingOrDeleted(entity))
|
||||
return;
|
||||
|
||||
if (!ContainerSystem.Remove(entity.Value, container))
|
||||
{
|
||||
Log.Error($"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(uid)}. Hand: {hand.Name}.");
|
||||
Log.Error($"Failed to remove {ToPrettyString(entity)} from users hand container when dropping. User: {ToPrettyString(ent)}. Hand: {handId}.");
|
||||
return;
|
||||
}
|
||||
|
||||
Dirty(uid, handsComp);
|
||||
Dirty(ent);
|
||||
|
||||
if (doDropInteraction)
|
||||
_interactionSystem.DroppedInteraction(uid, entity);
|
||||
_interactionSystem.DroppedInteraction(ent, entity.Value);
|
||||
|
||||
if (log)
|
||||
_adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(uid):user} dropped {ToPrettyString(entity):entity}");
|
||||
_adminLogger.Add(LogType.Drop, LogImpact.Low, $"{ToPrettyString(ent):user} dropped {ToPrettyString(entity):entity}");
|
||||
|
||||
if (hand == handsComp.ActiveHand)
|
||||
RaiseLocalEvent(entity, new HandDeselectedEvent(uid));
|
||||
if (handId == ent.Comp.ActiveHandId)
|
||||
RaiseLocalEvent(entity.Value, new HandDeselectedEvent(ent));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,20 +97,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!_actionBlocker.CanInteract(session.AttachedEntity.Value, null))
|
||||
return;
|
||||
|
||||
if (component.ActiveHand == null || component.Hands.Count < 2)
|
||||
if (component.ActiveHandId == null || component.Hands.Count < 2)
|
||||
return;
|
||||
|
||||
var currentIndex = component.SortedHands.IndexOf(component.ActiveHand.Name);
|
||||
var currentIndex = component.SortedHands.IndexOf(component.ActiveHandId);
|
||||
var newActiveIndex = (currentIndex + (reverse ? -1 : 1) + component.Hands.Count) % component.Hands.Count;
|
||||
var nextHand = component.SortedHands[newActiveIndex];
|
||||
|
||||
TrySetActiveHand(session.AttachedEntity.Value, nextHand, component);
|
||||
TrySetActiveHand((session.AttachedEntity.Value, component), nextHand);
|
||||
}
|
||||
|
||||
private bool DropPressed(ICommonSession? session, EntityCoordinates coords, EntityUid netEntity)
|
||||
{
|
||||
if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHand != null)
|
||||
TryDrop(session.AttachedEntity.Value, hands.ActiveHand, coords, handsComp: hands);
|
||||
if (TryComp(session?.AttachedEntity, out HandsComponent? hands) && hands.ActiveHandId != null)
|
||||
TryDrop((session.AttachedEntity.Value, hands), hands.ActiveHandId, coords);
|
||||
|
||||
// always send to server.
|
||||
return false;
|
||||
@@ -122,14 +122,14 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
Hand? hand;
|
||||
if (handName == null || !handsComp.Hands.TryGetValue(handName, out hand))
|
||||
hand = handsComp.ActiveHand;
|
||||
var hand = handName;
|
||||
if (!TryGetHand(uid, hand, out _))
|
||||
hand = handsComp.ActiveHandId;
|
||||
|
||||
if (hand?.HeldEntity is not { } held)
|
||||
if (!TryGetHeldItem((uid, handsComp), hand, out var held))
|
||||
return false;
|
||||
|
||||
return _interactionSystem.InteractionActivate(uid, held);
|
||||
return _interactionSystem.InteractionActivate(uid, held.Value);
|
||||
}
|
||||
|
||||
public bool TryInteractHandWithActiveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
|
||||
@@ -137,16 +137,13 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHandEntity == null)
|
||||
if (!TryGetActiveItem((uid, handsComp), out var activeHeldItem))
|
||||
return false;
|
||||
|
||||
if (!handsComp.Hands.TryGetValue(handName, out var hand))
|
||||
if (!TryGetHeldItem((uid, handsComp), handName, out var held))
|
||||
return false;
|
||||
|
||||
if (hand.HeldEntity == null)
|
||||
return false;
|
||||
|
||||
_interactionSystem.InteractUsing(uid, handsComp.ActiveHandEntity.Value, hand.HeldEntity.Value, Transform(hand.HeldEntity.Value).Coordinates);
|
||||
_interactionSystem.InteractUsing(uid, activeHeldItem.Value, held.Value, Transform(held.Value).Coordinates);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -155,17 +152,16 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
Hand? hand;
|
||||
if (handName == null || !handsComp.Hands.TryGetValue(handName, out hand))
|
||||
hand = handsComp.ActiveHand;
|
||||
var hand = handName;
|
||||
if (!TryGetHand(uid, hand, out _))
|
||||
hand = handsComp.ActiveHandId;
|
||||
|
||||
if (hand?.HeldEntity is not { } held)
|
||||
if (!TryGetHeldItem((uid, handsComp), hand, out var held))
|
||||
return false;
|
||||
|
||||
if (altInteract)
|
||||
return _interactionSystem.AltInteract(uid, held);
|
||||
else
|
||||
return _interactionSystem.UseInHandInteraction(uid, held);
|
||||
return _interactionSystem.AltInteract(uid, held.Value);
|
||||
return _interactionSystem.UseInHandInteraction(uid, held.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,22 +172,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHand == null || !handsComp.ActiveHand.IsEmpty)
|
||||
if (handsComp.ActiveHandId == null || !HandIsEmpty((uid, handsComp), handsComp.ActiveHandId))
|
||||
return false;
|
||||
|
||||
if (!handsComp.Hands.TryGetValue(handName, out var hand))
|
||||
if (!TryGetHeldItem((uid, handsComp), handName, out var entity))
|
||||
return false;
|
||||
|
||||
if (!CanDropHeld(uid, hand, checkActionBlocker))
|
||||
if (!CanDropHeld(uid, handName, checkActionBlocker))
|
||||
return false;
|
||||
|
||||
var entity = hand.HeldEntity!.Value;
|
||||
|
||||
if (!CanPickupToHand(uid, entity, handsComp.ActiveHand, checkActionBlocker, handsComp))
|
||||
if (!CanPickupToHand(uid, entity.Value, handsComp.ActiveHandId, checkActionBlocker, handsComp))
|
||||
return false;
|
||||
|
||||
DoDrop(uid, hand, false, handsComp, log:false);
|
||||
DoPickup(uid, handsComp.ActiveHand, entity, handsComp, log: false);
|
||||
DoDrop(uid, handName, false, log: false);
|
||||
DoPickup(uid, handsComp.ActiveHandId, entity.Value, handsComp, log: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -200,19 +194,19 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (component.ActiveHandEntity.HasValue)
|
||||
if (TryGetActiveItem((uid, component), out var activeHeldItem))
|
||||
{
|
||||
// allow for the item to return a different entity, e.g. virtual items
|
||||
RaiseLocalEvent(component.ActiveHandEntity.Value, ref args);
|
||||
RaiseLocalEvent(activeHeldItem.Value, ref args);
|
||||
}
|
||||
|
||||
args.Used ??= component.ActiveHandEntity;
|
||||
args.Used ??= activeHeldItem;
|
||||
}
|
||||
|
||||
//TODO: Actually shows all items/clothing/etc.
|
||||
private void HandleExamined(EntityUid examinedUid, HandsComponent handsComp, ExaminedEvent args)
|
||||
{
|
||||
var heldItemNames = EnumerateHeld(examinedUid, handsComp)
|
||||
var heldItemNames = EnumerateHeld((examinedUid, handsComp))
|
||||
.Where(entity => !HasComp<VirtualItemComponent>(entity))
|
||||
.Select(item => FormattedMessage.EscapeText(Identity.Name(item, EntityManager)))
|
||||
.Select(itemName => Loc.GetString("comp-hands-examine-wrapper", ("item", itemName)))
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using Content.Shared.Clothing.Components;
|
||||
using System.Diagnostics;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
public abstract partial class SharedHandsSystem : EntitySystem
|
||||
public abstract partial class SharedHandsSystem
|
||||
{
|
||||
private void InitializePickup()
|
||||
{
|
||||
@@ -23,11 +23,11 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
var didEquip = new DidEquipHandEvent(uid, args.Entity, hand);
|
||||
RaiseLocalEvent(uid, didEquip, false);
|
||||
var didEquip = new DidEquipHandEvent(uid, args.Entity, hand.Value);
|
||||
RaiseLocalEvent(uid, didEquip);
|
||||
|
||||
var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand);
|
||||
RaiseLocalEvent(args.Entity, gotEquipped, false);
|
||||
var gotEquipped = new GotEquippedHandEvent(uid, args.Entity, hand.Value);
|
||||
RaiseLocalEvent(args.Entity, gotEquipped);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,32 +35,6 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
/// </summary>
|
||||
public const float MaxAnimationRange = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to pick up an entity to a specific hand. If no explicit hand is specified, defaults to using the currently active hand.
|
||||
/// </summary>
|
||||
public bool TryPickup(
|
||||
EntityUid uid,
|
||||
EntityUid entity,
|
||||
string? handName = null,
|
||||
bool checkActionBlocker = true,
|
||||
bool animateUser = false,
|
||||
bool animate = true,
|
||||
HandsComponent? handsComp = null,
|
||||
ItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
var hand = handsComp.ActiveHand;
|
||||
if (handName != null && !handsComp.Hands.TryGetValue(handName, out hand))
|
||||
return false;
|
||||
|
||||
if (hand == null)
|
||||
return false;
|
||||
|
||||
return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to pick up an item into any empty hand. Prioritizes the currently active hand.
|
||||
/// </summary>
|
||||
@@ -80,17 +54,21 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (!TryGetEmptyHand(uid, out var hand, handsComp))
|
||||
if (!TryGetEmptyHand((uid, handsComp), out var hand))
|
||||
return false;
|
||||
|
||||
return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
|
||||
return TryPickup(uid, entity, hand, checkActionBlocker, animateUser, animate, handsComp, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to pick up an entity to a specific hand. If no explicit hand is specified, defaults to using the currently active hand.
|
||||
/// </summary>
|
||||
public bool TryPickup(
|
||||
EntityUid uid,
|
||||
EntityUid entity,
|
||||
Hand hand,
|
||||
string? handId = null,
|
||||
bool checkActionBlocker = true,
|
||||
bool animateUser = false,
|
||||
bool animate = true,
|
||||
HandsComponent? handsComp = null,
|
||||
ItemComponent? item = null)
|
||||
@@ -98,10 +76,15 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
handId ??= handsComp.ActiveHandId;
|
||||
|
||||
if (handId == null)
|
||||
return false;
|
||||
|
||||
if (!Resolve(entity, ref item, false))
|
||||
return false;
|
||||
|
||||
if (!CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item))
|
||||
if (!CanPickupToHand(uid, entity, handId, checkActionBlocker, handsComp, item))
|
||||
return false;
|
||||
|
||||
if (animate)
|
||||
@@ -119,7 +102,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
_storage.PlayPickupAnimation(entity, initialPosition, xform.Coordinates, itemXform.LocalRotation, uid);
|
||||
}
|
||||
}
|
||||
DoPickup(uid, hand, entity, handsComp);
|
||||
DoPickup(uid, handId, entity, handsComp);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -129,20 +112,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
/// By default it does check if it's possible to drop items.
|
||||
/// </summary>
|
||||
public bool TryForcePickup(
|
||||
EntityUid uid,
|
||||
Entity<HandsComponent?> ent,
|
||||
EntityUid entity,
|
||||
Hand hand,
|
||||
string hand,
|
||||
bool checkActionBlocker = true,
|
||||
bool animate = true,
|
||||
HandsComponent? handsComp = null,
|
||||
ItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp);
|
||||
TryDrop(ent, hand, checkActionBlocker: checkActionBlocker);
|
||||
|
||||
return TryPickup(uid, entity, hand, checkActionBlocker, animate, handsComp, item);
|
||||
return TryPickup(ent, entity, hand, checkActionBlocker, animate: animate, handsComp: handsComp, item: item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -157,9 +140,9 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (TryPickupAnyHand(uid, entity, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
|
||||
return true;
|
||||
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
foreach (var hand in handsComp.Hands.Keys)
|
||||
{
|
||||
if (TryDrop(uid, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp) &&
|
||||
if (TryDrop((uid, handsComp), hand, checkActionBlocker: checkActionBlocker) &&
|
||||
TryPickup(uid, entity, hand, checkActionBlocker: checkActionBlocker, handsComp: handsComp))
|
||||
{
|
||||
return true;
|
||||
@@ -173,7 +156,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
if (!TryGetEmptyHand(uid, out var hand, handsComp))
|
||||
if (!TryGetEmptyHand((uid, handsComp), out var hand))
|
||||
return false;
|
||||
|
||||
return CanPickupToHand(uid, entity, hand, checkActionBlocker, handsComp, item);
|
||||
@@ -182,13 +165,15 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Checks whether a given item will fit into a specific user's hand. Unless otherwise specified, this will also check the general CanPickup action blocker.
|
||||
/// </summary>
|
||||
public bool CanPickupToHand(EntityUid uid, EntityUid entity, Hand hand, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
|
||||
public bool CanPickupToHand(EntityUid uid, EntityUid entity, string handId, bool checkActionBlocker = true, HandsComponent? handsComp = null, ItemComponent? item = null)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return false;
|
||||
|
||||
var handContainer = hand.Container;
|
||||
if (handContainer == null || handContainer.ContainedEntity != null)
|
||||
if (!ContainerSystem.TryGetContainer(uid, handId, out var handContainer))
|
||||
return false;
|
||||
|
||||
if (handContainer.ContainedEntities.FirstOrNull() != null)
|
||||
return false;
|
||||
|
||||
if (!Resolve(entity, ref item, false))
|
||||
@@ -231,8 +216,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
{
|
||||
if (uid == null
|
||||
|| !Resolve(uid.Value, ref handsComp, false)
|
||||
|| !TryGetEmptyHand(uid.Value, out var hand, handsComp)
|
||||
|| !TryPickup(uid.Value, entity, hand, checkActionBlocker, animate, handsComp, item))
|
||||
|| !TryPickupAnyHand(uid.Value, entity, checkActionBlocker, animateUser, animate, handsComp, item))
|
||||
{
|
||||
// TODO make this check upwards for any container, and parent to that.
|
||||
// Currently this just checks the direct parent, so items can still teleport through containers.
|
||||
@@ -248,18 +232,20 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Puts an entity into the player's hand, assumes that the insertion is allowed. In general, you should not be calling this function directly.
|
||||
/// </summary>
|
||||
public virtual void DoPickup(EntityUid uid, Hand hand, EntityUid entity, HandsComponent? hands = null, bool log = true)
|
||||
public virtual void DoPickup(EntityUid uid, string hand, EntityUid entity, HandsComponent? hands = null, bool log = true)
|
||||
{
|
||||
if (!Resolve(uid, ref hands))
|
||||
return;
|
||||
|
||||
var handContainer = hand.Container;
|
||||
if (handContainer == null || handContainer.ContainedEntity != null)
|
||||
if (!ContainerSystem.TryGetContainer(uid, hand, out var handContainer))
|
||||
return;
|
||||
|
||||
if (handContainer.ContainedEntities.FirstOrNull() != null)
|
||||
return;
|
||||
|
||||
if (!ContainerSystem.Insert(entity, handContainer))
|
||||
{
|
||||
Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand.Name}.");
|
||||
Log.Error($"Failed to insert {ToPrettyString(entity)} into users hand container when picking up. User: {ToPrettyString(uid)}. Hand: {hand}.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,7 +256,7 @@ public abstract partial class SharedHandsSystem : EntitySystem
|
||||
|
||||
Dirty(uid, hands);
|
||||
|
||||
if (hand == hands.ActiveHand)
|
||||
RaiseLocalEvent(entity, new HandSelectedEvent(uid), false);
|
||||
if (hand == hands.ActiveHandId)
|
||||
RaiseLocalEvent(entity, new HandSelectedEvent(uid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public abstract partial class SharedHandsSystem
|
||||
{
|
||||
var ev = new HeldRelayedEvent<T>(args);
|
||||
|
||||
foreach (var held in EnumerateHeld(entity, entity.Comp))
|
||||
foreach (var held in EnumerateHeld(entity.AsNullable()))
|
||||
{
|
||||
RaiseLocalEvent(held, ref ev);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
@@ -23,6 +24,8 @@ public abstract partial class SharedHandsSystem
|
||||
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
|
||||
[Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!;
|
||||
|
||||
public event Action<Entity<HandsComponent>, string, HandLocation>? OnPlayerAddHand;
|
||||
public event Action<Entity<HandsComponent>, string>? OnPlayerRemoveHand;
|
||||
protected event Action<Entity<HandsComponent>?>? OnHandSetActive;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -33,6 +36,9 @@ public abstract partial class SharedHandsSystem
|
||||
InitializeDrop();
|
||||
InitializePickup();
|
||||
InitializeRelay();
|
||||
|
||||
SubscribeLocalEvent<HandsComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<HandsComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -41,71 +47,94 @@ public abstract partial class SharedHandsSystem
|
||||
CommandBinds.Unregister<SharedHandsSystem>();
|
||||
}
|
||||
|
||||
public virtual void AddHand(EntityUid uid, string handName, HandLocation handLocation, HandsComponent? handsComp = null)
|
||||
private void OnInit(Entity<HandsComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
return;
|
||||
|
||||
if (handsComp.Hands.ContainsKey(handName))
|
||||
return;
|
||||
|
||||
var container = ContainerSystem.EnsureContainer<ContainerSlot>(uid, handName);
|
||||
container.OccludesLight = false;
|
||||
|
||||
var newHand = new Hand(handName, handLocation, container);
|
||||
handsComp.Hands.Add(handName, newHand);
|
||||
handsComp.SortedHands.Add(handName);
|
||||
|
||||
if (handsComp.ActiveHand == null)
|
||||
SetActiveHand(uid, newHand, handsComp);
|
||||
|
||||
RaiseLocalEvent(uid, new HandCountChangedEvent(uid));
|
||||
Dirty(uid, handsComp);
|
||||
var container = EnsureComp<ContainerManagerComponent>(ent);
|
||||
foreach (var id in ent.Comp.Hands.Keys)
|
||||
{
|
||||
ContainerSystem.EnsureContainer<ContainerSlot>(ent, id, container);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void RemoveHand(EntityUid uid, string handName, HandsComponent? handsComp = null)
|
||||
private void OnMapInit(Entity<HandsComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
if (ent.Comp.ActiveHandId == null)
|
||||
SetActiveHand(ent.AsNullable(), ent.Comp.SortedHands.FirstOrDefault());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a hand with the given container id and supplied location to the specified entity.
|
||||
/// </summary>
|
||||
public void AddHand(Entity<HandsComponent?> ent, string handName, HandLocation handLocation)
|
||||
{
|
||||
AddHand(ent, handName, new Hand(handLocation));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a hand with the given container id and supplied hand definition to the given entity.
|
||||
/// </summary>
|
||||
public void AddHand(Entity<HandsComponent?> ent, string handName, Hand hand)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
if (!handsComp.Hands.Remove(handName, out var hand))
|
||||
if (ent.Comp.Hands.ContainsKey(handName))
|
||||
return;
|
||||
|
||||
handsComp.SortedHands.Remove(hand.Name);
|
||||
TryDrop(uid, hand, null, false, true, handsComp);
|
||||
if (hand.Container != null)
|
||||
ContainerSystem.ShutdownContainer(hand.Container);
|
||||
var container = ContainerSystem.EnsureContainer<ContainerSlot>(ent, handName);
|
||||
container.OccludesLight = false;
|
||||
|
||||
if (handsComp.ActiveHand == hand)
|
||||
TrySetActiveHand(uid, handsComp.SortedHands.FirstOrDefault(), handsComp);
|
||||
ent.Comp.Hands.Add(handName, hand);
|
||||
ent.Comp.SortedHands.Add(handName);
|
||||
Dirty(ent);
|
||||
|
||||
RaiseLocalEvent(uid, new HandCountChangedEvent(uid));
|
||||
Dirty(uid, handsComp);
|
||||
OnPlayerAddHand?.Invoke((ent, ent.Comp), handName, hand.Location);
|
||||
|
||||
if (ent.Comp.ActiveHandId == null)
|
||||
SetActiveHand(ent, handName);
|
||||
|
||||
RaiseLocalEvent(ent, new HandCountChangedEvent(ent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified hand from the specified entity
|
||||
/// </summary>
|
||||
public virtual void RemoveHand(Entity<HandsComponent?> ent, string handName)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
OnPlayerRemoveHand?.Invoke((ent, ent.Comp), handName);
|
||||
|
||||
TryDrop(ent, handName, null, false);
|
||||
|
||||
if (!ent.Comp.Hands.Remove(handName))
|
||||
return;
|
||||
|
||||
if (ContainerSystem.TryGetContainer(ent, handName, out var container))
|
||||
ContainerSystem.ShutdownContainer(container);
|
||||
|
||||
ent.Comp.SortedHands.Remove(handName);
|
||||
if (ent.Comp.ActiveHandId == handName)
|
||||
TrySetActiveHand(ent, ent.Comp.SortedHands.FirstOrDefault());
|
||||
|
||||
RaiseLocalEvent(ent, new HandCountChangedEvent(ent));
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets rid of all the entity's hands.
|
||||
/// </summary>
|
||||
/// <param name="uid"></param>
|
||||
/// <param name="handsComp"></param>
|
||||
public void RemoveHands(EntityUid uid, HandsComponent? handsComp = null)
|
||||
public void RemoveHands(Entity<HandsComponent?> ent)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return;
|
||||
|
||||
RemoveHands(uid, EnumerateHands(uid), handsComp);
|
||||
var handIds = new List<string>(ent.Comp.Hands.Keys);
|
||||
foreach (var handId in handIds)
|
||||
{
|
||||
RemoveHand(ent, handId);
|
||||
}
|
||||
|
||||
private void RemoveHands(EntityUid uid, IEnumerable<Hand> hands, HandsComponent handsComp)
|
||||
{
|
||||
if (!hands.Any())
|
||||
return;
|
||||
|
||||
var hand = hands.First();
|
||||
RemoveHand(uid, hand.Name, handsComp);
|
||||
|
||||
// Repeats it for any additional hands.
|
||||
RemoveHands(uid, hands, handsComp);
|
||||
}
|
||||
|
||||
private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
|
||||
@@ -119,15 +148,15 @@ public abstract partial class SharedHandsSystem
|
||||
/// <summary>
|
||||
/// Get any empty hand. Prioritizes the currently active hand.
|
||||
/// </summary>
|
||||
public bool TryGetEmptyHand(EntityUid uid, [NotNullWhen(true)] out Hand? emptyHand, HandsComponent? handComp = null)
|
||||
public bool TryGetEmptyHand(Entity<HandsComponent?> ent, [NotNullWhen(true)] out string? emptyHand)
|
||||
{
|
||||
emptyHand = null;
|
||||
if (!Resolve(uid, ref handComp, false))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
foreach (var hand in EnumerateHands(uid, handComp))
|
||||
foreach (var hand in EnumerateHands(ent))
|
||||
{
|
||||
if (hand.IsEmpty)
|
||||
if (HandIsEmpty(ent, hand))
|
||||
{
|
||||
emptyHand = hand;
|
||||
return true;
|
||||
@@ -137,28 +166,20 @@ public abstract partial class SharedHandsSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetActiveHand(Entity<HandsComponent?> entity, [NotNullWhen(true)] out Hand? hand)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
{
|
||||
hand = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
hand = entity.Comp.ActiveHand;
|
||||
return hand != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the item held in the entity's active hand.
|
||||
/// </summary>
|
||||
public bool TryGetActiveItem(Entity<HandsComponent?> entity, [NotNullWhen(true)] out EntityUid? item)
|
||||
{
|
||||
if (!TryGetActiveHand(entity, out var hand))
|
||||
{
|
||||
item = null;
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return false;
|
||||
}
|
||||
|
||||
item = hand.HeldEntity;
|
||||
return item != null;
|
||||
if (!TryGetHeldItem(entity, entity.Comp.ActiveHandId, out var held))
|
||||
return false;
|
||||
|
||||
item = held;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -174,55 +195,73 @@ public abstract partial class SharedHandsSystem
|
||||
return item.Value;
|
||||
}
|
||||
|
||||
public Hand? GetActiveHand(Entity<HandsComponent?> entity)
|
||||
/// <summary>
|
||||
/// Gets the current active hand's Id for the specified entity
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public string? GetActiveHand(Entity<HandsComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return null;
|
||||
|
||||
return entity.Comp.ActiveHand;
|
||||
return entity.Comp.ActiveHandId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current active hand's held entity for the specified entity
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
/// <returns></returns>
|
||||
public EntityUid? GetActiveItem(Entity<HandsComponent?> entity)
|
||||
{
|
||||
return GetActiveHand(entity)?.HeldEntity;
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
return null;
|
||||
|
||||
return GetHeldItem(entity, entity.Comp.ActiveHandId);
|
||||
}
|
||||
|
||||
public bool ActiveHandIsEmpty(Entity<HandsComponent?> entity)
|
||||
{
|
||||
return GetActiveItem(entity) == null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate over hands, starting with the currently active hand.
|
||||
/// </summary>
|
||||
public IEnumerable<Hand> EnumerateHands(EntityUid uid, HandsComponent? handsComp = null)
|
||||
public IEnumerable<string> EnumerateHands(Entity<HandsComponent?> ent)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
yield break;
|
||||
|
||||
if (handsComp.ActiveHand != null)
|
||||
yield return handsComp.ActiveHand;
|
||||
if (ent.Comp.ActiveHandId != null)
|
||||
yield return ent.Comp.ActiveHandId;
|
||||
|
||||
foreach (var name in handsComp.SortedHands)
|
||||
foreach (var name in ent.Comp.SortedHands)
|
||||
{
|
||||
if (name != handsComp.ActiveHand?.Name)
|
||||
yield return handsComp.Hands[name];
|
||||
if (name != ent.Comp.ActiveHandId)
|
||||
yield return name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate over held items, starting with the item in the currently active hand (if there is one).
|
||||
/// </summary>
|
||||
public IEnumerable<EntityUid> EnumerateHeld(EntityUid uid, HandsComponent? handsComp = null)
|
||||
public IEnumerable<EntityUid> EnumerateHeld(Entity<HandsComponent?> ent)
|
||||
{
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
yield break;
|
||||
|
||||
if (handsComp.ActiveHandEntity != null)
|
||||
yield return handsComp.ActiveHandEntity.Value;
|
||||
if (TryGetActiveItem(ent, out var activeHeld))
|
||||
yield return activeHeld.Value;
|
||||
|
||||
foreach (var name in handsComp.SortedHands)
|
||||
foreach (var name in ent.Comp.SortedHands)
|
||||
{
|
||||
if (name == handsComp.ActiveHand?.Name)
|
||||
if (name == ent.Comp.ActiveHandId)
|
||||
continue;
|
||||
|
||||
if (handsComp.Hands[name].HeldEntity is { } held)
|
||||
yield return held;
|
||||
if (TryGetHeldItem(ent, name, out var held))
|
||||
yield return held.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,18 +270,17 @@ public abstract partial class SharedHandsSystem
|
||||
/// </summary>
|
||||
/// <returns>True if the active hand was set to a NEW value. Setting it to the same value returns false and does
|
||||
/// not trigger interactions.</returns>
|
||||
public virtual bool TrySetActiveHand(EntityUid uid, string? name, HandsComponent? handComp = null)
|
||||
public bool TrySetActiveHand(Entity<HandsComponent?> ent, string? name)
|
||||
{
|
||||
if (!Resolve(uid, ref handComp))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (name == handComp.ActiveHand?.Name)
|
||||
if (name == ent.Comp.ActiveHandId)
|
||||
return false;
|
||||
|
||||
Hand? hand = null;
|
||||
if (name != null && !handComp.Hands.TryGetValue(name, out hand))
|
||||
if (name != null && !ent.Comp.Hands.ContainsKey(name))
|
||||
return false;
|
||||
return SetActiveHand(uid, hand, handComp);
|
||||
return SetActiveHand(ent, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -250,50 +288,50 @@ public abstract partial class SharedHandsSystem
|
||||
/// </summary>
|
||||
/// <returns>True if the active hand was set to a NEW value. Setting it to the same value returns false and does
|
||||
/// not trigger interactions.</returns>
|
||||
public bool SetActiveHand(EntityUid uid, Hand? hand, HandsComponent? handComp = null)
|
||||
public bool SetActiveHand(Entity<HandsComponent?> ent, string? handId)
|
||||
{
|
||||
if (!Resolve(uid, ref handComp))
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return false;
|
||||
|
||||
if (hand == handComp.ActiveHand)
|
||||
if (handId == ent.Comp.ActiveHandId)
|
||||
return false;
|
||||
|
||||
if (handComp.ActiveHand?.HeldEntity is { } held)
|
||||
RaiseLocalEvent(held, new HandDeselectedEvent(uid));
|
||||
if (TryGetHeldItem(ent, handId, out var oldHeld))
|
||||
RaiseLocalEvent(oldHeld.Value, new HandDeselectedEvent(ent));
|
||||
|
||||
if (hand == null)
|
||||
if (handId == null)
|
||||
{
|
||||
handComp.ActiveHand = null;
|
||||
ent.Comp.ActiveHandId = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
handComp.ActiveHand = hand;
|
||||
OnHandSetActive?.Invoke((uid, handComp));
|
||||
ent.Comp.ActiveHandId = handId;
|
||||
OnHandSetActive?.Invoke((ent, ent.Comp));
|
||||
|
||||
if (hand.HeldEntity != null)
|
||||
RaiseLocalEvent(hand.HeldEntity.Value, new HandSelectedEvent(uid));
|
||||
if (TryGetHeldItem(ent, handId, out var newHeld))
|
||||
RaiseLocalEvent(newHeld.Value, new HandSelectedEvent(ent));
|
||||
|
||||
Dirty(uid, handComp);
|
||||
Dirty(ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsHolding(Entity<HandsComponent?> entity, [NotNullWhen(true)] EntityUid? item)
|
||||
{
|
||||
return IsHolding(entity, item, out _, entity);
|
||||
return IsHolding(entity, item, out _);
|
||||
}
|
||||
|
||||
public bool IsHolding(EntityUid uid, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out Hand? inHand, HandsComponent? handsComp = null)
|
||||
public bool IsHolding(Entity<HandsComponent?> ent, [NotNullWhen(true)] EntityUid? entity, [NotNullWhen(true)] out string? inHand)
|
||||
{
|
||||
inHand = null;
|
||||
if (entity == null)
|
||||
return false;
|
||||
|
||||
if (!Resolve(uid, ref handsComp, false))
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
foreach (var hand in handsComp.Hands.Values)
|
||||
foreach (var hand in ent.Comp.Hands.Keys)
|
||||
{
|
||||
if (hand.HeldEntity == entity)
|
||||
if (GetHeldItem(ent, hand) == entity)
|
||||
{
|
||||
inHand = hand;
|
||||
return true;
|
||||
@@ -303,23 +341,89 @@ public abstract partial class SharedHandsSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetHand(EntityUid handsUid, string handId, [NotNullWhen(true)] out Hand? hand,
|
||||
HandsComponent? hands = null)
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the associated hand struct corresponding to a hand ID on a given entity.
|
||||
/// </summary>
|
||||
public bool TryGetHand(Entity<HandsComponent?> ent, [NotNullWhen(true)] string? handId, [NotNullWhen(true)] out Hand? hand)
|
||||
{
|
||||
hand = null;
|
||||
|
||||
if (!Resolve(handsUid, ref hands))
|
||||
if (handId == null)
|
||||
return false;
|
||||
|
||||
return hands.Hands.TryGetValue(handId, out hand);
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
if (!ent.Comp.Hands.TryGetValue(handId, out var handsHand))
|
||||
return false;
|
||||
|
||||
hand = handsHand;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item currently held in the entity's specified hand. Returns null if no hands are present or there is no item.
|
||||
/// </summary>
|
||||
public EntityUid? GetHeldItem(Entity<HandsComponent?> ent, string? handId)
|
||||
{
|
||||
TryGetHeldItem(ent, handId, out var held);
|
||||
return held;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item currently held in the entity's specified hand. Returns false if no hands are present or there is no item.
|
||||
/// </summary>
|
||||
public bool TryGetHeldItem(Entity<HandsComponent?> ent, string? handId, [NotNullWhen(true)] out EntityUid? held)
|
||||
{
|
||||
held = null;
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return false;
|
||||
|
||||
// Sanity check to make sure this is actually a hand.
|
||||
if (handId == null || !ent.Comp.Hands.ContainsKey(handId))
|
||||
return false;
|
||||
|
||||
if (!ContainerSystem.TryGetContainer(ent, handId, out var container))
|
||||
return false;
|
||||
|
||||
held = container.ContainedEntities.FirstOrNull();
|
||||
return held != null;
|
||||
}
|
||||
|
||||
public bool HandIsEmpty(Entity<HandsComponent?> ent, string handId)
|
||||
{
|
||||
return GetHeldItem(ent, handId) == null;
|
||||
}
|
||||
|
||||
public int GetHandCount(Entity<HandsComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return 0;
|
||||
|
||||
return ent.Comp.Hands.Count;
|
||||
}
|
||||
|
||||
public int CountFreeHands(Entity<HandsComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp, false))
|
||||
return 0;
|
||||
|
||||
var free = 0;
|
||||
foreach (var name in ent.Comp.Hands.Keys)
|
||||
{
|
||||
if (HandIsEmpty(ent, name))
|
||||
free++;
|
||||
}
|
||||
|
||||
return free;
|
||||
}
|
||||
|
||||
public int CountFreeableHands(Entity<HandsComponent> hands)
|
||||
{
|
||||
var freeable = 0;
|
||||
foreach (var hand in hands.Comp.Hands.Values)
|
||||
foreach (var name in hands.Comp.Hands.Keys)
|
||||
{
|
||||
if (hand.IsEmpty || CanDropHeld(hands, hand))
|
||||
if (HandIsEmpty(hands.AsNullable(), name) || CanDropHeld(hands, name))
|
||||
freeable++;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Interaction.Events;
|
||||
@@ -58,6 +59,7 @@ namespace Content.Shared.Interaction
|
||||
[Dependency] private readonly ISharedChatManager _chat = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly PullingSystem _pullSystem = default!;
|
||||
[Dependency] private readonly RotateToFaceSystem _rotateToFaceSystem = default!;
|
||||
@@ -343,7 +345,7 @@ namespace Content.Shared.Interaction
|
||||
public bool CombatModeCanHandInteract(EntityUid user, EntityUid? target)
|
||||
{
|
||||
// Always allow attack in these cases
|
||||
if (target == null || !_handsQuery.TryComp(user, out var hands) || hands.ActiveHand?.HeldEntity is not null)
|
||||
if (target == null || !_handsQuery.TryComp(user, out var hands) || _hands.GetActiveItem((user, hands)) is not null)
|
||||
return false;
|
||||
|
||||
// Only eat input if:
|
||||
|
||||
@@ -64,10 +64,10 @@ public sealed class SmartEquipSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// early out if we don't have any hands or a valid inventory slot
|
||||
if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHand == null)
|
||||
if (!TryComp<HandsComponent>(uid, out var hands) || hands.ActiveHandId == null)
|
||||
return;
|
||||
|
||||
var handItem = hands.ActiveHand.HeldEntity;
|
||||
var handItem = _hands.GetActiveItem((uid, hands));
|
||||
|
||||
// can the user interact, and is the item interactable? e.g. virtual items
|
||||
if (!_actionBlocker.CanInteract(uid, handItem))
|
||||
@@ -80,7 +80,7 @@ public sealed class SmartEquipSystem : EntitySystem
|
||||
}
|
||||
|
||||
// early out if we have an item and cant drop it at all
|
||||
if (handItem != null && !_hands.CanDropHeld(uid, hands.ActiveHand))
|
||||
if (hands.ActiveHandId != null && !_hands.CanDropHeld(uid, hands.ActiveHandId))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("smart-equip-cant-drop"), uid, uid);
|
||||
return;
|
||||
@@ -121,7 +121,7 @@ public sealed class SmartEquipSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
_hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
|
||||
_hands.TryDrop((uid, hands), hands.ActiveHandId!);
|
||||
_inventory.TryEquip(uid, handItem.Value, equipmentSlot, predicted: true, checkDoafter:true);
|
||||
return;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ public sealed class SmartEquipSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
_hands.TryDrop(uid, hands.ActiveHand, handsComp: hands);
|
||||
_hands.TryDrop((uid, hands), hands.ActiveHandId!);
|
||||
_storage.Insert(slotItem, handItem.Value, out var stacked, out _);
|
||||
|
||||
// if the hand item stacked with the things in inventory, but there's no more space left for the rest
|
||||
|
||||
@@ -83,7 +83,7 @@ public abstract partial class InventorySystem
|
||||
if (!TryComp(actor, out InventoryComponent? inventory) || !TryComp<HandsComponent>(actor, out var hands))
|
||||
return;
|
||||
|
||||
var held = hands.ActiveHandEntity;
|
||||
var held = _handsSystem.GetActiveItem((actor, hands));
|
||||
TryGetSlotEntity(actor, ev.Slot, out var itemUid, inventory);
|
||||
|
||||
// attempt to perform some interaction
|
||||
@@ -115,7 +115,7 @@ public abstract partial class InventorySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_handsSystem.CanDropHeld(actor, hands.ActiveHand!, checkActionBlocker: false))
|
||||
if (!_handsSystem.CanDropHeld(actor, hands.ActiveHandId!, checkActionBlocker: false))
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(held.Value, new HandDeselectedEvent(actor));
|
||||
|
||||
@@ -16,12 +16,9 @@ public partial class InventorySystem
|
||||
{
|
||||
if (Resolve(user.Owner, ref user.Comp1, false))
|
||||
{
|
||||
foreach (var hand in user.Comp1.Hands.Values)
|
||||
foreach (var held in _handsSystem.EnumerateHeld(user))
|
||||
{
|
||||
if (hand.HeldEntity == null)
|
||||
continue;
|
||||
|
||||
yield return hand.HeldEntity.Value;
|
||||
yield return held;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
@@ -8,7 +7,6 @@ using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.Inventory.VirtualItem;
|
||||
@@ -88,9 +86,9 @@ public abstract class SharedVirtualItemSystem : EntitySystem
|
||||
|
||||
// if the user is holding the real item the virtual item points to,
|
||||
// we allow them to use it in the interaction
|
||||
foreach (var hand in _handsSystem.EnumerateHands(args.User))
|
||||
foreach (var held in _handsSystem.EnumerateHeld(args.User))
|
||||
{
|
||||
if (hand.HeldEntity == ent.Comp.BlockingEntity)
|
||||
if (held == ent.Comp.BlockingEntity)
|
||||
{
|
||||
args.Used = ent.Comp.BlockingEntity;
|
||||
return;
|
||||
@@ -112,7 +110,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TrySpawnVirtualItemInHand(Robust.Shared.GameObjects.EntityUid,Robust.Shared.GameObjects.EntityUid,bool)"/>
|
||||
public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, Hand? empty = null)
|
||||
public bool TrySpawnVirtualItemInHand(EntityUid blockingEnt, EntityUid user, [NotNullWhen(true)] out EntityUid? virtualItem, bool dropOthers = false, string? empty = null)
|
||||
{
|
||||
virtualItem = null;
|
||||
if (empty == null && !_handsSystem.TryGetEmptyHand(user, out empty))
|
||||
@@ -122,7 +120,7 @@ public abstract class SharedVirtualItemSystem : EntitySystem
|
||||
|
||||
foreach (var hand in _handsSystem.EnumerateHands(user))
|
||||
{
|
||||
if (hand.HeldEntity is not { } held)
|
||||
if (!_handsSystem.TryGetHeldItem(user, hand, out var held))
|
||||
continue;
|
||||
|
||||
if (held == blockingEnt)
|
||||
@@ -155,11 +153,11 @@ public abstract class SharedVirtualItemSystem : EntitySystem
|
||||
/// </summary>
|
||||
public void DeleteInHandsMatching(EntityUid user, EntityUid matching)
|
||||
{
|
||||
foreach (var hand in _handsSystem.EnumerateHands(user))
|
||||
foreach (var held in _handsSystem.EnumerateHeld(user))
|
||||
{
|
||||
if (TryComp(hand.HeldEntity, out VirtualItemComponent? virt) && virt.BlockingEntity == matching)
|
||||
if (TryComp(held, out VirtualItemComponent? virt) && virt.BlockingEntity == matching)
|
||||
{
|
||||
DeleteVirtualItem((hand.HeldEntity.Value, virt), user);
|
||||
DeleteVirtualItem((held, virt), user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Popups;
|
||||
@@ -38,7 +37,7 @@ public sealed class MultiHandedItemSystem : EntitySystem
|
||||
|
||||
private void OnAttemptPickup(Entity<MultiHandedItemComponent> ent, ref GettingPickedUpAttemptEvent args)
|
||||
{
|
||||
if (TryComp<HandsComponent>(args.User, out var hands) && hands.CountFreeHands() >= ent.Comp.HandsNeeded)
|
||||
if (_hands.CountFreeHands(ent.Owner) >= ent.Comp.HandsNeeded)
|
||||
return;
|
||||
|
||||
args.Cancel();
|
||||
|
||||
@@ -110,7 +110,7 @@ public abstract class SharedItemSystem : EntitySystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = _handsSystem.TryPickup(args.User, uid, animateUser: false);
|
||||
args.Handled = _handsSystem.TryPickup(args.User, uid, null, animateUser: false);
|
||||
}
|
||||
|
||||
private void AddPickupVerb(EntityUid uid, ItemComponent component, GetVerbsEvent<InteractionVerb> args)
|
||||
|
||||
@@ -434,7 +434,7 @@ public abstract class SharedMagicSystem : EntitySystem
|
||||
return;
|
||||
|
||||
EntityUid? wand = null;
|
||||
foreach (var item in _hands.EnumerateHeld(ev.Performer, handsComp))
|
||||
foreach (var item in _hands.EnumerateHeld((ev.Performer, handsComp)))
|
||||
{
|
||||
if (!_tag.HasTag(item, ev.WandTag))
|
||||
continue;
|
||||
|
||||
@@ -109,16 +109,12 @@ public sealed class PullingSystem : EntitySystem
|
||||
|
||||
// Try find hand that is doing this pull.
|
||||
// and clear it.
|
||||
foreach (var hand in component.Hands.Values)
|
||||
{
|
||||
if (hand.HeldEntity == null
|
||||
|| !TryComp(hand.HeldEntity, out VirtualItemComponent? virtualItem)
|
||||
|| virtualItem.BlockingEntity != args.PulledUid)
|
||||
foreach (var held in _handsSystem.EnumerateHeld((uid, component)))
|
||||
{
|
||||
if (!TryComp(held, out VirtualItemComponent? virtualItem) || virtualItem.BlockingEntity != args.PulledUid)
|
||||
continue;
|
||||
}
|
||||
|
||||
_handsSystem.TryDrop(args.PullerUid, hand, handsComp: component);
|
||||
_handsSystem.TryDrop((args.PullerUid, component), held);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item.ItemToggle;
|
||||
@@ -19,6 +20,7 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly SharedCombatModeSystem _combatMode = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly ItemToggleSystem _toggle = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
@@ -128,8 +130,7 @@ public abstract class SharedNinjaGlovesSystem : EntitySystem
|
||||
target = args.Target;
|
||||
return _timing.IsFirstTimePredicted
|
||||
&& !_combatMode.IsInCombatMode(uid)
|
||||
&& TryComp<HandsComponent>(uid, out var hands)
|
||||
&& hands.ActiveHandEntity == null
|
||||
&& _hands.GetActiveItem(uid) == null
|
||||
&& _interaction.InRangeUnobstructed(uid, target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ public sealed class FoodSystem : EntitySystem
|
||||
|
||||
var usedTypes = UtensilType.None;
|
||||
|
||||
foreach (var item in _hands.EnumerateHeld(user, hands))
|
||||
foreach (var item in _hands.EnumerateHeld((user, hands)))
|
||||
{
|
||||
// Is utensil?
|
||||
if (!TryComp<UtensilComponent>(item, out var utensil))
|
||||
|
||||
@@ -5,7 +5,7 @@ using Content.Shared.Construction;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Physics;
|
||||
@@ -35,6 +35,7 @@ public sealed class RCDSystem : EntitySystem
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
@@ -296,11 +297,10 @@ public sealed class RCDSystem : EntitySystem
|
||||
var uid = GetEntity(ev.NetEntity);
|
||||
|
||||
// Determine if player that send the message is carrying the specified RCD in their active hand
|
||||
if (session.SenderSession.AttachedEntity == null)
|
||||
if (session.SenderSession.AttachedEntity is not { } player)
|
||||
return;
|
||||
|
||||
if (!TryComp<HandsComponent>(session.SenderSession.AttachedEntity, out var hands) ||
|
||||
uid != hands.ActiveHand?.HeldEntity)
|
||||
if (_hands.GetActiveItem(player) != uid)
|
||||
return;
|
||||
|
||||
if (!TryComp<RCDComponent>(uid, out var rcd))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Cuffs;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction.Components;
|
||||
using Content.Shared.Inventory;
|
||||
@@ -42,7 +41,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
|
||||
|
||||
private void OnRetractableItemAction(Entity<RetractableItemActionComponent> ent, ref OnRetractableItemActionEvent args)
|
||||
{
|
||||
if (_hands.GetActiveHand(args.Performer) is not { } userHand)
|
||||
if (_hands.GetActiveHand(args.Performer) is not { } activeHand)
|
||||
return;
|
||||
|
||||
if (_actions.GetAction(ent.Owner) is not { } action)
|
||||
@@ -55,7 +54,9 @@ public sealed class RetractableItemActionSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Don't allow to summon an item if holding an unremoveable item unless that item is summoned by the action.
|
||||
if (userHand.HeldEntity != null && !_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid) && !_hands.CanDropHeld(args.Performer, userHand, false))
|
||||
if (_hands.GetActiveItem(ent.Owner) != null
|
||||
&& !_hands.IsHolding(args.Performer, ent.Comp.ActionItemUid)
|
||||
&& !_hands.CanDropHeld(args.Performer, activeHand, false))
|
||||
{
|
||||
_popups.PopupClient(Loc.GetString("retractable-item-hand-cannot-drop"), args.Performer, args.Performer);
|
||||
return;
|
||||
@@ -67,7 +68,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
SummonRetractableItem(args.Performer, ent.Comp.ActionItemUid.Value, userHand, ent.Owner);
|
||||
SummonRetractableItem(args.Performer, ent.Comp.ActionItemUid.Value, activeHand, ent.Owner);
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
@@ -93,7 +94,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
|
||||
if (action.Comp.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
if (_hands.GetActiveHand(action.Comp.AttachedEntity.Value) is not { } userHand)
|
||||
if (_hands.GetActiveHand(action.Comp.AttachedEntity.Value) is not { })
|
||||
return;
|
||||
|
||||
RetractRetractableItem(action.Comp.AttachedEntity.Value, ent, action.Owner);
|
||||
@@ -128,7 +129,7 @@ public sealed class RetractableItemActionSystem : EntitySystem
|
||||
_audio.PlayPredicted(action.Comp.RetractSounds, holder, holder);
|
||||
}
|
||||
|
||||
private void SummonRetractableItem(EntityUid holder, EntityUid item, Hand hand, Entity<RetractableItemActionComponent?> action)
|
||||
private void SummonRetractableItem(EntityUid holder, EntityUid item, string hand, Entity<RetractableItemActionComponent?> action)
|
||||
{
|
||||
if (!Resolve(action, ref action.Comp, false))
|
||||
return;
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace Content.Shared.Stacks
|
||||
}
|
||||
|
||||
// This is shit code until hands get fixed and give an easy way to enumerate over items, starting with the currently active item.
|
||||
foreach (var held in Hands.EnumerateHeld(user, hands))
|
||||
foreach (var held in Hands.EnumerateHeld((user, hands)))
|
||||
{
|
||||
TryMergeStacks(item, held, out _, donorStack: itemStack);
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ public abstract class SharedStationSpawningSystem : EntitySystem
|
||||
{
|
||||
var inhandEntity = EntityManager.SpawnEntity(prototype, coords);
|
||||
|
||||
if (_handsSystem.TryGetEmptyHand(entity, out var emptyHand, handsComponent))
|
||||
if (_handsSystem.TryGetEmptyHand((entity, handsComponent), out var emptyHand))
|
||||
{
|
||||
_handsSystem.TryPickup(entity, inhandEntity, emptyHand, checkActionBlocker: false, handsComp: handsComponent);
|
||||
}
|
||||
|
||||
@@ -689,7 +689,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// If the user's active hand is empty, try pick up the item.
|
||||
if (player.Comp.ActiveHandEntity == null)
|
||||
if (!_sharedHandsSystem.TryGetActiveItem(player.AsNullable(), out var activeItem))
|
||||
{
|
||||
_adminLog.Add(
|
||||
LogType.Storage,
|
||||
@@ -709,11 +709,11 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
_adminLog.Add(
|
||||
LogType.Storage,
|
||||
LogImpact.Low,
|
||||
$"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(player.Comp.ActiveHandEntity):used}");
|
||||
$"{ToPrettyString(player):player} is interacting with {ToPrettyString(item):item} while it is stored in {ToPrettyString(storage):storage} using {ToPrettyString(activeItem):used}");
|
||||
|
||||
// Else, interact using the held item
|
||||
if (_interactionSystem.InteractUsing(player,
|
||||
player.Comp.ActiveHandEntity.Value,
|
||||
activeItem.Value,
|
||||
item,
|
||||
Transform(item).Coordinates,
|
||||
checkCanInteract: false))
|
||||
@@ -1208,10 +1208,10 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
{
|
||||
if (!Resolve(ent.Owner, ref ent.Comp)
|
||||
|| !Resolve(player.Owner, ref player.Comp)
|
||||
|| player.Comp.ActiveHandEntity == null)
|
||||
|| !_sharedHandsSystem.TryGetActiveItem(player, out var activeItem))
|
||||
return false;
|
||||
|
||||
var toInsert = player.Comp.ActiveHandEntity;
|
||||
var toInsert = activeItem;
|
||||
|
||||
if (!CanInsert(ent, toInsert.Value, out var reason, ent.Comp))
|
||||
{
|
||||
@@ -1219,7 +1219,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_sharedHandsSystem.CanDrop(player, toInsert.Value, player.Comp))
|
||||
if (!_sharedHandsSystem.CanDrop(player, toInsert.Value))
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("comp-storage-cant-drop", ("entity", toInsert.Value)), ent, player);
|
||||
return false;
|
||||
@@ -1933,7 +1933,7 @@ public abstract class SharedStorageSystem : EntitySystem
|
||||
|
||||
if (held)
|
||||
{
|
||||
if (!_sharedHandsSystem.IsHolding(player, itemUid, out _))
|
||||
if (!_sharedHandsSystem.IsHolding(player.AsNullable(), itemUid, out _))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -104,8 +104,8 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
|
||||
var hasEnt = _inventorySystem.TryGetSlotEntity(strippable, args.Slot, out var held, inventory);
|
||||
|
||||
if (userHands.ActiveHandEntity != null && !hasEnt)
|
||||
StartStripInsertInventory((user, userHands), strippable.Owner, userHands.ActiveHandEntity.Value, args.Slot);
|
||||
if (_handsSystem.GetActiveItem((user, userHands)) is { } activeItem && !hasEnt)
|
||||
StartStripInsertInventory((user, userHands), strippable.Owner, activeItem, args.Slot);
|
||||
else if (hasEnt)
|
||||
StartStripRemoveInventory(user, strippable.Owner, held!.Value, args.Slot);
|
||||
}
|
||||
@@ -124,11 +124,10 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (!target.Comp.CanBeStripped)
|
||||
return;
|
||||
|
||||
if (!_handsSystem.TryGetHand(target.Owner, handId, out var handSlot))
|
||||
return;
|
||||
var heldEntity = _handsSystem.GetHeldItem(target.Owner, handId);
|
||||
|
||||
// Is the target a handcuff?
|
||||
if (TryComp<VirtualItemComponent>(handSlot.HeldEntity, out var virtualItem) &&
|
||||
if (TryComp<VirtualItemComponent>(heldEntity, out var virtualItem) &&
|
||||
TryComp<CuffableComponent>(target.Owner, out var cuffable) &&
|
||||
_cuffableSystem.GetAllCuffs(cuffable).Contains(virtualItem.BlockingEntity))
|
||||
{
|
||||
@@ -136,10 +135,10 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.Comp.ActiveHandEntity != null && handSlot.HeldEntity == null)
|
||||
StartStripInsertHand(user, target, user.Comp.ActiveHandEntity.Value, handId, targetStrippable);
|
||||
else if (handSlot.HeldEntity != null)
|
||||
StartStripRemoveHand(user, target, handSlot.HeldEntity.Value, handId, targetStrippable);
|
||||
if (_handsSystem.GetActiveItem(user.AsNullable()) is { } activeItem && heldEntity == null)
|
||||
StartStripInsertHand(user, target, activeItem, handId, targetStrippable);
|
||||
else if (heldEntity != null)
|
||||
StartStripRemoveHand(user, target, heldEntity.Value, handId, targetStrippable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -154,16 +153,10 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (!Resolve(user, ref user.Comp))
|
||||
return false;
|
||||
|
||||
if (user.Comp.ActiveHand == null)
|
||||
if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held)
|
||||
return false;
|
||||
|
||||
if (user.Comp.ActiveHandEntity == null)
|
||||
return false;
|
||||
|
||||
if (user.Comp.ActiveHandEntity != held)
|
||||
return false;
|
||||
|
||||
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
|
||||
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
|
||||
return false;
|
||||
@@ -210,10 +203,14 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
var (time, stealth) = GetStripTimeModifiers(user, target, held, slotDef.StripTime);
|
||||
|
||||
if (!stealth)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert",
|
||||
("user", Identity.Entity(user, EntityManager)),
|
||||
("item", user.Comp.ActiveHandEntity!.Value)),
|
||||
target, target, PopupType.Large);
|
||||
("item", _handsSystem.GetActiveItem((user, user.Comp))!.Value)),
|
||||
target,
|
||||
target,
|
||||
PopupType.Large);
|
||||
}
|
||||
|
||||
var prefix = stealth ? "stealthily " : "";
|
||||
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s {slot} slot");
|
||||
@@ -246,7 +243,7 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (!CanStripInsertInventory(user, target, held, slot))
|
||||
return;
|
||||
|
||||
if (!_handsSystem.TryDrop(user, handsComp: user.Comp))
|
||||
if (!_handsSystem.TryDrop(user))
|
||||
return;
|
||||
|
||||
_inventorySystem.TryEquip(user, target, held, slot, triggerHandContact: true);
|
||||
@@ -305,10 +302,15 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (IsStripHidden(slotDef, user))
|
||||
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-hidden", ("slot", slot)), target, target, PopupType.Large);
|
||||
else
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner",
|
||||
("user", Identity.Entity(user, EntityManager)),
|
||||
("item", item)),
|
||||
target, target, PopupType.Large);
|
||||
target,
|
||||
target,
|
||||
PopupType.Large);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var prefix = stealth ? "stealthily " : "";
|
||||
@@ -368,23 +370,16 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (!target.Comp.CanBeStripped)
|
||||
return false;
|
||||
|
||||
if (user.Comp.ActiveHand == null)
|
||||
if (!_handsSystem.TryGetActiveItem(user, out var activeItem) || activeItem != held)
|
||||
return false;
|
||||
|
||||
if (user.Comp.ActiveHandEntity == null)
|
||||
return false;
|
||||
|
||||
if (user.Comp.ActiveHandEntity != held)
|
||||
return false;
|
||||
|
||||
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHand))
|
||||
if (!_handsSystem.CanDropHeld(user, user.Comp.ActiveHandId!))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp) ||
|
||||
!_handsSystem.CanPickupToHand(target, user.Comp.ActiveHandEntity.Value, handSlot, checkActionBlocker: false, target.Comp))
|
||||
if (!_handsSystem.CanPickupToHand(target, activeItem.Value, handName, checkActionBlocker: false, target.Comp))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-put-message", ("owner", Identity.Entity(target, EntityManager))));
|
||||
return false;
|
||||
@@ -414,10 +409,15 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);
|
||||
|
||||
if (!stealth)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner-insert-hand",
|
||||
("user", Identity.Entity(user, EntityManager)),
|
||||
("item", user.Comp.ActiveHandEntity!.Value)),
|
||||
target, target, PopupType.Large);
|
||||
("item", _handsSystem.GetActiveItem(user)!.Value)),
|
||||
target,
|
||||
target,
|
||||
PopupType.Large);
|
||||
|
||||
}
|
||||
|
||||
var prefix = stealth ? "stealthily " : "";
|
||||
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}place the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
|
||||
@@ -452,7 +452,7 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (!CanStripInsertHand(user, target, held, handName))
|
||||
return;
|
||||
|
||||
_handsSystem.TryDrop(user, checkActionBlocker: false, handsComp: user.Comp);
|
||||
_handsSystem.TryDrop(user, checkActionBlocker: false);
|
||||
_handsSystem.TryPickup(target, held, handName, checkActionBlocker: false, animateUser: stealth, animate: !stealth, handsComp: target.Comp);
|
||||
_adminLogger.Add(LogType.Stripping, LogImpact.Medium, $"{ToPrettyString(user):actor} has placed the item {ToPrettyString(held):item} in {ToPrettyString(target):target}'s hands");
|
||||
|
||||
@@ -474,22 +474,22 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (!target.Comp.CanBeStripped)
|
||||
return false;
|
||||
|
||||
if (!_handsSystem.TryGetHand(target, handName, out var handSlot, target.Comp))
|
||||
if (!_handsSystem.TryGetHand(target, handName, out _))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("strippable-component-item-slot-free-message", ("owner", Identity.Entity(target, EntityManager))));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HasComp<VirtualItemComponent>(handSlot.HeldEntity))
|
||||
if (!_handsSystem.TryGetHeldItem(target, handName, out var heldEntity))
|
||||
return false;
|
||||
|
||||
if (handSlot.HeldEntity == null)
|
||||
if (HasComp<VirtualItemComponent>(heldEntity))
|
||||
return false;
|
||||
|
||||
if (handSlot.HeldEntity != item)
|
||||
if (heldEntity != item)
|
||||
return false;
|
||||
|
||||
if (!_handsSystem.CanDropHeld(target, handSlot, false))
|
||||
if (!_handsSystem.CanDropHeld(target, handName, false))
|
||||
{
|
||||
_popupSystem.PopupCursor(Loc.GetString("strippable-component-cannot-drop-message", ("owner", Identity.Entity(target, EntityManager))));
|
||||
return false;
|
||||
@@ -519,10 +519,13 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
var (time, stealth) = GetStripTimeModifiers(user, target, null, targetStrippable.HandStripDelay);
|
||||
|
||||
if (!stealth)
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("strippable-component-alert-owner",
|
||||
("user", Identity.Entity(user, EntityManager)),
|
||||
("item", item)),
|
||||
target, target);
|
||||
target,
|
||||
target);
|
||||
}
|
||||
|
||||
var prefix = stealth ? "stealthily " : "";
|
||||
_adminLogger.Add(LogType.Stripping, LogImpact.Low, $"{ToPrettyString(user):actor} is trying to {prefix}strip the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");
|
||||
@@ -560,7 +563,7 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
if (!CanStripRemoveHand(user, target, item, handName))
|
||||
return;
|
||||
|
||||
_handsSystem.TryDrop(target, item, checkActionBlocker: false, handsComp: target.Comp);
|
||||
_handsSystem.TryDrop(target, item, checkActionBlocker: false);
|
||||
_handsSystem.PickupOrDrop(user, item, animateUser: stealth, animate: !stealth, handsComp: user.Comp);
|
||||
_adminLogger.Add(LogType.Stripping, LogImpact.High, $"{ToPrettyString(user):actor} has stripped the item {ToPrettyString(item):item} from {ToPrettyString(target):target}'s hands");
|
||||
|
||||
@@ -580,15 +583,19 @@ public abstract class SharedStrippableSystem : EntitySystem
|
||||
{
|
||||
if ( ev.Event.InsertOrRemove && !CanStripInsertInventory((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) ||
|
||||
!ev.Event.InsertOrRemove && !CanStripRemoveInventory(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName))
|
||||
{
|
||||
ev.Cancel();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( ev.Event.InsertOrRemove && !CanStripInsertHand((entity.Owner, entity.Comp), args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName) ||
|
||||
!ev.Event.InsertOrRemove && !CanStripRemoveHand(entity.Owner, args.Target.Value, args.Used.Value, ev.Event.SlotOrHandName))
|
||||
{
|
||||
ev.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStrippableDoAfterFinished(Entity<HandsComponent> entity, ref StrippableDoAfterEvent ev)
|
||||
{
|
||||
|
||||
@@ -108,10 +108,10 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
|
||||
if (component.InHandsOnly)
|
||||
{
|
||||
if (!_hands.IsHolding(args.User, uid, out var hand, args.Hands))
|
||||
if (!_hands.IsHolding((args.User, args.Hands), uid, out var hand ))
|
||||
return false;
|
||||
|
||||
if (component.RequireActiveHand && args.Hands.ActiveHand != hand)
|
||||
if (component.RequireActiveHand && args.Hands.ActiveHandId != hand)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -202,10 +202,10 @@ public sealed partial class ActivatableUISystem : EntitySystem
|
||||
if (!TryComp(user, out HandsComponent? hands))
|
||||
return false;
|
||||
|
||||
if (!_hands.IsHolding(user, uiEntity, out var hand, hands))
|
||||
if (!_hands.IsHolding((user, hands), uiEntity, out var hand))
|
||||
return false;
|
||||
|
||||
if (aui.RequireActiveHand && hands.ActiveHand != hand)
|
||||
if (aui.RequireActiveHand && hands.ActiveHandId != hand)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
@@ -51,6 +52,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
[Dependency] protected readonly ISharedAdminLogManager AdminLogger = default!;
|
||||
[Dependency] protected readonly ActionBlockerSystem Blocker = default!;
|
||||
[Dependency] protected readonly DamageableSystem Damageable = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly MeleeSoundSystem _meleeSound = default!;
|
||||
[Dependency] protected readonly MobStateSystem MobState = default!;
|
||||
@@ -288,15 +290,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Use inhands entity if we got one.
|
||||
if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) &&
|
||||
hands.ActiveHandEntity is { } held)
|
||||
if (_hands.TryGetActiveItem(entity, out var held))
|
||||
{
|
||||
// Make sure the entity is a weapon AND it doesn't need
|
||||
// to be equipped to be used (E.g boxing gloves).
|
||||
if (EntityManager.TryGetComponent(held, out melee) &&
|
||||
!melee.MustBeEquippedToUse)
|
||||
{
|
||||
weaponUid = held;
|
||||
weaponUid = held.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -858,9 +859,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
|
||||
|
||||
EntityUid? inTargetHand = null;
|
||||
|
||||
if (targetHandsComponent?.ActiveHand is { IsEmpty: false })
|
||||
if (_hands.TryGetActiveItem(target.Value, out var activeHeldEntity))
|
||||
{
|
||||
inTargetHand = targetHandsComponent.ActiveHand.HeldEntity!.Value;
|
||||
inTargetHand = activeHeldEntity.Value;
|
||||
}
|
||||
|
||||
var attemptEvent = new DisarmAttemptEvent(target.Value, user, inTargetHand);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.CombatMode;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.Physics;
|
||||
@@ -25,6 +25,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedJointSystem _joints = default!;
|
||||
[Dependency] private readonly SharedGunSystem _gun = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
@@ -80,9 +81,11 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
|
||||
|
||||
private void OnGrapplingReel(RequestGrapplingReelMessage msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var player = args.SenderSession.AttachedEntity;
|
||||
if (!TryComp<HandsComponent>(player, out var hands) ||
|
||||
!TryComp<GrapplingGunComponent>(hands.ActiveHandEntity, out var grappling))
|
||||
if (args.SenderSession.AttachedEntity is not { } player)
|
||||
return;
|
||||
|
||||
if (!_hands.TryGetActiveItem(player, out var activeItem) ||
|
||||
!TryComp<GrapplingGunComponent>(activeItem, out var grappling))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -94,7 +97,7 @@ public abstract class SharedGrapplingGunSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
SetReeling(hands.ActiveHandEntity.Value, grappling, msg.Reeling, player.Value);
|
||||
SetReeling(activeItem.Value, grappling, msg.Reeling, player);
|
||||
}
|
||||
|
||||
private void OnWeightlessMove(ref CanWeightlessMoveEvent ev)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Events;
|
||||
@@ -23,6 +24,7 @@ public abstract partial class SharedTetherGunSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly MobStateSystem _mob = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
@@ -139,14 +141,14 @@ public abstract partial class SharedTetherGunSystem : EntitySystem
|
||||
gunUid = null;
|
||||
gun = null;
|
||||
|
||||
if (!TryComp<HandsComponent>(user, out var hands) ||
|
||||
!TryComp(hands.ActiveHandEntity, out gun) ||
|
||||
if (!_hands.TryGetActiveItem(user, out var activeItem) ||
|
||||
!TryComp(activeItem, out gun) ||
|
||||
_container.IsEntityInContainer(user))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gunUid = hands.ActiveHandEntity.Value;
|
||||
gunUid = activeItem.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using Content.Shared.Damage;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Tag;
|
||||
@@ -49,6 +49,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
[Dependency] protected readonly ISharedAdminLogManager Logs = default!;
|
||||
[Dependency] protected readonly DamageableSystem Damageable = default!;
|
||||
[Dependency] protected readonly ExamineSystemShared Examine = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _slots = default!;
|
||||
[Dependency] private readonly RechargeBasicEntityAmmoSystem _recharge = default!;
|
||||
[Dependency] protected readonly SharedActionsSystem Actions = default!;
|
||||
@@ -173,8 +174,7 @@ public abstract partial class SharedGunSystem : EntitySystem
|
||||
gunEntity = default;
|
||||
gunComp = null;
|
||||
|
||||
if (EntityManager.TryGetComponent(entity, out HandsComponent? hands) &&
|
||||
hands.ActiveHandEntity is { } held &&
|
||||
if (_hands.GetActiveItem(entity) is { } held &&
|
||||
TryComp(held, out GunComponent? gun))
|
||||
{
|
||||
gunEntity = held;
|
||||
|
||||
@@ -112,7 +112,7 @@ public abstract class SharedWieldableSystem : EntitySystem
|
||||
|
||||
private void OnDeselectWieldable(EntityUid uid, WieldableComponent component, HandDeselectedEvent args)
|
||||
{
|
||||
if (_hands.EnumerateHands(args.User).Count() > 2)
|
||||
if (_hands.GetHandCount(uid) > 2)
|
||||
return;
|
||||
|
||||
TryUnwield(uid, component, args.User);
|
||||
@@ -168,7 +168,7 @@ public abstract class SharedWieldableSystem : EntitySystem
|
||||
if (args.Hands == null || !args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
if (!_hands.IsHolding(args.User, uid, out _, args.Hands))
|
||||
if (!_hands.IsHolding((args.User, args.Hands), uid, out _))
|
||||
return;
|
||||
|
||||
// TODO VERB TOOLTIPS Make CanWield or some other function return string, set as verb tooltip and disable
|
||||
@@ -252,7 +252,7 @@ public abstract class SharedWieldableSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Is it.. actually in one of their hands?
|
||||
if (!_hands.IsHolding(user, uid, out _, hands))
|
||||
if (!_hands.IsHolding((user, hands), uid, out _))
|
||||
{
|
||||
if (!quiet)
|
||||
_popup.PopupClient(Loc.GetString("wieldable-component-not-in-hands", ("item", uid)), user, user);
|
||||
@@ -373,7 +373,7 @@ public abstract class SharedWieldableSystem : EntitySystem
|
||||
/// <param name="force">If this is true we will bypass UnwieldAttemptEvent.</param>
|
||||
public void UnwieldAll(Entity<HandsComponent?> wielder, bool force = false)
|
||||
{
|
||||
foreach (var held in _hands.EnumerateHeld(wielder.Owner, wielder.Comp))
|
||||
foreach (var held in _hands.EnumerateHeld(wielder))
|
||||
{
|
||||
if (TryComp<WieldableComponent>(held, out var wieldable))
|
||||
TryUnwield(held, wieldable, wielder, force);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
- type: body
|
||||
id: Aghost
|
||||
name: "aghost"
|
||||
root: torso
|
||||
slots:
|
||||
torso:
|
||||
part: TorsoHuman
|
||||
connections:
|
||||
- right_arm
|
||||
- left_arm
|
||||
right_arm:
|
||||
part: RightArmHuman
|
||||
connections:
|
||||
- right_hand
|
||||
left_arm:
|
||||
part: LeftArmHuman
|
||||
connections:
|
||||
- left_hand
|
||||
right_hand:
|
||||
part: RightHandHuman
|
||||
left_hand:
|
||||
part: LeftHandHuman
|
||||
@@ -22,6 +22,14 @@
|
||||
canInteract: true
|
||||
- type: GhostHearing
|
||||
- type: Hands
|
||||
hands:
|
||||
hand_right:
|
||||
location: Right
|
||||
hand_left:
|
||||
location: Left
|
||||
sortedHands:
|
||||
- hand_right
|
||||
- hand_left
|
||||
- type: ComplexInteraction
|
||||
- type: Puller
|
||||
needsHands: false
|
||||
@@ -29,8 +37,6 @@
|
||||
- type: Physics
|
||||
ignorePaused: true
|
||||
bodyType: Kinematic
|
||||
- type: Body
|
||||
prototype: Aghost
|
||||
- type: Access
|
||||
groups:
|
||||
- AllAccess
|
||||
|
||||
Reference in New Issue
Block a user