Re-organize all projects (#4166)
This commit is contained in:
853
Content.Server/Hands/Components/HandsComponent.cs
Normal file
853
Content.Server/Hands/Components/HandsComponent.cs
Normal file
@@ -0,0 +1,853 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Act;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Notification;
|
||||
using Content.Server.Pulling;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Notification;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Content.Shared.Pulling.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Hands.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IHandsComponent))]
|
||||
[ComponentReference(typeof(ISharedHandsComponent))]
|
||||
[ComponentReference(typeof(SharedHandsComponent))]
|
||||
public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved, IDisarmedAct
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
private string? _activeHand;
|
||||
private uint _nextHand;
|
||||
|
||||
public event Action? OnItemChanged;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? ActiveHand
|
||||
{
|
||||
get => _activeHand;
|
||||
set
|
||||
{
|
||||
if (value != null && GetHand(value) == null)
|
||||
{
|
||||
throw new ArgumentException($"No hand '{value}'");
|
||||
}
|
||||
|
||||
_activeHand = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] private readonly List<Hand> _hands = new();
|
||||
|
||||
public IEnumerable<string> Hands => _hands.Select(h => h.Name);
|
||||
|
||||
// Mostly arbitrary.
|
||||
public const float PickupRange = 2;
|
||||
|
||||
[ViewVariables] public int Count => _hands.Count;
|
||||
|
||||
// TODO: This does not serialize what objects are held.
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
ActiveHand = _hands.LastOrDefault()?.Name;
|
||||
}
|
||||
|
||||
public IEnumerable<ItemComponent> GetAllHeldItems()
|
||||
{
|
||||
foreach (var hand in _hands)
|
||||
{
|
||||
if (hand.Entity != null)
|
||||
{
|
||||
yield return hand.Entity.GetComponent<ItemComponent>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsHolding(IEntity entity)
|
||||
{
|
||||
foreach (var hand in _hands)
|
||||
{
|
||||
if (hand.Entity == entity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Hand? GetHand(string? name)
|
||||
{
|
||||
return _hands.FirstOrDefault(hand => hand.Name == name);
|
||||
}
|
||||
|
||||
public ItemComponent? GetItem(string? handName)
|
||||
{
|
||||
return GetHand(handName)?.Entity?.GetComponent<ItemComponent>();
|
||||
}
|
||||
|
||||
public bool TryGetItem(string handName, [NotNullWhen(true)] out ItemComponent? item)
|
||||
{
|
||||
return (item = GetItem(handName)) != null;
|
||||
}
|
||||
|
||||
public ItemComponent? GetActiveHand => ActiveHand == null
|
||||
? null
|
||||
: GetItem(ActiveHand);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates over the enabled hand keys,
|
||||
/// returning the active hand first.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ActivePriorityEnumerable()
|
||||
{
|
||||
if (ActiveHand != null)
|
||||
{
|
||||
yield return ActiveHand;
|
||||
}
|
||||
|
||||
foreach (var hand in _hands)
|
||||
{
|
||||
if (hand.Name == ActiveHand)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hand.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return hand.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PutInHand(ItemComponent item, bool mobCheck = true)
|
||||
{
|
||||
foreach (var hand in ActivePriorityEnumerable())
|
||||
{
|
||||
if (PutInHand(item, hand, false, mobCheck))
|
||||
{
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PutInHand(ItemComponent item, string index, bool fallback = true, bool mobChecks = true)
|
||||
{
|
||||
var hand = GetHand(index);
|
||||
if (!CanPutInHand(item, index, mobChecks) || hand == null)
|
||||
{
|
||||
return fallback && PutInHand(item);
|
||||
}
|
||||
|
||||
Dirty();
|
||||
|
||||
var oldParent = item.Owner.Transform.Parent;
|
||||
var oldPosition = item.Owner.Transform.Coordinates;
|
||||
var contained = item.Owner.IsInContainer();
|
||||
var success = hand.Container.Insert(item.Owner);
|
||||
if (success)
|
||||
{
|
||||
//If the entity isn't in a container, and it isn't located exactly at our position (i.e. in our own storage), then we can safely play the animation
|
||||
if (oldParent != Owner.Transform && !contained)
|
||||
{
|
||||
SendNetworkMessage(new AnimatePickupEntityMessage(item.Owner.Uid, oldPosition));
|
||||
}
|
||||
item.Owner.Transform.LocalPosition = Vector2.Zero;
|
||||
OnItemChanged?.Invoke();
|
||||
}
|
||||
|
||||
_entitySystemManager.GetEntitySystem<InteractionSystem>().EquippedHandInteraction(Owner, item.Owner,
|
||||
ToSharedHand(hand));
|
||||
|
||||
_entitySystemManager.GetEntitySystem<InteractionSystem>().HandSelectedInteraction(Owner, item.Owner);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops the item if <paramref name="mob"/> doesn't have hands.
|
||||
/// </summary>
|
||||
public static void PutInHandOrDropStatic(IEntity mob, ItemComponent item, bool mobCheck = true)
|
||||
{
|
||||
if (!mob.TryGetComponent(out HandsComponent? hands))
|
||||
{
|
||||
DropAtFeet(mob, item);
|
||||
return;
|
||||
}
|
||||
|
||||
hands.PutInHandOrDrop(item, mobCheck);
|
||||
}
|
||||
|
||||
public void PutInHandOrDrop(ItemComponent item, bool mobCheck = true)
|
||||
{
|
||||
if (!PutInHand(item, mobCheck))
|
||||
{
|
||||
DropAtFeet(Owner, item);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DropAtFeet(IEntity mob, ItemComponent item)
|
||||
{
|
||||
item.Owner.Transform.Coordinates = mob.Transform.Coordinates;
|
||||
}
|
||||
|
||||
public bool CanPutInHand(ItemComponent item, bool mobCheck = true)
|
||||
{
|
||||
if (mobCheck && !ActionBlockerSystem.CanPickup(Owner))
|
||||
return false;
|
||||
|
||||
foreach (var handName in ActivePriorityEnumerable())
|
||||
{
|
||||
// We already did a mobCheck, so let's not waste cycles.
|
||||
if (CanPutInHand(item, handName, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanPutInHand(ItemComponent item, string index, bool mobCheck = true)
|
||||
{
|
||||
if (mobCheck && !ActionBlockerSystem.CanPickup(Owner))
|
||||
return false;
|
||||
|
||||
var hand = GetHand(index);
|
||||
|
||||
return hand != null &&
|
||||
hand.Enabled &&
|
||||
hand.Container.CanInsert(item.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the Dropped Interaction with the item.
|
||||
/// </summary>
|
||||
/// <param name="item">The itemcomponent of the item to be dropped</param>
|
||||
/// <param name="doMobChecks">Check if the item can be dropped</param>
|
||||
/// <param name="intentional">If the item was dropped intentionally</param>
|
||||
/// <returns>True if IDropped.Dropped was called, otherwise false</returns>
|
||||
private bool DroppedInteraction(ItemComponent item, bool doMobChecks, bool intentional)
|
||||
{
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
if (doMobChecks)
|
||||
{
|
||||
if (!interactionSystem.TryDroppedInteraction(Owner, item.Owner, intentional))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
interactionSystem.DroppedInteraction(Owner, item.Owner, intentional);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryHand(IEntity entity, [NotNullWhen(true)] out string? handName)
|
||||
{
|
||||
handName = null;
|
||||
|
||||
foreach (var hand in _hands)
|
||||
{
|
||||
if (hand.Entity == entity)
|
||||
{
|
||||
handName = hand.Name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true)
|
||||
{
|
||||
var hand = GetHand(slot);
|
||||
if (!CanDrop(slot, doMobChecks) || !coords.IsValid(Owner.EntityManager) || hand?.Entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = hand.Entity.GetComponent<ItemComponent>();
|
||||
|
||||
if (!hand.Container.Remove(hand.Entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedHandInteraction(Owner, item.Owner,
|
||||
ToSharedHand(hand));
|
||||
|
||||
if (doDropInteraction && !DroppedInteraction(item, false, intentional))
|
||||
return false;
|
||||
|
||||
item.RemovedFromSlot();
|
||||
item.Owner.Transform.Coordinates = coords;
|
||||
|
||||
if (item.Owner.TryGetComponent<SpriteComponent>(out var spriteComponent))
|
||||
{
|
||||
spriteComponent.RenderOrder = item.Owner.EntityManager.CurrentTick.Value;
|
||||
}
|
||||
|
||||
if (Owner.TryGetContainer(out var container))
|
||||
{
|
||||
container.Insert(item.Owner);
|
||||
}
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true)
|
||||
{
|
||||
if (slot == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(slot));
|
||||
}
|
||||
|
||||
if (targetContainer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetContainer));
|
||||
}
|
||||
|
||||
var hand = GetHand(slot);
|
||||
if (!CanDrop(slot, doMobChecks) || hand?.Entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hand.Container.CanRemove(hand.Entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!targetContainer.CanInsert(hand.Entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = hand.Entity.GetComponent<ItemComponent>();
|
||||
|
||||
if (!hand.Container.Remove(hand.Entity))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
_entitySystemManager.GetEntitySystem<InteractionSystem>().UnequippedHandInteraction(Owner, item.Owner,
|
||||
ToSharedHand(hand));
|
||||
|
||||
if (doDropInteraction && !DroppedInteraction(item, doMobChecks, intentional))
|
||||
return false;
|
||||
|
||||
item.RemovedFromSlot();
|
||||
|
||||
if (!targetContainer.Insert(item.Owner))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
Dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
}
|
||||
|
||||
if (!TryHand(entity, out var slot))
|
||||
{
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, coords, doMobChecks, doDropInteraction, intentional);
|
||||
}
|
||||
|
||||
public bool Drop(string slot, bool mobChecks = true, bool doDropInteraction = true, bool intentional = true)
|
||||
{
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks, doDropInteraction, intentional);
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, bool mobChecks = true, bool doDropInteraction = true, bool intentional = true)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
}
|
||||
|
||||
if (!TryHand(entity, out var slot))
|
||||
{
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks, doDropInteraction, intentional);
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
}
|
||||
|
||||
if (!TryHand(entity, out var slot))
|
||||
{
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, targetContainer, doMobChecks, doDropInteraction, intentional);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an item can be dropped from the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="name">The slot to check for.</param>
|
||||
/// <returns>
|
||||
/// True if there is an item in the slot and it can be dropped, false otherwise.
|
||||
/// </returns>
|
||||
public bool CanDrop(string name, bool mobCheck = true)
|
||||
{
|
||||
var hand = GetHand(name);
|
||||
|
||||
if (mobCheck && !ActionBlockerSystem.CanDrop(Owner))
|
||||
return false;
|
||||
|
||||
if (hand?.Entity == null)
|
||||
return false;
|
||||
|
||||
return hand.Container.CanRemove(hand.Entity);
|
||||
}
|
||||
|
||||
public void AddHand(string name)
|
||||
{
|
||||
if (HasHand(name))
|
||||
{
|
||||
throw new InvalidOperationException($"Hand '{name}' already exists.");
|
||||
}
|
||||
|
||||
var container = ContainerHelpers.CreateContainer<ContainerSlot>(Owner, $"hand {_nextHand++}");
|
||||
container.OccludesLight = false;
|
||||
var hand = new Hand(this, name, container);
|
||||
|
||||
_hands.Add(hand);
|
||||
|
||||
ActiveHand ??= name;
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner));
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public void RemoveHand(string name)
|
||||
{
|
||||
var hand = GetHand(name);
|
||||
if (hand == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Hand '{name}' does not exist.");
|
||||
}
|
||||
|
||||
Drop(hand.Name, false);
|
||||
hand!.Dispose();
|
||||
_hands.Remove(hand);
|
||||
|
||||
if (name == ActiveHand)
|
||||
{
|
||||
_activeHand = _hands.FirstOrDefault()?.Name;
|
||||
}
|
||||
|
||||
OnItemChanged?.Invoke();
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner));
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
public bool HasHand(string name)
|
||||
{
|
||||
return _hands.Any(hand => hand.Name == name);
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState(ICommonSession player)
|
||||
{
|
||||
var hands = new SharedHand[_hands.Count];
|
||||
|
||||
for (var i = 0; i < _hands.Count; i++)
|
||||
{
|
||||
var hand = _hands[i].ToShared(i, IndexToHandLocation(i));
|
||||
hands[i] = hand;
|
||||
}
|
||||
|
||||
return new HandsComponentState(hands, ActiveHand);
|
||||
}
|
||||
|
||||
private HandLocation IndexToHandLocation(int index)
|
||||
{
|
||||
return index == 0
|
||||
? HandLocation.Right
|
||||
: index == _hands.Count - 1
|
||||
? HandLocation.Left
|
||||
: HandLocation.Middle;
|
||||
}
|
||||
|
||||
private SharedHand ToSharedHand(Hand hand)
|
||||
{
|
||||
var index = _hands.IndexOf(hand);
|
||||
return hand.ToShared(index, IndexToHandLocation(index));
|
||||
}
|
||||
|
||||
public void SwapHands()
|
||||
{
|
||||
if (ActiveHand == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var hand = GetHand(ActiveHand);
|
||||
if (hand == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No hand found with name {ActiveHand}");
|
||||
}
|
||||
|
||||
var index = _hands.IndexOf(hand);
|
||||
index++;
|
||||
if (index == _hands.Count)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
|
||||
ActiveHand = _hands[index].Name;
|
||||
}
|
||||
|
||||
public void ActivateItem()
|
||||
{
|
||||
var used = GetActiveHand?.Owner;
|
||||
if (used != null)
|
||||
{
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
interactionSystem.TryUseInteraction(Owner, used);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ThrowItem()
|
||||
{
|
||||
var item = GetActiveHand?.Owner;
|
||||
if (item != null)
|
||||
{
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
return interactionSystem.TryThrowInteraction(Owner, item);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if (message is PullMessage pullMessage &&
|
||||
pullMessage.Puller.Owner != Owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case PullAttemptMessage msg:
|
||||
if (!_hands.Any(hand => hand.Enabled))
|
||||
{
|
||||
msg.Cancelled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case PullStartedMessage _:
|
||||
var firstFreeHand = _hands.FirstOrDefault(hand => hand.Enabled);
|
||||
|
||||
if (firstFreeHand == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
firstFreeHand.Enabled = false;
|
||||
|
||||
break;
|
||||
case PullStoppedMessage _:
|
||||
var firstOccupiedHand = _hands.FirstOrDefault(hand => !hand.Enabled);
|
||||
|
||||
if (firstOccupiedHand == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
firstOccupiedHand.Enabled = true;
|
||||
|
||||
break;
|
||||
case HandDisabledMsg msg:
|
||||
Drop(msg.Name, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override async void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
|
||||
{
|
||||
base.HandleNetworkMessage(message, channel, session);
|
||||
|
||||
if (session == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case ClientChangedHandMsg msg:
|
||||
{
|
||||
var playerEntity = session.AttachedEntity;
|
||||
|
||||
if (playerEntity == Owner && HasHand(msg.Index))
|
||||
{
|
||||
ActiveHand = msg.Index;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ClientAttackByInHandMsg msg:
|
||||
{
|
||||
var hand = GetHand(msg.Index);
|
||||
if (hand == null)
|
||||
{
|
||||
Logger.WarningS("go.comp.hands", "Got a ClientAttackByInHandMsg with invalid hand name '{0}'",
|
||||
msg.Index);
|
||||
return;
|
||||
}
|
||||
|
||||
var playerEntity = session.AttachedEntity;
|
||||
var used = GetActiveHand?.Owner;
|
||||
|
||||
if (playerEntity == Owner && hand.Entity != null)
|
||||
{
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
if (used != null)
|
||||
{
|
||||
await interactionSystem.InteractUsing(Owner, used, hand.Entity,
|
||||
EntityCoordinates.Invalid);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entity = hand.Entity;
|
||||
interactionSystem.InteractHand(Owner, entity);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case UseInHandMsg _:
|
||||
{
|
||||
var playerEntity = session.AttachedEntity;
|
||||
var used = GetActiveHand?.Owner;
|
||||
|
||||
if (playerEntity == Owner && used != null)
|
||||
{
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
interactionSystem.TryUseInteraction(Owner, used);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ActivateInHandMsg msg:
|
||||
{
|
||||
var playerEntity = session.AttachedEntity;
|
||||
var used = GetItem(msg.Index)?.Owner;
|
||||
|
||||
if (playerEntity == Owner && used != null)
|
||||
{
|
||||
var interactionSystem = _entitySystemManager.GetEntitySystem<InteractionSystem>();
|
||||
interactionSystem.TryInteractionActivate(Owner, used);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleSlotModifiedMaybe(ContainerModifiedMessage message)
|
||||
{
|
||||
foreach (var hand in _hands)
|
||||
{
|
||||
if (hand.Container != message.Container)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Dirty();
|
||||
|
||||
if (!message.Entity.TryGetComponent(out IPhysBody? physics))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// set velocity to zero
|
||||
physics.LinearVelocity = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs args)
|
||||
{
|
||||
if (args.Part.PartType != BodyPartType.Hand)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddHand(args.Slot);
|
||||
}
|
||||
|
||||
void IBodyPartRemoved.BodyPartRemoved(BodyPartRemovedEventArgs args)
|
||||
{
|
||||
if (args.Part.PartType != BodyPartType.Hand)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveHand(args.Slot);
|
||||
}
|
||||
|
||||
bool IDisarmedAct.Disarmed(DisarmedActEventArgs eventArgs)
|
||||
{
|
||||
if (BreakPulls())
|
||||
return false;
|
||||
|
||||
var source = eventArgs.Source;
|
||||
var target = eventArgs.Target;
|
||||
|
||||
if (source != null)
|
||||
{
|
||||
SoundSystem.Play(Filter.Pvs(source), "/Audio/Effects/thudswoosh.ogg", source,
|
||||
AudioHelpers.WithVariation(0.025f));
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
if (ActiveHand != null && Drop(ActiveHand, false))
|
||||
{
|
||||
source.PopupMessageOtherClients(Loc.GetString("{0} disarms {1}!", source.Name, target.Name));
|
||||
source.PopupMessageCursor(Loc.GetString("You disarm {0}!", target.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
source.PopupMessageOtherClients(Loc.GetString("{0} shoves {1}!", source.Name, target.Name));
|
||||
source.PopupMessageCursor(Loc.GetString("You shove {0}!", target.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// We want this to be the last disarm act to run.
|
||||
int IDisarmedAct.Priority => int.MaxValue;
|
||||
|
||||
private bool BreakPulls()
|
||||
{
|
||||
// What is this API??
|
||||
if (!Owner.TryGetComponent(out SharedPullerComponent? puller)
|
||||
|| puller.Pulling == null || !puller.Pulling.TryGetComponent(out PullableComponent? pullable))
|
||||
return false;
|
||||
|
||||
return pullable.TryStopPull();
|
||||
}
|
||||
}
|
||||
|
||||
public class Hand : IDisposable
|
||||
{
|
||||
private bool _enabled = true;
|
||||
|
||||
public Hand(HandsComponent parent, string name, ContainerSlot container)
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
Container = container;
|
||||
}
|
||||
|
||||
private HandsComponent Parent { get; }
|
||||
public string Name { get; }
|
||||
public IEntity? Entity => Container.ContainedEntity;
|
||||
public ContainerSlot Container { get; }
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enabled = value;
|
||||
Parent.Dirty();
|
||||
|
||||
var message = value
|
||||
? (ComponentMessage) new HandEnabledMsg(Name)
|
||||
: new HandDisabledMsg(Name);
|
||||
|
||||
Parent.HandleMessage(message, Parent);
|
||||
Parent.Owner.SendMessage(Parent, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Container.Shutdown(); // TODO verify this
|
||||
}
|
||||
|
||||
public SharedHand ToShared(int index, HandLocation location)
|
||||
{
|
||||
return new(index, Name, Entity?.Uid, location, Enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public class HandCountChangedEvent : EntityEventArgs
|
||||
{
|
||||
public HandCountChangedEvent(IEntity sender)
|
||||
{
|
||||
Sender = sender;
|
||||
}
|
||||
|
||||
public IEntity Sender { get; }
|
||||
}
|
||||
}
|
||||
236
Content.Server/Hands/Components/IHandsComponent.cs
Normal file
236
Content.Server/Hands/Components/IHandsComponent.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Items;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Hands.Components
|
||||
{
|
||||
public interface IHandsComponent : ISharedHandsComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the hand contents changes or when a hand is added/removed.
|
||||
/// </summary>
|
||||
event Action? OnItemChanged;
|
||||
|
||||
/// <summary>
|
||||
/// The hands in this component.
|
||||
/// </summary>
|
||||
IEnumerable<string> Hands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The hand name of the currently active hand.
|
||||
/// </summary>
|
||||
string? ActiveHand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates over every held item.
|
||||
/// </summary>
|
||||
IEnumerable<ItemComponent> GetAllHeldItems();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item held by a hand.
|
||||
/// </summary>
|
||||
/// <param name="handName">The name of the hand to get.</param>
|
||||
/// <returns>The item in the held, null if no item is held</returns>
|
||||
ItemComponent? GetItem(string handName);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get an item in a hand.
|
||||
/// </summary>
|
||||
/// <param name="handName">The name of the hand to get.</param>
|
||||
/// <param name="item">The item in the held, null if no item is held</param>
|
||||
/// <returns>Whether it was holding an item</returns>
|
||||
bool TryGetItem(string handName, [NotNullWhen(true)] out ItemComponent? item);
|
||||
|
||||
/// <summary>
|
||||
/// Gets item held by the current active hand
|
||||
/// </summary>
|
||||
ItemComponent? GetActiveHand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Puts an item into any empty hand, preferring the active hand.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to put in a hand.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <returns>True if the item was inserted, false otherwise.</returns>
|
||||
bool PutInHand(ItemComponent item, bool mobCheck = true);
|
||||
|
||||
/// <summary>
|
||||
/// Puts an item into a specific hand.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to put in the hand.</param>
|
||||
/// <param name="index">The name of the hand to put the item into.</param>
|
||||
/// <param name="fallback">
|
||||
/// If true and the provided hand is full, the method will fall back to <see cref="PutInHand(ItemComponent)" />
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// </param>
|
||||
/// <returns>True if the item was inserted into a hand, false otherwise.</returns>
|
||||
bool PutInHand(ItemComponent item, string index, bool fallback=true, bool mobCheck = true);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if an item can be put in any hand.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to check for.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <returns>True if the item can be inserted, false otherwise.</returns>
|
||||
bool CanPutInHand(ItemComponent item, bool mobCheck = true);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if an item can be put in the specified hand.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to check for.</param>
|
||||
/// <param name="index">The name for the hand to check for.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <returns>True if the item can be inserted, false otherwise.</returns>
|
||||
bool CanPutInHand(ItemComponent item, string index, bool mobCheck = true);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the hand slot holding the specified entity, if any.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to look for in our hands.</param>
|
||||
/// <param name="handName">
|
||||
/// The name of the hand slot if the entity is indeed held,
|
||||
/// <see langword="null" /> otherwise.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// true if the entity is held, false otherwise
|
||||
/// </returns>
|
||||
bool TryHand(IEntity entity, [NotNullWhen(true)] out string? handName);
|
||||
|
||||
/// <summary>
|
||||
/// Drops the item contained in the slot to the same position as our entity.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot of which to drop to drop the item.</param>
|
||||
/// <param name="mobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
|
||||
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
|
||||
/// <returns>True on success, false if something blocked the drop.</returns>
|
||||
bool Drop(string slot, bool mobChecks = true, bool doDropInteraction = true, bool intentional = true);
|
||||
|
||||
/// <summary>
|
||||
/// Drops an item held by one of our hand slots to the same position as our owning entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The item to drop.</param>
|
||||
/// <param name="mobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
|
||||
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
|
||||
/// <returns>True on success, false if something blocked the drop.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown if <see cref="entity"/> is null.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown if <see cref="entity"/> is not actually held in any hand.
|
||||
/// </exception>
|
||||
bool Drop(IEntity entity, bool mobChecks = true, bool doDropInteraction = true, bool intentional = true);
|
||||
|
||||
/// <summary>
|
||||
/// Drops the item in a slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to drop the item from.</param>
|
||||
/// <param name="coords"></param>
|
||||
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
|
||||
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
|
||||
/// <returns>True if an item was dropped, false otherwise.</returns>
|
||||
bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true);
|
||||
|
||||
/// <summary>
|
||||
/// Drop the specified entity in our hands to a certain position.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are no checks whether or not the user is within interaction range of the drop location
|
||||
/// or whether the drop location is occupied.
|
||||
/// </remarks>
|
||||
/// <param name="entity">The entity to drop, must be held in one of the hands.</param>
|
||||
/// <param name="coords">The coordinates to drop the entity at.</param>
|
||||
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
|
||||
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
|
||||
/// <returns>
|
||||
/// True if the drop succeeded,
|
||||
/// false if it failed (due to failing to eject from our hand slot, etc...)
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown if <see cref="entity"/> is null.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown if <see cref="entity"/> is not actually held in any hand.
|
||||
/// </exception>
|
||||
bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true);
|
||||
|
||||
/// <summary>
|
||||
/// Drop the item contained in a slot into another container.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot of which to drop the entity.</param>
|
||||
/// <param name="targetContainer">The container to drop into.</param>
|
||||
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop"/> for the mob or not.</param>
|
||||
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
|
||||
/// <returns>True on success, false if something was blocked (insertion or removal).</returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown if dry-run checks reported OK to remove and insert,
|
||||
/// but practical remove or insert returned false anyways.
|
||||
/// This is an edge-case that is currently unhandled.
|
||||
/// </exception>
|
||||
bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true);
|
||||
|
||||
/// <summary>
|
||||
/// Drops an item in one of the hands into a container.
|
||||
/// </summary>
|
||||
/// <param name="entity">The item to drop.</param>
|
||||
/// <param name="targetContainer">The container to drop into.</param>
|
||||
/// <param name="doMobChecks">Whether to check the <see cref="ActionBlockerSystem.CanDrop()"/> for the mob or not.</param>
|
||||
/// <param name="doDropInteraction">Whether to perform Dropped interactions.</param>
|
||||
/// <returns>True on success, false if something was blocked (insertion or removal).</returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown if dry-run checks reported OK to remove and insert,
|
||||
/// but practical remove or insert returned false anyways.
|
||||
/// This is an edge-case that is currently unhandled.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// Thrown if <see cref="entity"/> is null.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown if <see cref="entity"/> is not actually held in any hand.
|
||||
/// </exception>
|
||||
bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true, bool intentional = true);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the item in the specified hand can be dropped.
|
||||
/// </summary>
|
||||
/// <param name="name">The hand to check for.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <returns>
|
||||
/// True if the item can be dropped, false if the hand is empty or the item in the hand cannot be dropped.
|
||||
/// </returns>
|
||||
bool CanDrop(string name, bool mobCheck = true);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new hand to this hands component.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the hand to add.</param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown if a hand with specified name already exists.
|
||||
/// </exception>
|
||||
void AddHand(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a hand from this hands component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the hand contains an item, the item is dropped.
|
||||
/// </remarks>
|
||||
/// <param name="name">The name of the hand to remove.</param>
|
||||
void RemoveHand(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a hand with the specified name exists.
|
||||
/// </summary>
|
||||
/// <param name="name">The hand name to check.</param>
|
||||
/// <returns>True if the hand exists, false otherwise.</returns>
|
||||
bool HasHand(string name);
|
||||
|
||||
void HandleSlotModifiedMaybe(ContainerModifiedMessage message);
|
||||
}
|
||||
}
|
||||
270
Content.Server/Hands/HandsSystem.cs
Normal file
270
Content.Server/Hands/HandsSystem.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Inventory.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.Stack;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.Throwing;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Notification;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using static Content.Shared.Inventory.EquipmentSlotDefines;
|
||||
|
||||
namespace Content.Server.Hands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class HandsSystem : EntitySystem
|
||||
{
|
||||
private const float ThrowForce = 1.5f; // Throwing force of mobs in Newtons
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EntRemovedFromContainerMessage>(HandleContainerModified);
|
||||
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerModified);
|
||||
SubscribeLocalEvent<HandsComponent, ExaminedEvent>(HandleExamined);
|
||||
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.SwapHands, InputCmdHandler.FromDelegate(HandleSwapHands))
|
||||
.Bind(ContentKeyFunctions.Drop, new PointerInputCmdHandler(HandleDrop))
|
||||
.Bind(ContentKeyFunctions.ActivateItemInHand, InputCmdHandler.FromDelegate(HandleActivateItem))
|
||||
.Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem))
|
||||
.Bind(ContentKeyFunctions.SmartEquipBackpack, InputCmdHandler.FromDelegate(HandleSmartEquipBackpack))
|
||||
.Bind(ContentKeyFunctions.SmartEquipBelt, InputCmdHandler.FromDelegate(HandleSmartEquipBelt))
|
||||
.Register<HandsSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
CommandBinds.Unregister<HandsSystem>();
|
||||
}
|
||||
|
||||
private static void HandleContainerModified(ContainerModifiedMessage args)
|
||||
{
|
||||
if (args.Container.Owner.TryGetComponent(out IHandsComponent? handsComponent))
|
||||
{
|
||||
handsComponent.HandleSlotModifiedMaybe(args);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Actually shows all items/clothing/etc.
|
||||
private void HandleExamined(EntityUid uid, HandsComponent component, ExaminedEvent args)
|
||||
{
|
||||
foreach (var inhand in component.GetAllHeldItems())
|
||||
{
|
||||
args.Message.AddText($"\n{Loc.GetString("comp-hands-examine", ("user", component.Owner), ("item", inhand.Owner))}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetAttachedComponent<T>(IPlayerSession? session, [NotNullWhen(true)] out T? component)
|
||||
where T : Component
|
||||
{
|
||||
component = default;
|
||||
|
||||
var ent = session?.AttachedEntity;
|
||||
|
||||
if (ent == null || !ent.IsValid() || !ent.TryGetComponent(out T? comp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
component = comp;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void HandleSwapHands(ICommonSession? session)
|
||||
{
|
||||
if (!TryGetAttachedComponent(session as IPlayerSession, out HandsComponent? handsComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var interactionSystem = Get<InteractionSystem>();
|
||||
|
||||
var oldItem = handsComp.GetActiveHand;
|
||||
|
||||
handsComp.SwapHands();
|
||||
|
||||
var newItem = handsComp.GetActiveHand;
|
||||
|
||||
if (oldItem != null)
|
||||
{
|
||||
interactionSystem.HandDeselectedInteraction(handsComp.Owner, oldItem.Owner);
|
||||
}
|
||||
|
||||
if (newItem != null)
|
||||
{
|
||||
interactionSystem.HandSelectedInteraction(handsComp.Owner, newItem.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleDrop(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
var ent = ((IPlayerSession?) session)?.AttachedEntity;
|
||||
|
||||
if (ent == null || !ent.IsValid())
|
||||
return false;
|
||||
|
||||
if (!ent.TryGetComponent(out HandsComponent? handsComp))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHand == null || handsComp.GetActiveHand == null)
|
||||
return false;
|
||||
|
||||
// It's important to note that the calculations are done in map coordinates (they're absolute).
|
||||
// They're translated back to EntityCoordinates at the end.
|
||||
var entMap = ent.Transform.MapPosition;
|
||||
var targetPos = coords.ToMapPos(EntityManager);
|
||||
var dropVector = targetPos - entMap.Position;
|
||||
var targetVector = Vector2.Zero;
|
||||
|
||||
if (dropVector != Vector2.Zero)
|
||||
{
|
||||
var targetLength = MathF.Min(dropVector.Length, SharedInteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error
|
||||
var newCoords = new MapCoordinates(dropVector.Normalized * targetLength + entMap.Position, entMap.MapId);
|
||||
var rayLength = Get<SharedInteractionSystem>().UnobstructedDistance(entMap, newCoords, ignoredEnt: ent);
|
||||
targetVector = dropVector.Normalized * rayLength;
|
||||
}
|
||||
|
||||
var resultMapCoordinates = new MapCoordinates(entMap.Position + targetVector, entMap.MapId);
|
||||
var resultEntCoordinates = EntityCoordinates.FromMap(coords.GetParent(EntityManager), resultMapCoordinates);
|
||||
handsComp.Drop(handsComp.ActiveHand, resultEntCoordinates);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void HandleActivateItem(ICommonSession? session)
|
||||
{
|
||||
if (!TryGetAttachedComponent(session as IPlayerSession, out HandsComponent? handsComp))
|
||||
return;
|
||||
|
||||
handsComp.ActivateItem();
|
||||
}
|
||||
|
||||
private bool HandleThrowItem(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
||||
{
|
||||
var playerEnt = ((IPlayerSession?) session)?.AttachedEntity;
|
||||
|
||||
if (playerEnt == null || !playerEnt.IsValid())
|
||||
return false;
|
||||
|
||||
if (!playerEnt.TryGetComponent(out HandsComponent? handsComp))
|
||||
return false;
|
||||
|
||||
if (handsComp.ActiveHand == null || !handsComp.CanDrop(handsComp.ActiveHand))
|
||||
return false;
|
||||
|
||||
var throwEnt = handsComp.GetItem(handsComp.ActiveHand)?.Owner;
|
||||
|
||||
if (throwEnt == null)
|
||||
return false;
|
||||
|
||||
if (!handsComp.ThrowItem())
|
||||
return false;
|
||||
|
||||
// throw the item, split off from a stack if it's meant to be thrown individually
|
||||
if (!throwEnt.TryGetComponent(out StackComponent? stackComp) || stackComp.Count < 2 || !stackComp.ThrowIndividually)
|
||||
{
|
||||
handsComp.Drop(handsComp.ActiveHand);
|
||||
}
|
||||
else
|
||||
{
|
||||
var splitStack = new StackSplitEvent() { Amount = 1, SpawnPosition = playerEnt.Transform.Coordinates };
|
||||
RaiseLocalEvent(throwEnt.Uid, splitStack);
|
||||
|
||||
if (splitStack.Result == null)
|
||||
return false;
|
||||
|
||||
throwEnt = splitStack.Result;
|
||||
}
|
||||
|
||||
var direction = coords.ToMapPos(EntityManager) - playerEnt.Transform.WorldPosition;
|
||||
if (direction == Vector2.Zero) return true;
|
||||
|
||||
direction = direction.Normalized * MathF.Min(direction.Length, 8.0f);
|
||||
var yeet = direction * ThrowForce * 15;
|
||||
|
||||
// Softer yeet in weightlessness
|
||||
if (playerEnt.IsWeightless())
|
||||
{
|
||||
throwEnt.TryThrow(yeet / 4, playerEnt, 10.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
throwEnt.TryThrow(yeet, playerEnt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleSmartEquipBackpack(ICommonSession? session)
|
||||
{
|
||||
HandleSmartEquip(session, Slots.BACKPACK);
|
||||
}
|
||||
|
||||
private void HandleSmartEquipBelt(ICommonSession? session)
|
||||
{
|
||||
HandleSmartEquip(session, Slots.BELT);
|
||||
}
|
||||
|
||||
private void HandleSmartEquip(ICommonSession? session, Slots equipmentSlot)
|
||||
{
|
||||
var plyEnt = ((IPlayerSession?) session)?.AttachedEntity;
|
||||
|
||||
if (plyEnt == null || !plyEnt.IsValid())
|
||||
return;
|
||||
|
||||
if (!plyEnt.TryGetComponent(out HandsComponent? handsComp) ||
|
||||
!plyEnt.TryGetComponent(out InventoryComponent? inventoryComp))
|
||||
return;
|
||||
|
||||
if (!inventoryComp.TryGetSlotItem(equipmentSlot, out ItemComponent? equipmentItem)
|
||||
|| !equipmentItem.Owner.TryGetComponent<ServerStorageComponent>(out var storageComponent))
|
||||
{
|
||||
plyEnt.PopupMessage(Loc.GetString("You have no {0} to take something out of!",
|
||||
SlotNames[equipmentSlot].ToLower()));
|
||||
return;
|
||||
}
|
||||
|
||||
var heldItem = handsComp.GetItem(handsComp.ActiveHand)?.Owner;
|
||||
|
||||
if (heldItem != null)
|
||||
{
|
||||
storageComponent.PlayerInsertHeldEntity(plyEnt);
|
||||
}
|
||||
else if (storageComponent.StoredEntities != null)
|
||||
{
|
||||
if (storageComponent.StoredEntities.Count == 0)
|
||||
{
|
||||
plyEnt.PopupMessage(Loc.GetString("There's nothing in your {0} to take out!",
|
||||
SlotNames[equipmentSlot].ToLower()));
|
||||
}
|
||||
else
|
||||
{
|
||||
var lastStoredEntity = Enumerable.Last(storageComponent.StoredEntities);
|
||||
if (storageComponent.Remove(lastStoredEntity))
|
||||
handsComp.PutInHandOrDrop(lastStoredEntity.GetComponent<ItemComponent>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user