TriggerOnUserInteractHand and TriggerOnUserInteractUsing (#41843)

* init

* handle check

* oops

* cleanup

* fix resolve

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
This commit is contained in:
ScarKy0
2025-12-14 03:11:58 +01:00
committed by GitHub
parent a40d130f0f
commit 231a93e742
7 changed files with 240 additions and 79 deletions

View File

@@ -1,4 +1,5 @@
using Content.Server.Atmos.EntitySystems;
using Content.Shared.Atmos.Components;
using Content.Shared.Trigger;
using Content.Shared.Trigger.Components.Effects;
@@ -31,7 +32,10 @@ public sealed class FireStackOnTriggerSystem : EntitySystem
if (target == null)
return;
_flame.AdjustFireStacks(target.Value, ent.Comp.FireStacks, ignite: ent.Comp.DoIgnite);
if (!TryComp<FlammableComponent>(target.Value, out var flammable))
return;
_flame.AdjustFireStacks(target.Value, ent.Comp.FireStacks, ignite: ent.Comp.DoIgnite, flammable: flammable);
args.Handled = true;
}
@@ -46,7 +50,10 @@ public sealed class FireStackOnTriggerSystem : EntitySystem
if (target == null)
return;
_flame.Extinguish(target.Value);
if (!TryComp<FlammableComponent>(target.Value, out var flammable))
return;
_flame.Extinguish(target.Value, flammable: flammable);
args.Handled = true;
}

View File

@@ -1,54 +1,75 @@
using JetBrains.Annotations;
using Robust.Shared.Map;
namespace Content.Shared.Interaction
namespace Content.Shared.Interaction;
public sealed class InteractHandEventArgs : EventArgs, ITargetedInteractEventArgs
{
public sealed class InteractHandEventArgs : EventArgs, ITargetedInteractEventArgs
public InteractHandEventArgs(EntityUid user, EntityUid target)
{
public InteractHandEventArgs(EntityUid user, EntityUid target)
{
User = user;
Target = target;
}
public EntityUid User { get; }
public EntityUid Target { get; }
User = user;
Target = target;
}
public EntityUid User { get; }
public EntityUid Target { get; }
}
/// <summary>
/// Raised directed on a target entity when it is interacted with by a user with an empty hand.
/// </summary>
[PublicAPI]
public sealed class InteractHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs
{
/// <summary>
/// Raised directed on a target entity when it is interacted with by a user with an empty hand.
/// Entity that triggered the interaction.
/// </summary>
[PublicAPI]
public sealed class InteractHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs
{
/// <summary>
/// Entity that triggered the interaction.
/// </summary>
public EntityUid User { get; }
/// <summary>
/// Entity that was interacted on.
/// </summary>
public EntityUid Target { get; }
public InteractHandEvent(EntityUid user, EntityUid target)
{
User = user;
Target = target;
}
}
public EntityUid User { get; }
/// <summary>
/// Raised on the user before interacting on an entity with bare hand.
/// Interaction is cancelled if this event is handled, so set it to true if you do custom interaction logic.
/// Entity that was interacted on.
/// </summary>
public sealed class BeforeInteractHandEvent : HandledEntityEventArgs
{
public EntityUid Target { get; }
public EntityUid Target { get; }
public BeforeInteractHandEvent(EntityUid target)
{
Target = target;
}
public InteractHandEvent(EntityUid user, EntityUid target)
{
User = user;
Target = target;
}
}
/// <summary>
/// Raised directed on the user when they interact with an entity with an empty hand.
/// </summary>
[PublicAPI]
public sealed class UserInteractHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs
{
/// <summary>
/// Entity that triggered the interaction.
/// </summary>
public EntityUid User { get; }
/// <summary>
/// Entity that was interacted on.
/// </summary>
public EntityUid Target { get; }
public UserInteractHandEvent(EntityUid user, EntityUid target)
{
User = user;
Target = target;
}
}
/// <summary>
/// Raised on the user before interacting on an entity with bare hand.
/// Interaction is cancelled if this event is handled, so set it to true if you do custom interaction logic.
/// </summary>
public sealed class BeforeInteractHandEvent : HandledEntityEventArgs
{
public EntityUid Target { get; }
public BeforeInteractHandEvent(EntityUid target)
{
Target = target;
}
}

View File

@@ -2,45 +2,85 @@ using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.Shared.Interaction
namespace Content.Shared.Interaction;
/// <summary>
/// Raised when a target entity is interacted with by a user while holding an object in their hand.
/// </summary>
[PublicAPI]
public sealed class InteractUsingEvent : HandledEntityEventArgs
{
/// <summary>
/// Raised when a target entity is interacted with by a user while holding an object in their hand.
/// Entity that triggered the interaction.
/// </summary>
[PublicAPI]
public sealed class InteractUsingEvent : HandledEntityEventArgs
public EntityUid User { get; }
/// <summary>
/// Entity that the user used to interact.
/// </summary>
public EntityUid Used { get; }
/// <summary>
/// Entity that was interacted on.
/// </summary>
public EntityUid Target { get; }
/// <summary>
/// The original location that was clicked by the user.
/// </summary>
public EntityCoordinates ClickLocation { get; }
public InteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation)
{
/// <summary>
/// Entity that triggered the interaction.
/// </summary>
public EntityUid User { get; }
// Interact using should not have the same used and target.
// That should be a use-in-hand event instead.
// If this is not the case, can lead to bugs (e.g., attempting to merge a item stack into itself).
DebugTools.Assert(used != target);
/// <summary>
/// Entity that the user used to interact.
/// </summary>
public EntityUid Used { get; }
/// <summary>
/// Entity that was interacted on.
/// </summary>
public EntityUid Target { get; }
/// <summary>
/// The original location that was clicked by the user.
/// </summary>
public EntityCoordinates ClickLocation { get; }
public InteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation)
{
// Interact using should not have the same used and target.
// That should be a use-in-hand event instead.
// If this is not the case, can lead to bugs (e.g., attempting to merge a item stack into itself).
DebugTools.Assert(used != target);
User = user;
Used = used;
Target = target;
ClickLocation = clickLocation;
}
User = user;
Used = used;
Target = target;
ClickLocation = clickLocation;
}
}
/// <summary>
/// Raised when a user entity interacts with a target while holding an object in their hand.
/// </summary>
[PublicAPI]
public sealed class UserInteractUsingEvent : HandledEntityEventArgs
{
/// <summary>
/// Entity that triggered the interaction.
/// </summary>
public EntityUid User { get; }
/// <summary>
/// Entity that the user used to interact.
/// </summary>
public EntityUid Used { get; }
/// <summary>
/// Entity that was interacted on.
/// </summary>
public EntityUid Target { get; }
/// <summary>
/// The original location that was clicked by the user.
/// </summary>
public EntityCoordinates ClickLocation { get; }
public UserInteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation)
{
// Interact using should not have the same used and target.
// That should be a use-in-hand event instead.
// If this is not the case, can lead to bugs (e.g., attempting to merge a item stack into itself).
DebugTools.Assert(used != target);
User = user;
Used = used;
Target = target;
ClickLocation = clickLocation;
}
}

View File

@@ -523,12 +523,16 @@ namespace Content.Shared.Interaction
}
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(target));
// all interactions should only happen when in range / unobstructed, so no range check is needed
var message = new InteractHandEvent(user, target);
RaiseLocalEvent(target, message, true);
var userMessage = new UserInteractHandEvent(user, target);
RaiseLocalEvent(user, userMessage, true);
_adminLogger.Add(LogType.InteractHand, LogImpact.Low, $"{user} interacted with {target}");
DoContactInteraction(user, target, message);
if (message.Handled)
if (message.Handled || userMessage.Handled)
return;
DebugTools.Assert(!IsDeleted(user) && !IsDeleted(target));
@@ -1061,10 +1065,14 @@ namespace Content.Shared.Interaction
// all interactions should only happen when in range / unobstructed, so no range check is needed
var interactUsingEvent = new InteractUsingEvent(user, used, target, clickLocation);
RaiseLocalEvent(target, interactUsingEvent, true);
var userInteractUsingEvent = new UserInteractUsingEvent(user, used, target, clickLocation);
RaiseLocalEvent(user, userInteractUsingEvent, true);
DoContactInteraction(user, used, interactUsingEvent);
DoContactInteraction(user, target, interactUsingEvent);
// Contact interactions are currently only used for forensics, so we don't raise used -> target
if (interactUsingEvent.Handled)
if (interactUsingEvent.Handled || userInteractUsingEvent.Handled)
return true;
if (InteractDoAfter(user, used, target, clickLocation, canReach: true, checkDeletion: false))

View File

@@ -0,0 +1,18 @@
using Content.Shared.Interaction;
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Triggers;
/// <summary>
/// Trigger on <see cref="UserInteractHandEvent"/>, aka when owner clicks on an entity with an empty hand.
/// The trigger user is the entity that got interacted with.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class TriggerOnUserInteractHandComponent : BaseTriggerOnXComponent
{
/// <summary>
/// Whether the interaction should be marked as handled after it happens.
/// </summary>
[DataField, AutoNetworkedField]
public bool Handle = true;
}

View File

@@ -0,0 +1,40 @@
using Content.Shared.Interaction;
using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Triggers;
/// <summary>
/// Triggers when the owner uses another entity to interact with another entity (<see cref="UserInteractUsingEvent"/>).
/// The trigger user is the interacted entity or the item used, depending on the TargetUsed datafield.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class TriggerOnUserInteractUsingComponent : BaseTriggerOnXComponent
{
/// <summary>
/// Whitelist of entities that can be used to trigger this component.
/// </summary>
/// <remarks>No whitelist check when null.</remarks>
[DataField, AutoNetworkedField]
public EntityWhitelist? Whitelist;
/// <summary>
/// Blacklist of entities that cannot be used to trigger this component.
/// </summary>
/// <remarks>No blacklist check when null.</remarks>
[DataField, AutoNetworkedField]
public EntityWhitelist? Blacklist;
/// <summary>
/// If false, the trigger user will be the entity that got interacted with.
/// If true, the trigger user will the entity that was used to interact.
/// </summary>
[DataField, AutoNetworkedField]
public bool TargetUsed = false;
/// <summary>
/// Whether the interaction should be marked as handled after it happens.
/// </summary>
[DataField, AutoNetworkedField]
public bool Handle = true;
}

View File

@@ -16,7 +16,9 @@ public sealed partial class TriggerSystem
SubscribeLocalEvent<TriggerOnActivateComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<TriggerOnUseComponent, UseInHandEvent>(OnUse);
SubscribeLocalEvent<TriggerOnInteractHandComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<TriggerOnUserInteractHandComponent, UserInteractHandEvent>(OnUserInteractHand);
SubscribeLocalEvent<TriggerOnInteractUsingComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<TriggerOnUserInteractUsingComponent, UserInteractUsingEvent>(OnUserInteractUsing);
SubscribeLocalEvent<TriggerOnThrowComponent, ThrowEvent>(OnThrow);
SubscribeLocalEvent<TriggerOnThrownComponent, ThrownEvent>(OnThrown);
@@ -64,6 +66,17 @@ public sealed partial class TriggerSystem
args.Handled = true;
}
private void OnUserInteractHand(Entity<TriggerOnUserInteractHandComponent> ent, ref UserInteractHandEvent args)
{
if (args.Handled)
return;
Trigger(ent.Owner, args.Target, ent.Comp.KeyOut);
if (ent.Comp.Handle)
args.Handled = true;
}
private void OnInteractUsing(Entity<TriggerOnInteractUsingComponent> ent, ref InteractUsingEvent args)
{
if (args.Handled)
@@ -76,6 +89,20 @@ public sealed partial class TriggerSystem
args.Handled = true;
}
private void OnUserInteractUsing(Entity<TriggerOnUserInteractUsingComponent> ent, ref UserInteractUsingEvent args)
{
if (args.Handled)
return;
if (!_whitelist.CheckBoth(args.Used, ent.Comp.Blacklist, ent.Comp.Whitelist))
return;
Trigger(ent.Owner, ent.Comp.TargetUsed ? args.Used : args.Target, ent.Comp.KeyOut);
if (ent.Comp.Handle)
args.Handled = true;
}
private void OnThrow(Entity<TriggerOnThrowComponent> ent, ref ThrowEvent args)
{
Trigger(ent.Owner, args.Thrown, ent.Comp.KeyOut);