From eee5751a22b028ef1210d648944fd9e4bc45fd81 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C4=81da?= Date: Tue, 23 Sep 2025 15:24:45 -0500 Subject: [PATCH] TriggerOnPlayerSpawnComplete and ExplosionOnTrigger (#39820) --- .../EntitySystems/ExplosionSystem.cs | 16 +------ .../EntitySystems/SharedExplosionSystem.cs | 32 +++++++++++++ .../Effects/ExplodeOnTriggerComponent.cs | 5 +- .../Effects/ExplosionOnTriggerComponent.cs | 46 +++++++++++++++++++ .../TriggerOnPlayerSpawnCompleteComponent.cs | 11 +++++ .../Trigger/Systems/ExplodeOnTriggerSystem.cs | 28 ++++++++++- .../Trigger/Systems/TriggerSystem.Spawn.cs | 9 +++- 7 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 Content.Shared/Trigger/Components/Effects/ExplosionOnTriggerComponent.cs create mode 100644 Content.Shared/Trigger/Components/Triggers/TriggerOnPlayerSpawnCompleteComponent.cs diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index fc31a77041..b459f5c70f 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -63,16 +63,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem public const int MaxExplosionAudioRange = 30; - /// - /// The "default" explosion prototype. - /// - /// - /// Generally components should specify an explosion prototype via a yaml datafield, so that the yaml-linter can - /// find errors. However some components, like rogue arrows, or some commands like the admin-smite need to have - /// a "default" option specified outside of yaml data-fields. Hence this const string. - /// - public static readonly ProtoId DefaultExplosionPrototypeId = "Default"; - public override void Initialize() { base.Initialize(); @@ -222,10 +212,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem return r0 * (MathF.Sqrt(12 * totalIntensity / v0 - 3) / 6 + 0.5f); } - /// - /// Queue an explosions, centered on some entity. - /// - public void QueueExplosion(EntityUid uid, + /// + public override void QueueExplosion(EntityUid uid, string typeId, float totalIntensity, float slope, diff --git a/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs index f298255807..d6053c9c3c 100644 --- a/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs +++ b/Content.Shared/Explosion/EntitySystems/SharedExplosionSystem.cs @@ -1,14 +1,26 @@ using Content.Shared.Armor; using Content.Shared.Explosion.Components; +using Robust.Shared.Prototypes; namespace Content.Shared.Explosion.EntitySystems; +// TODO some sort of struct like DamageSpecifier but for explosions. /// /// Lets code in shared trigger explosions and handles explosion resistance examining. /// All processing is still done clientside. /// public abstract class SharedExplosionSystem : EntitySystem { + /// + /// The "default" explosion prototype. + /// + /// + /// Generally components should specify an explosion prototype via a yaml datafield, so that the yaml-linter can + /// find errors. However some components, like rogue arrows, or some commands like the admin-smite need to have + /// a "default" option specified outside of yaml data-fields. Hence this const string. + /// + public static readonly ProtoId DefaultExplosionPrototypeId = "Default"; + public override void Initialize() { base.Initialize(); @@ -37,4 +49,24 @@ public abstract class SharedExplosionSystem : EntitySystem public virtual void TriggerExplosive(EntityUid uid, ExplosiveComponent? explosive = null, bool delete = true, float? totalIntensity = null, float? radius = null, EntityUid? user = null) { } + + /// + /// Queue an explosion centered on some entity. Bypasses needing . + /// + /// Where the explosion happens. + /// A ProtoId of type . + /// The entity which caused the explosion. + /// Whether to add an admin log about this explosion. Includes user. + public virtual void QueueExplosion(EntityUid uid, + string typeId, + float totalIntensity, + float slope, + float maxTileIntensity, + float tileBreakScale = 1f, + int maxTileBreak = int.MaxValue, + bool canCreateVacuum = true, + EntityUid? user = null, + bool addLog = true) + { + } } diff --git a/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs index 2a1af40a2c..9bb7ce9fa0 100644 --- a/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs +++ b/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.Explosion.Components; using Robust.Shared.GameStates; namespace Content.Shared.Trigger.Components.Effects; @@ -7,8 +8,6 @@ namespace Content.Shared.Trigger.Components.Effects; /// TargetUser will only work of the user has ExplosiveComponent as well. /// The User will be logged in the admin logs. /// -/// -/// TODO: Allow this to work without an ExplosiveComponent on the user via QueueExplosion. -/// +/// [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class ExplodeOnTriggerComponent : BaseXOnTriggerComponent; diff --git a/Content.Shared/Trigger/Components/Effects/ExplosionOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ExplosionOnTriggerComponent.cs new file mode 100644 index 0000000000..40b59e7a97 --- /dev/null +++ b/Content.Shared/Trigger/Components/Effects/ExplosionOnTriggerComponent.cs @@ -0,0 +1,46 @@ +using Content.Shared.Explosion; +using Content.Shared.Explosion.Components; +using Content.Shared.Explosion.EntitySystems; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Trigger.Components.Effects; + +// TODO some sort of struct like DamageSpecifier but for explosions. +/// +/// Will explode the entity using this component's explosion specifications. +/// If TargetUser is true, they'll explode instead. +/// The User will be logged in the admin logs. +/// +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ExplosionOnTriggerComponent : BaseXOnTriggerComponent +{ + /// + [DataField, AutoNetworkedField] + public ProtoId ExplosionType = SharedExplosionSystem.DefaultExplosionPrototypeId; + + /// + [DataField, AutoNetworkedField] + public float MaxTileIntensity = 4; + + /// + [DataField, AutoNetworkedField] + public float IntensitySlope = 1; + + /// + [DataField, AutoNetworkedField] + public float TotalIntensity = 10; + + /// + [DataField, AutoNetworkedField] + public float TileBreakScale = 1f; + + /// + [DataField, AutoNetworkedField] + public int MaxTileBreak = int.MaxValue; + + /// + [DataField, AutoNetworkedField] + public bool CanCreateVacuum = true; +} diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnPlayerSpawnCompleteComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnPlayerSpawnCompleteComponent.cs new file mode 100644 index 0000000000..2151b7edcc --- /dev/null +++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnPlayerSpawnCompleteComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.GameTicking; +using Robust.Shared.GameStates; + +namespace Content.Shared.Trigger.Components.Triggers; + +/// +/// A trigger which occurs on . +/// +/// This does not work with , as it would add this component while the event is getting raised. +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class TriggerOnPlayerSpawnCompleteComponent : BaseTriggerOnXComponent; diff --git a/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs index 1c773b79a6..120aa23a9d 100644 --- a/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs +++ b/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs @@ -11,10 +11,11 @@ public sealed class ExplodeOnTriggerSystem : EntitySystem { base.Initialize(); - SubscribeLocalEvent(OnTrigger); + SubscribeLocalEvent(OnExplodeTrigger); + SubscribeLocalEvent(OnQueueExplosionTrigger); } - private void OnTrigger(Entity ent, ref TriggerEvent args) + private void OnExplodeTrigger(Entity ent, ref TriggerEvent args) { if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) return; @@ -27,4 +28,27 @@ public sealed class ExplodeOnTriggerSystem : EntitySystem _explosion.TriggerExplosive(target.Value, user: args.User); args.Handled = true; } + + private void OnQueueExplosionTrigger(Entity ent, ref TriggerEvent args) + { + var (uid, comp) = ent; + if (args.Key != null && !comp.KeysIn.Contains(args.Key)) + return; + + var target = comp.TargetUser ? args.User : uid; + + if (target == null) + return; + + _explosion.QueueExplosion(target.Value, + comp.ExplosionType, + comp.TotalIntensity, + comp.IntensitySlope, + comp.MaxTileIntensity, + comp.TileBreakScale, + comp.MaxTileBreak, + comp.CanCreateVacuum, + args.User); + args.Handled = true; + } } diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs index 83457361fd..8750110744 100644 --- a/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs +++ b/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs @@ -1,4 +1,5 @@ -using Content.Shared.Trigger.Components.Effects; +using Content.Shared.GameTicking; +using Content.Shared.Trigger.Components.Effects; using Content.Shared.Trigger.Components.Triggers; using Robust.Shared.Prototypes; @@ -9,6 +10,7 @@ public sealed partial class TriggerSystem private void InitializeSpawn() { SubscribeLocalEvent(OnSpawnInit); + SubscribeLocalEvent(OnPlayerSpawn); SubscribeLocalEvent(HandleSpawnOnTrigger); SubscribeLocalEvent(HandleSpawnTableOnTrigger); @@ -20,6 +22,11 @@ public sealed partial class TriggerSystem Trigger(ent.Owner, null, ent.Comp.KeyOut); } + private void OnPlayerSpawn(Entity ent, ref PlayerSpawnCompleteEvent args) + { + Trigger(ent.Owner, null, ent.Comp.KeyOut); + } + private void HandleSpawnOnTrigger(Entity ent, ref TriggerEvent args) { if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) -- 2.51.2