From 231a93e742fb2cbab906e418f62b6ff18df0a6dd Mon Sep 17 00:00:00 2001 From: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Date: Sun, 14 Dec 2025 03:11:58 +0100 Subject: [PATCH] TriggerOnUserInteractHand and TriggerOnUserInteractUsing (#41843) * init * handle check * oops * cleanup * fix resolve --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Systems/FireStackOnTriggerSystem.cs | 11 +- Content.Shared/Interaction/InteractHand.cs | 95 +++++++++------ Content.Shared/Interaction/InteractUsing.cs | 112 ++++++++++++------ .../Interaction/SharedInteractionSystem.cs | 12 +- .../TriggerOnUserInteractHandComponent.cs | 18 +++ .../TriggerOnUserInteractUsingComponent.cs | 40 +++++++ .../Systems/TriggerSystem.Interaction.cs | 27 +++++ 7 files changed, 238 insertions(+), 77 deletions(-) create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractHandComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractUsingComponent.cs diff --git a/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs b/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs index af3298b865..b1d89bda05 100644 --- a/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs +++ b/Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs @@ -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(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(target.Value, out var flammable)) + return; + + _flame.Extinguish(target.Value, flammable: flammable); args.Handled = true; } diff --git a/Content.Shared/Interaction/InteractHand.cs b/Content.Shared/Interaction/InteractHand.cs index 1d2df4c28b..f6bf7dd231 100644 --- a/Content.Shared/Interaction/InteractHand.cs +++ b/Content.Shared/Interaction/InteractHand.cs @@ -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; } +} + +/// +/// Raised directed on a target entity when it is interacted with by a user with an empty hand. +/// +[PublicAPI] +public sealed class InteractHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs +{ + /// + /// Entity that triggered the interaction. + /// + public EntityUid User { get; } + /// - /// Raised directed on a target entity when it is interacted with by a user with an empty hand. + /// Entity that was interacted on. /// - [PublicAPI] - public sealed class InteractHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs + public EntityUid Target { get; } + + public InteractHandEvent(EntityUid user, EntityUid target) { - /// - /// Entity that triggered the interaction. - /// - public EntityUid User { get; } - - /// - /// Entity that was interacted on. - /// - public EntityUid Target { get; } - - public InteractHandEvent(EntityUid user, EntityUid target) - { - User = user; - Target = target; - } + User = user; + Target = target; } +} +/// +/// Raised directed on the user when they interact with an entity with an empty hand. +/// +[PublicAPI] +public sealed class UserInteractHandEvent : HandledEntityEventArgs, ITargetedInteractEventArgs +{ /// - /// 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 triggered the interaction. /// - public sealed class BeforeInteractHandEvent : HandledEntityEventArgs + public EntityUid User { get; } + + /// + /// Entity that was interacted on. + /// + public EntityUid Target { get; } + + public UserInteractHandEvent(EntityUid user, EntityUid target) { - public EntityUid Target { get; } + User = user; + Target = target; + } +} + +/// +/// 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. +/// +public sealed class BeforeInteractHandEvent : HandledEntityEventArgs +{ + public EntityUid Target { get; } - public BeforeInteractHandEvent(EntityUid target) - { - Target = target; - } + public BeforeInteractHandEvent(EntityUid target) + { + Target = target; } } diff --git a/Content.Shared/Interaction/InteractUsing.cs b/Content.Shared/Interaction/InteractUsing.cs index 3ebf40a517..7d3d4e978e 100644 --- a/Content.Shared/Interaction/InteractUsing.cs +++ b/Content.Shared/Interaction/InteractUsing.cs @@ -2,45 +2,85 @@ using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Utility; -namespace Content.Shared.Interaction +namespace Content.Shared.Interaction; + +/// +/// Raised when a target entity is interacted with by a user while holding an object in their hand. +/// +[PublicAPI] +public sealed class InteractUsingEvent : HandledEntityEventArgs { /// - /// Raised when a target entity is interacted with by a user while holding an object in their hand. + /// Entity that triggered the interaction. + /// + public EntityUid User { get; } + + /// + /// Entity that the user used to interact. + /// + public EntityUid Used { get; } + + /// + /// Entity that was interacted on. + /// + public EntityUid Target { get; } + + /// + /// The original location that was clicked by the user. /// - [PublicAPI] - public sealed class InteractUsingEvent : HandledEntityEventArgs + public EntityCoordinates ClickLocation { get; } + + public InteractUsingEvent(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation) { - /// - /// Entity that triggered the interaction. - /// - public EntityUid User { get; } - - /// - /// Entity that the user used to interact. - /// - public EntityUid Used { get; } - - /// - /// Entity that was interacted on. - /// - public EntityUid Target { get; } - - /// - /// The original location that was clicked by the user. - /// - 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; - } + // 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; } } + +/// +/// Raised when a user entity interacts with a target while holding an object in their hand. +/// +[PublicAPI] +public sealed class UserInteractUsingEvent : HandledEntityEventArgs +{ + /// + /// Entity that triggered the interaction. + /// + public EntityUid User { get; } + + /// + /// Entity that the user used to interact. + /// + public EntityUid Used { get; } + + /// + /// Entity that was interacted on. + /// + public EntityUid Target { get; } + + /// + /// The original location that was clicked by the user. + /// + 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; + } +} + diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index d34e86ad5c..88d42b8a28 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -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)) diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractHandComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractHandComponent.cs new file mode 100644 index 0000000000..58deef945a --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractHandComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Interaction; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Trigger on , aka when owner clicks on an entity with an empty hand. +/// The trigger user is the entity that got interacted with. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnUserInteractHandComponent : BaseTriggerOnXComponent +{ + /// + /// Whether the interaction should be marked as handled after it happens. + /// + [DataField, AutoNetworkedField] + public bool Handle = true; +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractUsingComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractUsingComponent.cs new file mode 100644 index 0000000000..0550b3bbaf --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnUserInteractUsingComponent.cs @@ -0,0 +1,40 @@ +using Content.Shared.Interaction; +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// Triggers when the owner uses another entity to interact with another entity (). +/// The trigger user is the interacted entity or the item used, depending on the TargetUsed datafield. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnUserInteractUsingComponent : BaseTriggerOnXComponent +{ + /// + /// Whitelist of entities that can be used to trigger this component. + /// + /// No whitelist check when null. + [DataField, AutoNetworkedField] + public EntityWhitelist? Whitelist; + + /// + /// Blacklist of entities that cannot be used to trigger this component. + /// + /// No blacklist check when null. + [DataField, AutoNetworkedField] + public EntityWhitelist? Blacklist; + + /// + /// 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. + /// + [DataField, AutoNetworkedField] + public bool TargetUsed = false; + + /// + /// Whether the interaction should be marked as handled after it happens. + /// + [DataField, AutoNetworkedField] + public bool Handle = true; +} diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs index 944239ccf8..922762973c 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs @@ -16,7 +16,9 @@ public sealed partial class TriggerSystem SubscribeLocalEvent(OnActivate); SubscribeLocalEvent(OnUse); SubscribeLocalEvent(OnInteractHand); + SubscribeLocalEvent(OnUserInteractHand); SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnUserInteractUsing); SubscribeLocalEvent(OnThrow); SubscribeLocalEvent(OnThrown); @@ -64,6 +66,17 @@ public sealed partial class TriggerSystem args.Handled = true; } + private void OnUserInteractHand(Entity 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 ent, ref InteractUsingEvent args) { if (args.Handled) @@ -76,6 +89,20 @@ public sealed partial class TriggerSystem args.Handled = true; } + private void OnUserInteractUsing(Entity 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 ent, ref ThrowEvent args) { Trigger(ent.Owner, args.Thrown, ent.Comp.KeyOut); -- 2.52.0