Improve hands & pulling (#4389)

This commit is contained in:
Pieter-Jan Briers
2021-07-31 03:14:00 +02:00
committed by GitHub
parent 73e4946e27
commit 632e72b817
33 changed files with 945 additions and 612 deletions

View File

@@ -0,0 +1,50 @@
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
namespace Content.Shared.Hands.Components
{
[RegisterComponent]
[NetworkedComponent]
public sealed class HandVirtualPullComponent : Component
{
private EntityUid _pulledEntity;
public override string Name => "HandVirtualPull";
public EntityUid PulledEntity
{
get => _pulledEntity;
set
{
_pulledEntity = value;
Dirty();
}
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new VirtualPullComponentState(_pulledEntity);
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if (curState is not VirtualPullComponentState pullState)
return;
_pulledEntity = pullState.PulledEntity;
}
[Serializable, NetSerializable]
public sealed class VirtualPullComponentState : ComponentState
{
public readonly EntityUid PulledEntity;
public VirtualPullComponentState(EntityUid pulledEntity)
{
PulledEntity = pulledEntity;
}
}
}
}

View File

@@ -55,11 +55,11 @@ namespace Content.Shared.Hands.Components
}
}
}
private string? _activeHand;
[ViewVariables]
public IReadOnlyList<IReadOnlyHand> ReadOnlyHands => Hands;
protected readonly List<Hand> Hands = new();
public readonly List<Hand> Hands = new();
/// <summary>
/// The amount of throw impulse per distance the player is from the throw target.
@@ -89,7 +89,10 @@ namespace Content.Shared.Hands.Components
public virtual void HandsModified()
{
// todo axe all this for ECS.
Dirty();
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandsModifiedMessage { Hands = this });
}
public void AddHand(string handName, HandLocation handLocation)
@@ -100,7 +103,7 @@ namespace Content.Shared.Hands.Components
var container = ContainerHelpers.CreateContainer<ContainerSlot>(Owner, handName);
container.OccludesLight = false;
Hands.Add(new Hand(handName, true, handLocation, container));
Hands.Add(new Hand(handName, handLocation, container));
if (ActiveHand == null)
ActiveHand = handName;
@@ -125,48 +128,55 @@ namespace Content.Shared.Hands.Components
Hands.Remove(hand);
if (ActiveHand == hand.Name)
ActiveHand = ReadOnlyHands.FirstOrDefault()?.Name;
ActiveHand = Hands.FirstOrDefault()?.Name;
HandCountChanged();
HandsModified();
}
public bool HasHand(string handName)
{
foreach (var hand in Hands)
{
if (hand.Name == handName)
return true;
}
return false;
}
private Hand? GetHand(string handName)
{
foreach (var hand in Hands)
{
if (hand.Name == handName)
return hand;
}
return null;
}
private Hand? GetActiveHand()
{
if (ActiveHand == null)
return null;
return GetHand(ActiveHand);
return GetHandOrNull(ActiveHand);
}
protected bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand)
public bool HasHand(string handName)
{
foundHand = GetHand(handName);
return foundHand != null;
return TryGetHand(handName, out _);
}
protected bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand)
public Hand? GetHandOrNull(string handName)
{
return TryGetHand(handName, out var hand) ? hand : null;
}
public Hand GetHand(string handName)
{
if (!TryGetHand(handName, out var hand))
throw new KeyNotFoundException($"Unable to find hand with name {handName}");
return hand;
}
public bool TryGetHand(string handName, [NotNullWhen(true)] out Hand? foundHand)
{
foreach (var hand in Hands)
{
if (hand.Name == handName)
{
foundHand = hand;
return true;
};
}
foundHand = null;
return false;
}
public bool TryGetActiveHand([NotNullWhen(true)] out Hand? activeHand)
{
activeHand = GetActiveHand();
return activeHand != null;
@@ -211,7 +221,7 @@ namespace Content.Shared.Hands.Components
public IEnumerable<IEntity> GetAllHeldEntities()
{
foreach (var hand in ReadOnlyHands)
foreach (var hand in Hands)
{
if (hand.HeldEntity != null)
yield return hand.HeldEntity;
@@ -416,7 +426,7 @@ namespace Content.Shared.Hands.Components
/// <summary>
/// Drops a hands contents to the target location.
/// </summary>
private void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true)
public void DropHeldEntity(Hand hand, EntityCoordinates targetDropLocation, bool intentionalDrop = true)
{
var heldEntity = hand.HeldEntity;
@@ -538,16 +548,7 @@ namespace Content.Shared.Hands.Components
public bool CanPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true)
{
if (!TryGetActiveHand(out var hand))
return false;
if (checkActionBlocker && !PlayerCanPickup())
return false;
if (!CanInsertEntityIntoHand(hand, entity))
return false;
return true;
return ActiveHand != null && CanPickupEntity(ActiveHand, entity, checkActionBlocker);
}
/// <summary>
@@ -563,10 +564,7 @@ namespace Content.Shared.Hands.Components
public bool TryPickupEntityToActiveHand(IEntity entity, bool checkActionBlocker = true)
{
if (!TryGetActiveHand(out var hand))
return false;
return TryPickupEntity(hand, entity, checkActionBlocker);
return ActiveHand != null && TryPickupEntity(ActiveHand, entity, checkActionBlocker);
}
/// <summary>
@@ -574,9 +572,6 @@ namespace Content.Shared.Hands.Components
/// </summary>
protected bool CanInsertEntityIntoHand(Hand hand, IEntity entity)
{
if (!hand.Enabled)
return false;
var handContainer = hand.Container;
if (handContainer == null)
return false;
@@ -602,7 +597,7 @@ namespace Content.Shared.Hands.Components
/// <summary>
/// Puts an entity into the player's hand, assumes that the insertion is allowed.
/// </summary>
private void PutEntityIntoHand(Hand hand, IEntity entity)
public void PutEntityIntoHand(Hand hand, IEntity entity)
{
var handContainer = hand.Container;
if (handContainer == null)
@@ -658,7 +653,7 @@ namespace Content.Shared.Hands.Components
if (newActiveIndex > finalHandIndex)
newActiveIndex = 0;
nextHand = ReadOnlyHands[newActiveIndex].Name;
nextHand = Hands[newActiveIndex].Name;
return true;
}
@@ -752,7 +747,7 @@ namespace Content.Shared.Hands.Components
Hand? priorityHand = null;
if (priorityHandName != null)
priorityHand = GetHand(priorityHandName);
priorityHand = GetHandOrNull(priorityHandName);
return TryPutInAnyHand(entity, priorityHand, checkActionBlocker);
}
@@ -793,43 +788,16 @@ namespace Content.Shared.Hands.Components
protected virtual void DoActivate(IEntity heldEntity) { }
protected virtual void HandlePickupAnimation(IEntity entity) { }
protected void EnableHand(Hand hand)
{
hand.Enabled = true;
Dirty();
}
protected void DisableHand(Hand hand)
{
hand.Enabled = false;
DropHeldEntityToFloor(hand, intentionalDrop: false);
Dirty();
}
}
public interface IReadOnlyHand
public class Hand
{
[ViewVariables]
public string Name { get; }
public bool Enabled { get; }
[ViewVariables]
public HandLocation Location { get; }
public abstract IEntity? HeldEntity { get; }
}
public class Hand : IReadOnlyHand
{
[ViewVariables]
public string Name { get; set; }
[ViewVariables]
public bool Enabled { get; set; }
[ViewVariables]
public HandLocation Location { get; set; }
/// <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.
@@ -840,37 +808,36 @@ namespace Content.Shared.Hands.Components
[ViewVariables]
public IEntity? HeldEntity => Container?.ContainedEntities?.FirstOrDefault();
public Hand(string name, bool enabled, HandLocation location, IContainer? container = null)
public bool IsEmpty => HeldEntity == null;
public Hand(string name, HandLocation location, IContainer? container = null)
{
Name = name;
Enabled = enabled;
Location = location;
Container = container;
}
public HandState ToHandState()
{
return new(Name, Location, Enabled);
return new(Name, Location);
}
}
[Serializable, NetSerializable]
public sealed class HandState
public struct HandState
{
public string Name { get; }
public HandLocation Location { get; }
public bool Enabled { get; }
public HandState(string name, HandLocation location, bool enabled)
public HandState(string name, HandLocation location)
{
Name = name;
Location = location;
Enabled = enabled;
}
}
[Serializable, NetSerializable]
public class HandsComponentState : ComponentState
public sealed class HandsComponentState : ComponentState
{
public HandState[] Hands { get; }
public string? ActiveHand { get; }
@@ -886,25 +853,20 @@ namespace Content.Shared.Hands.Components
/// A message that calls the use interaction on an item in hand, presumed for now the interaction will occur only on the active hand.
/// </summary>
[Serializable, NetSerializable]
public class UseInHandMsg : ComponentMessage
public sealed class UseInHandMsg : EntityEventArgs
{
public UseInHandMsg()
{
Directed = true;
}
}
/// <summary>
/// A message that calls the activate interaction on the item in the specified hand.
/// </summary>
[Serializable, NetSerializable]
public class ActivateInHandMsg : ComponentMessage
public class ActivateInHandMsg : EntityEventArgs
{
public string HandName { get; }
public ActivateInHandMsg(string handName)
{
Directed = true;
HandName = handName;
}
}
@@ -913,13 +875,12 @@ namespace Content.Shared.Hands.Components
/// Uses the item in the active hand on the item in the specified hand.
/// </summary>
[Serializable, NetSerializable]
public class ClientAttackByInHandMsg : ComponentMessage
public class ClientInteractUsingInHandMsg : EntityEventArgs
{
public string HandName { get; }
public ClientAttackByInHandMsg(string handName)
public ClientInteractUsingInHandMsg(string handName)
{
Directed = true;
HandName = handName;
}
}
@@ -928,28 +889,12 @@ namespace Content.Shared.Hands.Components
/// Moves an item from one hand to the active hand.
/// </summary>
[Serializable, NetSerializable]
public class MoveItemFromHandMsg : ComponentMessage
public class MoveItemFromHandMsg : EntityEventArgs
{
public string HandName { get; }
public MoveItemFromHandMsg(string handName)
{
Directed = true;
HandName = handName;
}
}
/// <summary>
/// Sets the player's active hand to a specified hand.
/// </summary>
[Serializable, NetSerializable]
public class ClientChangedHandMsg : ComponentMessage
{
public string HandName { get; }
public ClientChangedHandMsg(string handName)
{
Directed = true;
HandName = handName;
}
}
@@ -975,7 +920,7 @@ namespace Content.Shared.Hands.Components
}
[Serializable, NetSerializable]
public class PickupAnimationMessage : ComponentMessage
public class PickupAnimationMessage : EntityEventArgs
{
public EntityUid EntityUid { get; }
public EntityCoordinates InitialPosition { get; }
@@ -983,10 +928,15 @@ namespace Content.Shared.Hands.Components
public PickupAnimationMessage(EntityUid entityUid, Vector2 pickupDirection, EntityCoordinates initialPosition)
{
Directed = true;
EntityUid = entityUid;
PickupDirection = pickupDirection;
InitialPosition = initialPosition;
}
}
[Serializable, NetSerializable]
public struct HandsModifiedMessage
{
public SharedHandsComponent Hands;
}
}

View File

@@ -1,7 +1,6 @@
using Content.Shared.Hands.Components;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using System;
@@ -16,11 +15,7 @@ namespace Content.Shared.Hands
SubscribeLocalEvent<SharedHandsComponent, EntRemovedFromContainerMessage>(HandleContainerModified);
SubscribeLocalEvent<SharedHandsComponent, EntInsertedIntoContainerMessage>(HandleContainerModified);
SubscribeLocalEvent<RequestSetHandEvent>(HandleSetHand);
SubscribeNetworkEvent<RequestSetHandEvent>(HandleSetHand);
SubscribeLocalEvent<RequestDropHeldEntityEvent>(HandleDrop);
SubscribeNetworkEvent<RequestDropHeldEntityEvent>(HandleDrop);
SubscribeAllEvent<RequestSetHandEvent>(HandleSetHand);
}
public void DropHandItems(IEntity entity, bool doMobChecks = true)
@@ -38,14 +33,16 @@ namespace Content.Shared.Hands
eventBus.RaiseLocalEvent(uid, msg);
if (msg.Cancelled) return;
if (msg.Cancelled)
return;
if (entity.TryGetContainerMan(out var containerManager))
{
var parentMsg = new ContainedEntityDropHandItemsAttemptEvent(uid);
eventBus.RaiseLocalEvent(containerManager.Owner.Uid, parentMsg);
if (parentMsg.Cancelled) return;
if (parentMsg.Cancelled)
return;
}
DropAllItemsInHands(entity, doMobChecks);
@@ -55,9 +52,9 @@ namespace Content.Shared.Hands
{
}
private void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
private static void HandleSetHand(RequestSetHandEvent msg, EntitySessionEventArgs eventArgs)
{
var entity = eventArgs.SenderSession?.AttachedEntity;
var entity = eventArgs.SenderSession.AttachedEntity;
if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands))
return;
@@ -65,17 +62,13 @@ namespace Content.Shared.Hands
hands.ActiveHand = msg.HandName;
}
private void HandleDrop(RequestDropHeldEntityEvent msg, EntitySessionEventArgs eventArgs)
protected virtual void HandleContainerModified(
EntityUid uid,
SharedHandsComponent component,
ContainerModifiedMessage args)
{
var entity = eventArgs.SenderSession?.AttachedEntity;
if (entity == null || !entity.TryGetComponent(out SharedHandsComponent? hands))
return;
hands.TryDropHand(msg.HandName, msg.DropTarget);
component.Dirty();
}
protected abstract void HandleContainerModified(EntityUid uid, SharedHandsComponent component, ContainerModifiedMessage args);
}
public sealed class ContainedEntityDropHandItemsAttemptEvent : CancellableEntityEventArgs
@@ -103,21 +96,4 @@ namespace Content.Shared.Hands
HandName = handName;
}
}
[Serializable, NetSerializable]
public class RequestDropHeldEntityEvent : EntityEventArgs
{
/// <summary>
/// The hand to drop from.
/// </summary>
public string HandName { get; }
public EntityCoordinates DropTarget { get; }
public RequestDropHeldEntityEvent(string handName, EntityCoordinates dropTarget)
{
HandName = handName;
DropTarget = dropTarget;
}
}
}

View File

@@ -0,0 +1,53 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Shared.Interaction
{
/// <summary>
/// Raised directed on the used object when clicking on another object before an interaction is handled.
/// </summary>
[PublicAPI]
public class BeforeInteractEvent : HandledEntityEventArgs
{
/// <summary>
/// Entity that triggered the interaction.
/// </summary>
public IEntity User { get; }
/// <summary>
/// Entity that the user used to interact.
/// </summary>
public IEntity Used { get; }
/// <summary>
/// Entity that was interacted on. This can be null if the attack did not click on an entity.
/// </summary>
public IEntity? Target { get; }
/// <summary>
/// Location that the user clicked outside of their interaction range.
/// </summary>
public EntityCoordinates ClickLocation { get; }
/// <summary>
/// Is the click location close enough to reach by the player? This does not check for obstructions, just that the target is within
/// reach radius around the user.
/// </summary>
public bool CanReach { get; }
public BeforeInteractEvent(
IEntity user,
IEntity used,
IEntity? target,
EntityCoordinates clickLocation,
bool canReach)
{
User = user;
Used = used;
Target = target;
ClickLocation = clickLocation;
CanReach = canReach;
}
}
}

View File

@@ -3,7 +3,7 @@ using JetBrains.Annotations;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
namespace Content.Shared.DragDrop
namespace Content.Shared.Interaction
{
/// <summary>
/// This interface gives components behavior when they're dropped by a mob.

View File

@@ -50,6 +50,8 @@ namespace Content.Shared.Pulling.Components
return;
}
var eventBus = Owner.EntityManager.EventBus;
// New value. Abandon being pulled by any existing object.
if (_puller != null)
{
@@ -64,10 +66,9 @@ namespace Content.Shared.Pulling.Components
{
var message = new PullStoppedMessage(oldPullerPhysics, _physics);
oldPuller.SendMessage(null, message);
Owner.SendMessage(null, message);
eventBus.RaiseLocalEvent(oldPuller.Uid, message, broadcast: false);
eventBus.RaiseLocalEvent(Owner.Uid, message);
oldPuller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message);
_physics.WakeBody();
}
// else-branch warning is handled below
@@ -125,14 +126,14 @@ namespace Content.Shared.Pulling.Components
var pullAttempt = new PullAttemptMessage(pullerPhysics, _physics);
value.SendMessage(null, pullAttempt);
eventBus.RaiseLocalEvent(value.Uid, pullAttempt, broadcast: false);
if (pullAttempt.Cancelled)
{
return;
}
Owner.SendMessage(null, pullAttempt);
eventBus.RaiseLocalEvent(Owner.Uid, pullAttempt);
if (pullAttempt.Cancelled)
{
@@ -147,10 +148,8 @@ namespace Content.Shared.Pulling.Components
var message = new PullStartedMessage(PullerPhysics, _physics);
_puller.SendMessage(null, message);
Owner.SendMessage(null, message);
_puller.EntityManager.EventBus.RaiseEvent(EventSource.Local, message);
eventBus.RaiseLocalEvent(_puller.Uid, message, broadcast: false);
eventBus.RaiseLocalEvent(Owner.Uid, message);
var union = PullerPhysics.GetWorldAABB().Union(_physics.GetWorldAABB());
var length = Math.Max(union.Size.X, union.Size.Y) * 0.75f;
@@ -335,29 +334,6 @@ namespace Content.Shared.Pulling.Components
Puller = entity;
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
if (message is not PullMessage pullMessage ||
pullMessage.Pulled.Owner != Owner)
{
return;
}
var pulledStatus = Owner.GetComponentOrNull<SharedAlertsComponent>();
switch (message)
{
case PullStartedMessage:
pulledStatus?.ShowAlert(AlertType.Pulled);
break;
case PullStoppedMessage:
pulledStatus?.ClearAlert(AlertType.Pulled);
break;
}
}
protected override void OnRemove()
{
TryStopPull();

View File

@@ -1,6 +1,4 @@
using Content.Shared.Alert;
using Content.Shared.Movement.Components;
using Content.Shared.Physics.Pull;
using Content.Shared.Movement.Components;
using Robust.Shared.GameObjects;
using Component = Robust.Shared.GameObjects.Component;
@@ -46,30 +44,5 @@ namespace Content.Shared.Pulling.Components
base.OnRemove();
}
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
if (message is not PullMessage pullMessage ||
pullMessage.Puller.Owner != Owner)
{
return;
}
SharedAlertsComponent? ownerStatus = Owner.GetComponentOrNull<SharedAlertsComponent>();
switch (message)
{
case PullStartedMessage msg:
Pulling = msg.Pulled.Owner;
ownerStatus?.ShowAlert(AlertType.Pulling);
break;
case PullStoppedMessage _:
Pulling = null;
ownerStatus?.ClearAlert(AlertType.Pulling);
break;
}
}
}
}

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Physics;
namespace Content.Shared.Physics.Pull
{
public class PullMessage : ComponentMessage
public class PullMessage : EntityEventArgs
{
public readonly IPhysBody Puller;
public readonly IPhysBody Pulled;

View File

@@ -0,0 +1,44 @@
using Content.Shared.Alert;
using Content.Shared.Physics.Pull;
using Content.Shared.Pulling.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Shared.Pulling
{
[UsedImplicitly]
public sealed class SharedPullerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SharedPullerComponent, PullStartedMessage>(PullerHandlePullStarted);
SubscribeLocalEvent<SharedPullerComponent, PullStoppedMessage>(PullerHandlePullStopped);
}
private static void PullerHandlePullStarted(
EntityUid uid,
SharedPullerComponent component,
PullStartedMessage args)
{
if (args.Puller.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ShowAlert(AlertType.Pulling);
}
private static void PullerHandlePullStopped(
EntityUid uid,
SharedPullerComponent component,
PullStoppedMessage args)
{
if (args.Puller.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ClearAlert(AlertType.Pulling);
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Alert;
using Content.Shared.GameTicking;
using Content.Shared.Input;
using Content.Shared.Physics.Pull;
@@ -56,12 +57,34 @@ namespace Content.Shared.Pulling
SubscribeLocalEvent<MoveEvent>(PullerMoved);
SubscribeLocalEvent<EntInsertedIntoContainerMessage>(HandleContainerInsert);
SubscribeLocalEvent<SharedPullableComponent, PullStartedMessage>(PullableHandlePullStarted);
SubscribeLocalEvent<SharedPullableComponent, PullStoppedMessage>(PullableHandlePullStopped);
CommandBinds.Builder
.Bind(ContentKeyFunctions.MovePulledObject, new PointerInputCmdHandler(HandleMovePulledObject))
.Bind(ContentKeyFunctions.ReleasePulledObject, InputCmdHandler.FromDelegate(HandleReleasePulledObject))
.Register<SharedPullingSystem>();
}
// Raise a "you are being pulled" alert if the pulled entity has alerts.
private static void PullableHandlePullStarted(EntityUid uid, SharedPullableComponent component, PullStartedMessage args)
{
if (args.Pulled.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ShowAlert(AlertType.Pulled);
}
private static void PullableHandlePullStopped(EntityUid uid, SharedPullableComponent component, PullStoppedMessage args)
{
if (args.Pulled.Owner.Uid != uid)
return;
if (component.Owner.TryGetComponent(out SharedAlertsComponent? alerts))
alerts.ClearAlert(AlertType.Pulled);
}
public override void Update(float frameTime)
{
base.Update(frameTime);