Add Alt-click functionality (#4497)

* Fix ItemSlot Bug

* Add Alt-use Key

* Fix TransferAmount window bug

* Alt-click functionality

* Added AltInteract verbs

* Add new verbs

* verb icons

* Changed Comments

* Change Comments

* Fix disposal verbs

* Changed Get...() to Get...OrNull()

* Changed alt-interact combat behaviour

* Update verb icons

* Inventory interact event

* Add Alt+E secondary binding

* Add alt-z keybinding

* Rename AltUse -> AltActivateItemInWorld
This commit is contained in:
Leon Friedrich
2021-08-22 03:20:18 +10:00
committed by GitHub
parent ad5f7bb71b
commit 486dc6ca62
51 changed files with 748 additions and 53 deletions

View File

@@ -21,6 +21,7 @@ using Content.Shared.Inventory;
using Content.Shared.Notification.Managers;
using Content.Shared.Rotatable;
using Content.Shared.Throwing;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
@@ -52,10 +53,13 @@ namespace Content.Server.Interaction
public override void Initialize()
{
SubscribeNetworkEvent<DragDropRequestEvent>(HandleDragDropRequestEvent);
SubscribeNetworkEvent<InteractInventorySlotEvent>(HandleInteractInventorySlotEvent);
CommandBinds.Builder
.Bind(EngineKeyFunctions.Use,
new PointerInputCmdHandler(HandleUseInteraction))
.Bind(ContentKeyFunctions.AltActivateItemInWorld,
new PointerInputCmdHandler(HandleAltUseInteraction))
.Bind(ContentKeyFunctions.WideAttack,
new PointerInputCmdHandler(HandleWideAttack))
.Bind(ContentKeyFunctions.ActivateItemInWorld,
@@ -102,6 +106,34 @@ namespace Content.Server.Interaction
}
#endregion
/// <summary>
/// Handles the event were a client uses an item in their inventory or in their hands, either by
/// alt-clicking it or pressing 'E' while hovering over it.
/// </summary>
private void HandleInteractInventorySlotEvent(InteractInventorySlotEvent msg, EntitySessionEventArgs args)
{
if (!EntityManager.TryGetEntity(msg.ItemUid, out var item))
{
Logger.WarningS("system.interaction",
$"Client sent inventory interaction with an invalid target item. Session={args.SenderSession}");
return;
}
// client sanitization
if (!ValidateClientInput(args.SenderSession, item.Transform.Coordinates, msg.ItemUid, out var userEntity))
{
Logger.InfoS("system.interaction", $"Inventory interaction validation failed. Session={args.SenderSession}");
return;
}
if (msg.AltInteract)
// Use 'UserInteraction' function - behaves as if the user alt-clicked the item in the world.
UserInteraction(userEntity, item.Transform.Coordinates, msg.ItemUid, msg.AltInteract);
else
// User used 'E'. We want to activate it, not simulate clicking on the item
InteractionActivate(userEntity, item);
}
#region Drag drop
private void HandleDragDropRequestEvent(DragDropRequestEvent msg, EntitySessionEventArgs args)
{
@@ -241,6 +273,20 @@ namespace Content.Server.Interaction
return true;
}
public bool HandleAltUseInteraction(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
// client sanitization
if (!ValidateClientInput(session, coords, uid, out var userEntity))
{
Logger.InfoS("system.interaction", $"Alt-use input validation failed");
return true;
}
UserInteraction(userEntity, coords, uid, altInteract : true );
return true;
}
private bool HandleTryPullObject(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
{
if (!ValidateClientInput(session, coords, uid, out var userEntity))
@@ -264,12 +310,24 @@ namespace Content.Server.Interaction
return pull.TogglePull(userEntity);
}
public async void UserInteraction(IEntity user, EntityCoordinates coordinates, EntityUid clickedUid)
/// <summary>
/// Resolves user interactions with objects.
/// </summary>
/// <remarks>
/// Checks Whether combat mode is enabled and whether the user can actually interact with the given entity.
/// </remarks>
/// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of
/// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat
/// interaction. Having an item in the active hand also disables alternative interactions.</param>
public async void UserInteraction(IEntity user, EntityCoordinates coordinates, EntityUid clickedUid, bool altInteract = false )
{
if (user.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
// TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms?
if (!altInteract && user.TryGetComponent(out CombatModeComponent? combatMode) && combatMode.IsInCombatMode)
{
DoAttack(user, coordinates, false, clickedUid);
return;
}
if (!ValidateInteractAndFace(user, coordinates))
@@ -313,12 +371,22 @@ namespace Content.Server.Interaction
}
else
{
// We are close to the nearby object and the object isn't contained in our active hand
// InteractUsing/AfterInteract: We will either use the item on the nearby object
if (item != null)
// We are close to the nearby object.
if (altInteract)
// We are trying to use alternative interactions. Perform alternative interactions, using context
// menu verbs.
// Verbs can be triggered with an item in the hand, but currently there are no verbs that depend on
// the currently held item. Maybe this if statement should be changed to
// (altInteract && (item == null || item == target)).
// Note that item == target will happen when alt-clicking the item currently in your hands.
AltInteract(user, target);
else if (item != null && item != target)
// We are performing a standard interaction with an item, and the target isn't the same as the item
// currently in our hand. We will use the item in our hand on the nearby object via InteractUsing
await InteractUsing(user, item, target, coordinates);
// InteractHand/Activate: Since our hand is empty we will use InteractHand/Activate
else
else if (item == null)
// Since our hand is empty we will use InteractHand/Activate
InteractHand(user, target);
}
}
@@ -432,6 +500,44 @@ namespace Content.Server.Interaction
await InteractDoAfter(user, used, target, clickLocation, true);
}
/// <summary>
/// Alternative interactions on an entity.
/// </summary>
/// <remarks>
/// Uses the context menu verb list, and acts out the first verb marked as an alternative interaction. Note
/// that this does not have any checks to see whether this interaction is valid, as these are all done in <see
/// cref="UserInteraction(IEntity, EntityCoordinates, EntityUid, bool)"/>
/// </remarks>
public void AltInteract(IEntity user, IEntity target)
{
// TODO VERB SYSTEM when ECS-ing verbs and re-writing VerbUtility.GetVerbs, maybe sort verbs by some
// priority property, such that which verbs appear first is more predictable?.
// Iterate through list of verbs that apply to target. We do not include global verbs here. If in the future
// alt click should also support global verbs, this needs to be changed.
foreach (var (component, verb) in VerbUtility.GetVerbs(target))
{
// Check that the verb marked as an alternative interaction?
if (!verb.AlternativeInteraction)
continue;
// Can the verb be acted out?
if (!VerbUtility.VerbAccessChecks(user, target, verb))
continue;
// Is the verb currently enabled?
var verbData = verb.GetData(user, component);
if (verbData.IsInvisible || verbData.IsDisabled)
continue;
// Act out the verb. Note that, if there is more than one AlternativeInteraction verb, only the first
// one is activated. The priority is effectively determined by the order in which VerbUtility.GetVerbs()
// returns the verbs.
verb.Activate(user, component);
break;
}
}
/// <summary>
/// Uses an empty hand on an entity
/// Finds components with the InteractHand interface and calls their function
@@ -470,11 +576,14 @@ namespace Content.Server.Interaction
/// </summary>
/// <param name="user"></param>
/// <param name="used"></param>
public void TryUseInteraction(IEntity user, IEntity used)
public void TryUseInteraction(IEntity user, IEntity used, bool altInteract = false)
{
if (user != null && used != null && _actionBlockerSystem.CanUse(user))
{
UseInteraction(user, used);
if (altInteract)
AltInteract(user, used);
else
UseInteraction(user, used);
}
}