5 new triggers: EmptyContainers, Knockdown, Stun, TriggerOnThrowDoHit (#41472)

* ideas

* finish components

* systems

* one more

* requested

---------

Co-authored-by: iaada <iaada@users.noreply.github.com>
This commit is contained in:
āda
2025-11-18 20:05:13 -06:00
committed by GitHub
parent 414817e38a
commit 78101bceac
9 changed files with 252 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
/// Trigger effect for removing and *deleting* all items in container(s) on the target.
/// </summary>
/// <remarks>
/// Be very careful when setting <see cref="BaseXOnTriggerComponent.TargetUser"/> to true or all your organs might fall out.
/// In fact, never set it to true.
/// </remarks>
/// <seealso cref="EmptyContainersOnTriggerComponent"/>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CleanContainersOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
/// Names of containers to empty.
/// If null, all containers will be emptied.
/// </summary>
[DataField, AutoNetworkedField]
public List<string>? Container;
}

View File

@@ -0,0 +1,22 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
/// Trigger effect for removing all items in container(s) on the target.
/// </summary>
/// <remarks>
/// Be very careful when setting <see cref="BaseXOnTriggerComponent.TargetUser"/> to true or all your organs might fall out.
/// In fact, never set it to true.
/// </remarks>
/// <seealso cref="CleanContainersOnTriggerComponent"/>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class EmptyContainersOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
/// Names of containers to empty.
/// If null, all containers will be emptied.
/// </summary>
[DataField, AutoNetworkedField]
public List<string>? Container;
}

View File

@@ -0,0 +1,36 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
/// Trigger effect for sending the target sidewise (crawling).
/// Knockdowns the user if <see cref="BaseXOnTriggerComponent.TargetUser"/> is true.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class KnockdownOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
/// How long the target is forced to be on the ground.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan KnockdownAmount = TimeSpan.FromSeconds(1);
/// <summary>
/// If true, refresh the duration.
/// If false, time is added on-top of any existing forced knockdown.
/// </summary>
[DataField, AutoNetworkedField]
public bool Refresh = true;
/// <summary>
/// Should the entity try and stand automatically?
/// </summary>
[DataField, AutoNetworkedField]
public bool AutoStand = true;
/// <summary>
/// Should the entity drop their items upon first being knocked down?
/// </summary>
[DataField, AutoNetworkedField]
public bool Drop = true;
}

View File

@@ -0,0 +1,24 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
/// Trigger effect for stunning an entity.
/// Stuns the user if <see cref="BaseXOnTriggerComponent.TargetUser"/> is true.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class StunOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
/// How long to stun the target.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan StunAmount = TimeSpan.FromSeconds(1);
/// <summary>
/// If true, refresh the stun duration.
/// If false, stun is added on-top of any existing stun.
/// </summary>
[DataField, AutoNetworkedField]
public bool Refresh = true;
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Trigger.Components.Triggers;
/// <summary>
/// Trigger for when this entity is thrown and then hits a second entity.
/// User is the entity hit.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class TriggerOnThrowDoHitComponent : BaseTriggerOnXComponent;

View File

@@ -0,0 +1,81 @@
using Content.Shared.Trigger.Components.Effects;
using Robust.Shared.Containers;
namespace Content.Shared.Trigger.Systems;
/// <summary>
/// Empty containers trigger system.
/// </summary>
public sealed class EmptyContainersOnTriggerSystem : XOnTriggerSystem<EmptyContainersOnTriggerComponent>
{
[Dependency] private readonly SharedContainerSystem _container = default!;
protected override void OnTrigger(Entity<EmptyContainersOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
{
if (!TryComp<ContainerManagerComponent>(target, out var containerComp))
return;
// Empty everything. Make sure a player isn't the target because they will get removed from their body along with their organs
if (ent.Comp.Container is null)
{
foreach (var container in _container.GetAllContainers(target, containerComp))
{
_container.EmptyContainer(container);
}
args.Handled = true;
}
// Empty containers in a sane way
else
{
foreach (var containerId in ent.Comp.Container)
{
if (!_container.TryGetContainer(target, containerId, out var container, containerComp))
continue;
_container.EmptyContainer(container);
args.Handled = true;
}
}
}
}
/// <summary>
/// Empty containers and delete items trigger system.
/// </summary>
public sealed class CleanContainersOnTriggerSystem : XOnTriggerSystem<CleanContainersOnTriggerComponent>
{
[Dependency] private readonly SharedContainerSystem _container = default!;
protected override void OnTrigger(Entity<CleanContainersOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
{
if (!TryComp<ContainerManagerComponent>(target, out var containerComp))
return;
// Empty everything. Make sure a player isn't the target because they will get DELETED
if (ent.Comp.Container is null)
{
foreach (var container in _container.GetAllContainers(target, containerComp))
{
_container.CleanContainer(container);
}
args.Handled = true;
}
// Empty containers in a sane way
else
{
foreach (var containerId in ent.Comp.Container)
{
if (!_container.TryGetContainer(target, containerId, out var container, containerComp))
continue;
_container.CleanContainer(container);
args.Handled = true;
}
}
}
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.Stunnable;
using Content.Shared.Trigger.Components.Effects;
namespace Content.Shared.Trigger.Systems;
public sealed class KnockdownOnTriggerSystem : XOnTriggerSystem<KnockdownOnTriggerComponent>
{
[Dependency] private readonly SharedStunSystem _stun = default!;
protected override void OnTrigger(Entity<KnockdownOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
{
args.Handled |= _stun.TryKnockdown(
target,
ent.Comp.KnockdownAmount,
ent.Comp.Refresh,
ent.Comp.AutoStand,
ent.Comp.Drop,
true
);
}
}

View File

@@ -0,0 +1,17 @@
using Content.Shared.Stunnable;
using Content.Shared.Trigger.Components.Effects;
namespace Content.Shared.Trigger.Systems;
public sealed class StunOnTriggerSystem : XOnTriggerSystem<StunOnTriggerComponent>
{
[Dependency] private readonly SharedStunSystem _stun = default!;
protected override void OnTrigger(Entity<StunOnTriggerComponent> ent, EntityUid target, ref TriggerEvent args)
{
if (ent.Comp.Refresh)
args.Handled |= _stun.TryUpdateStunDuration(target, ent.Comp.StunAmount);
else
args.Handled |= _stun.TryAddStunDuration(target, ent.Comp.StunAmount);
}
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.Throwing;
using Content.Shared.Trigger.Components.Triggers;
namespace Content.Shared.Trigger.Systems;
public sealed partial class TriggerOnThrowDoHitSystem : TriggerOnXSystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TriggerOnThrowDoHitComponent, ThrowDoHitEvent>(OnHit);
}
private void OnHit(Entity<TriggerOnThrowDoHitComponent> ent, ref ThrowDoHitEvent args)
{
Trigger.Trigger(ent.Owner, args.Target, ent.Comp.KeyOut);
}
}