]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Trigger Refactor (#39034)
authorslarticodefast <161409025+slarticodefast@users.noreply.github.com>
Sun, 3 Aug 2025 19:20:37 +0000 (21:20 +0200)
committerGitHub <noreply@github.com>
Sun, 3 Aug 2025 19:20:37 +0000 (21:20 +0200)
256 files changed:
Content.Client/Explosion/SmokeOnTriggerSystem.cs [deleted file]
Content.Client/Explosion/TriggerOnProximityComponent.cs [deleted file]
Content.Client/Explosion/TriggerSystem.cs [deleted file]
Content.Client/HotPotato/HotPotatoSystem.cs
Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs [moved from Content.Client/Trigger/TimerTriggerVisualizerComponent.cs with 84% similarity]
Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs [moved from Content.Client/Explosion/TriggerSystem.Proximity.cs with 91% similarity]
Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs [new file with mode: 0644]
Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs [moved from Content.Client/Trigger/TimerTriggerVisualizerSystem.cs with 80% similarity]
Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs
Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs [deleted file]
Content.Server/Animals/Systems/ParrotMemorySystem.cs
Content.Server/Chat/SpeakOnTriggerComponent.cs [deleted file]
Content.Server/Chat/SuicideSystem.cs
Content.Server/Chat/Systems/ChatSystem.cs
Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs [deleted file]
Content.Server/Defusable/Systems/DefusableSystem.cs
Content.Server/Destructible/DestructibleSystem.cs
Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs
Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs
Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs
Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs
Content.Server/DeviceLinking/Systems/SignallerSystem.cs
Content.Server/Electrocution/ElectrocutionSystem.cs
Content.Server/Emp/EmpOnTriggerComponent.cs [deleted file]
Content.Server/Emp/EmpSystem.cs
Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs [deleted file]
Content.Server/Explosion/Components/AutomatedTimerComponent.cs [deleted file]
Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs
Content.Server/Explosion/Components/ShockOnTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnActivateComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnCollideComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnProximityComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnSignalComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnSlipComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnUseComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs [deleted file]
Content.Server/Explosion/Components/TriggerWhitelistComponent.cs [deleted file]
Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs
Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs [deleted file]
Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs [deleted file]
Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs
Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs [deleted file]
Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs [deleted file]
Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs [deleted file]
Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs [deleted file]
Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs [deleted file]
Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs [deleted file]
Content.Server/Explosion/EntitySystems/TriggerSystem.cs [deleted file]
Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs [deleted file]
Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs [deleted file]
Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs [deleted file]
Content.Server/Holopad/HolopadSystem.cs
Content.Server/HotPotato/HotPotatoSystem.cs
Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs [deleted file]
Content.Server/LandMines/LandMineSystem.cs
Content.Server/Mousetrap/MousetrapSystem.cs [deleted file]
Content.Server/Ninja/Systems/SpiderChargeSystem.cs
Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs
Content.Server/Payload/EntitySystems/PayloadSystem.cs
Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs [deleted file]
Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs [deleted file]
Content.Server/Polymorph/Systems/PolymorphSystem.cs
Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs
Content.Server/Silicons/Borgs/BorgSystem.cs
Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs [deleted file]
Content.Server/Sound/EmitSoundSystem.cs
Content.Server/Speech/Components/ActiveListenerComponent.cs [deleted file]
Content.Server/Speech/EntitySystems/BlockListeningSystem.cs
Content.Server/Speech/EntitySystems/ListeningSystem.cs
Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs
Content.Server/Telephone/TelephoneSystem.cs
Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs [moved from Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs with 74% similarity]
Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs [new file with mode: 0644]
Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs [moved from Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs with 66% similarity]
Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs [new file with mode: 0644]
Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs [new file with mode: 0644]
Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs [new file with mode: 0644]
Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs [new file with mode: 0644]
Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs [moved from Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs with 53% similarity]
Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs
Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs
Content.Shared/Chat/SharedChatSystem.cs
Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs [deleted file]
Content.Shared/Emp/SharedEmpSystem.cs
Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs [deleted file]
Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs [deleted file]
Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs [deleted file]
Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs
Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs [deleted file]
Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs [deleted file]
Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs [deleted file]
Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs [deleted file]
Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs [deleted file]
Content.Shared/Flash/Components/FlashOnTriggerComponent.cs [deleted file]
Content.Shared/HotPotato/ActiveHotPotatoComponent.cs
Content.Shared/HotPotato/HotPotatoComponent.cs
Content.Shared/HotPotato/SharedHotPotatoSystem.cs
Content.Shared/Implants/Components/RattleComponent.cs [deleted file]
Content.Shared/Implants/Components/TriggerImplantActionComponent.cs [deleted file]
Content.Shared/Implants/SharedSubdermalImplantSystem.cs
Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
Content.Shared/Mousetrap/MousetrapComponent.cs
Content.Shared/Mousetrap/MousetrapSystem.cs [new file with mode: 0644]
Content.Shared/Mousetrap/MousetrapVisuals.cs [deleted file]
Content.Shared/Ninja/Components/SpiderChargeComponent.cs
Content.Shared/Payload/Components/PayloadTriggerComponent.cs
Content.Shared/Rootable/SharedRootableSystem.cs
Content.Shared/Speech/Components/ActiveListenerComponent.cs [new file with mode: 0644]
Content.Shared/Speech/ListenEvent.cs [moved from Content.Server/Speech/ListenEvent.cs with 93% similarity]
Content.Shared/Sticky/Components/StickyComponent.cs
Content.Shared/Timing/UseDelaySystem.cs
Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs [moved from Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs with 90% similarity]
Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs [moved from Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs with 57% similarity]
Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs [moved from Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs with 59% similarity]
Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs [moved from Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs with 50% similarity]
Content.Shared/Trigger/Components/TimerTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs [moved from Content.Server/Explosion/Components/RepeatingTriggerComponent.cs with 56% similarity]
Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs [moved from Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs with 56% similarity]
Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Proximity.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Signal.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Timer.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/Systems/TwoStageTriggerSystem.cs [new file with mode: 0644]
Content.Shared/Trigger/TriggerEvent.cs [new file with mode: 0644]
Content.Shared/Trigger/TriggerVisuals.cs
Content.Shared/Trigger/VoiceTriggeredEvent.cs [new file with mode: 0644]
Content.Shared/Weapons/Ranged/Events/OnEmptyGunShotEvent.cs
Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
Resources/Locale/en-US/machine-linking/receiver_ports.ftl
Resources/Locale/en-US/machine-linking/transmitter_ports.ftl
Resources/Locale/en-US/triggers/ghost-kick-on-trigger.ftl [new file with mode: 0644]
Resources/Locale/en-US/triggers/timer-trigger.ftl [new file with mode: 0644]
Resources/Locale/en-US/triggers/toggle-trigger-condition.ftl [new file with mode: 0644]
Resources/Locale/en-US/triggers/trigger-on-verb.ftl [new file with mode: 0644]
Resources/Locale/en-US/triggers/trigger-on-voice.ftl [new file with mode: 0644]
Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl [deleted file]
Resources/Locale/en-US/weapons/grenades/voice-trigger.ftl [deleted file]
Resources/Maps/Shuttles/ShuttleEvent/quark.yml
Resources/Prototypes/DeviceLinking/sink_ports.yml
Resources/Prototypes/DeviceLinking/source_ports.yml
Resources/Prototypes/Entities/Clothing/Back/backpacks.yml
Resources/Prototypes/Entities/Effects/admin_triggers.yml
Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/NPCs/elemental.yml
Resources/Prototypes/Entities/Objects/Consumable/Food/Baked/pie.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/igniter.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/signaller.yml
Resources/Prototypes/Entities/Objects/Devices/Electronics/triggers.yml
Resources/Prototypes/Entities/Objects/Devices/desynchronizer.yml
Resources/Prototypes/Entities/Objects/Devices/mousetrap.yml
Resources/Prototypes/Entities/Objects/Devices/payload.yml
Resources/Prototypes/Entities/Objects/Fun/bike_horn.yml
Resources/Prototypes/Entities/Objects/Fun/dice.yml
Resources/Prototypes/Entities/Objects/Fun/figurines.yml
Resources/Prototypes/Entities/Objects/Fun/toys.yml
Resources/Prototypes/Entities/Objects/Materials/shards.yml
Resources/Prototypes/Entities/Objects/Misc/land_mine.yml
Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml
Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml
Resources/Prototypes/Entities/Objects/Specific/Janitorial/soap.yml
Resources/Prototypes/Entities/Objects/Weapons/Bombs/firebomb.yml
Resources/Prototypes/Entities/Objects/Weapons/Bombs/funny.yml
Resources/Prototypes/Entities/Objects/Weapons/Bombs/pen.yml
Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml
Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml
Resources/Prototypes/Entities/Objects/Weapons/Bombs/spider.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
Resources/Prototypes/Entities/Objects/Weapons/Guns/Turrets/turrets_ballistic.yml
Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
Resources/Prototypes/Entities/Objects/Weapons/Throwable/projectile_grenades.yml
Resources/Prototypes/Entities/Objects/Weapons/Throwable/scattering_grenades.yml
Resources/Prototypes/Entities/Objects/Weapons/security.yml
Resources/Prototypes/Entities/Structures/Machines/bombs.yml
Resources/Prototypes/Entities/Structures/Walls/asteroid.yml
Resources/ServerInfo/Guidebook/Security/Defusal.xml

diff --git a/Content.Client/Explosion/SmokeOnTriggerSystem.cs b/Content.Client/Explosion/SmokeOnTriggerSystem.cs
deleted file mode 100644 (file)
index cac255e..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Explosion.EntitySystems;
-
-namespace Content.Client.Explosion;
-
-public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
-{
-}
\ No newline at end of file
diff --git a/Content.Client/Explosion/TriggerOnProximityComponent.cs b/Content.Client/Explosion/TriggerOnProximityComponent.cs
deleted file mode 100644 (file)
index 5fa9bbf..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Explosion;
-using Content.Shared.Explosion.Components;
-
-namespace Content.Client.Explosion;
-
-[RegisterComponent, Access(typeof(TriggerSystem))]
-public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent {}
diff --git a/Content.Client/Explosion/TriggerSystem.cs b/Content.Client/Explosion/TriggerSystem.cs
deleted file mode 100644 (file)
index e18569a..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Client.Explosion;
-
-public sealed partial class TriggerSystem : EntitySystem
-{
-    public override void Initialize()
-    {
-        base.Initialize();
-        InitializeProximity();
-    }
-}
index 028a3b70d9f42ac2511bb9d2fddaa797a28a3634..a1495ab9945e8f9447176a22537ee3d801462dde 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.HotPotato;
 using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
 using Robust.Shared.Timing;
 
 namespace Content.Client.HotPotato;
@@ -10,6 +11,9 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
 
+    private readonly EntProtoId _hotPotatoEffectId = "HotPotatoEffect";
+
+    // TODO: particle system
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
@@ -23,7 +27,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
             if (_timing.CurTime < comp.TargetTime)
                 continue;
             comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown);
-            Spawn("HotPotatoEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
+            Spawn(_hotPotatoEffectId, _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
         }
     }
 }
similarity index 84%
rename from Content.Client/Trigger/TimerTriggerVisualizerComponent.cs
rename to Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs
index 0e5a74b83b186b98e097781a6ce861055e45b102..0cb89edd895a051e8baf23b5afd8193798b2c974 100644 (file)
@@ -1,7 +1,8 @@
+using Content.Client.Trigger.Systems;
 using Robust.Client.Animations;
 using Robust.Shared.Audio;
 
-namespace Content.Client.Trigger;
+namespace Content.Client.Trigger.Components;
 
 [RegisterComponent]
 [Access(typeof(TimerTriggerVisualizerSystem))]
@@ -16,28 +17,27 @@ public sealed partial class TimerTriggerVisualsComponent : Component
     /// <summary>
     /// The RSI state used while the device has not been primed.
     /// </summary>
-    [DataField("unprimedSprite")]
-    [ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public string UnprimedSprite = "icon";
 
     /// <summary>
     /// The RSI state used when the device is primed.
     /// Not VVWrite-able because it's only used at component init to construct the priming animation.
     /// </summary>
-    [DataField("primingSprite")]
+    [DataField]
     public string PrimingSprite = "primed";
 
     /// <summary>
     /// The sound played when the device is primed.
     /// Not VVWrite-able because it's only used at component init to construct the priming animation.
     /// </summary>
-    [DataField("primingSound")]
+    [DataField, ViewVariables]
     public SoundSpecifier? PrimingSound;
 
     /// <summary>
     /// The actual priming animation.
     /// Constructed at component init from the sprite and sound.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
+    [ViewVariables]
     public Animation PrimingAnimation = default!;
 }
similarity index 91%
rename from Content.Client/Explosion/TriggerSystem.Proximity.cs
rename to Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs
index 03e743697148e534d6d520f9d0b940a69a6ad1f2..7954399505166d5ca53f4014e4b7ade80e68b5b1 100644 (file)
@@ -1,11 +1,12 @@
 using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Triggers;
 using Robust.Client.Animations;
 using Robust.Client.GameObjects;
 using Robust.Shared.Animations;
 
-namespace Content.Client.Explosion;
+namespace Content.Client.Trigger.Systems;
 
-public sealed partial class TriggerSystem
+public sealed class ProximityTriggerAnimationSystem : EntitySystem
 {
     [Dependency] private readonly AnimationPlayerSystem _player = default!;
     [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
@@ -18,7 +19,7 @@ public sealed partial class TriggerSystem
 
     private const string AnimKey = "proximity";
 
-    private static readonly Animation _flasherAnimation = new Animation
+    private static readonly Animation FlasherAnimation = new Animation
     {
         Length = TimeSpan.FromSeconds(0.6f),
         AnimationTracks = {
@@ -42,8 +43,10 @@ public sealed partial class TriggerSystem
         }
     };
 
-    private void InitializeProximity()
+    public override void Initialize()
     {
+        base.Initialize();
+
         SubscribeLocalEvent<TriggerOnProximityComponent, ComponentInit>(OnProximityInit);
         SubscribeLocalEvent<TriggerOnProximityComponent, AppearanceChangeEvent>(OnProxAppChange);
         SubscribeLocalEvent<TriggerOnProximityComponent, AnimationCompletedEvent>(OnProxAnimation);
@@ -94,7 +97,7 @@ public sealed partial class TriggerSystem
                 break;
             case ProximityTriggerVisuals.Active:
                 if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
-                _player.Play((uid, player), _flasherAnimation, AnimKey);
+                _player.Play((uid, player), FlasherAnimation, AnimKey);
                 break;
             case ProximityTriggerVisuals.Off:
             default:
diff --git a/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs b/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..a183282
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Client.Trigger.Systems;
+
+public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem;
similarity index 80%
rename from Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
rename to Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs
index b3d85f2017acdb7e732b7c3a299a68652cf3d41d..7c977c758972c95df9dd4cad5e3c569b97c21b4e 100644 (file)
@@ -1,11 +1,10 @@
+using Content.Client.Trigger.Components;
 using Content.Shared.Trigger;
 using Robust.Client.Animations;
 using Robust.Client.GameObjects;
-using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
-using Robust.Shared.GameObjects;
 
-namespace Content.Client.Trigger;
+namespace Content.Client.Trigger.Systems;
 
 public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTriggerVisualsComponent>
 {
@@ -17,25 +16,26 @@ public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTrigger
         SubscribeLocalEvent<TimerTriggerVisualsComponent, ComponentInit>(OnComponentInit);
     }
 
-    private void OnComponentInit(EntityUid uid, TimerTriggerVisualsComponent comp, ComponentInit args)
+    private void OnComponentInit(Entity<TimerTriggerVisualsComponent> ent, ref ComponentInit args)
     {
-        comp.PrimingAnimation = new Animation
+        ent.Comp.PrimingAnimation = new Animation
         {
             Length = TimeSpan.MaxValue,
             AnimationTracks = {
-                new AnimationTrackSpriteFlick() {
+                new AnimationTrackSpriteFlick()
+                {
                     LayerKey = TriggerVisualLayers.Base,
-                    KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.PrimingSprite, 0f) }
+                    KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.PrimingSprite, 0f) }
                 }
             },
         };
 
-        if (comp.PrimingSound != null)
+        if (ent.Comp.PrimingSound != null)
         {
-            comp.PrimingAnimation.AnimationTracks.Add(
+            ent.Comp.PrimingAnimation.AnimationTracks.Add(
                 new AnimationTrackPlaySound()
                 {
-                    KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) }
+                    KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(ent.Comp.PrimingSound), 0) }
                 }
             );
         }
index 4db79373d38933495c08352826fbc120800c13ee..f5a15295fd61fcf79ccc990fffd9a0df34cba729 100644 (file)
@@ -1,6 +1,6 @@
 using Content.IntegrationTests.Tests.Interaction;
-using Content.Server.Explosion.Components;
-using Content.Shared.Explosion.Components;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Systems;
 using Robust.Shared.Containers;
 using Robust.Shared.GameObjects;
 
@@ -25,19 +25,19 @@ public sealed class ModularGrenadeTests : InteractionTest
         await InteractUsing(Cable);
 
         // Insert & remove trigger
-        AssertComp<OnUseTimerTriggerComponent>(false);
+        AssertComp<TimerTriggerComponent>(false);
         await InteractUsing(Trigger);
-        AssertComp<OnUseTimerTriggerComponent>();
+        AssertComp<TimerTriggerComponent>();
         await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false);
         await InteractUsing(Pry);
-        AssertComp<OnUseTimerTriggerComponent>(false);
+        AssertComp<TimerTriggerComponent>(false);
 
         // Trigger was dropped to floor, not deleted.
         await FindEntity(Trigger, LookupFlags.Uncontained);
 
         // Re-insert
         await InteractUsing(Trigger);
-        AssertComp<OnUseTimerTriggerComponent>();
+        AssertComp<TimerTriggerComponent>();
 
         // Insert & remove payload.
         await InteractUsing(Payload);
@@ -56,13 +56,14 @@ public sealed class ModularGrenadeTests : InteractionTest
         await Pickup();
         AssertComp<ActiveTimerTriggerComponent>(false);
         await UseInHand();
+        AssertComp<ActiveTimerTriggerComponent>(true);
 
         // So uhhh grenades in hands don't destroy themselves when exploding. Maybe that will be fixed eventually.
         await Drop();
 
         // Wait until grenade explodes
-        var timer = Comp<ActiveTimerTriggerComponent>();
-        while (timer.TimeRemaining >= 0)
+        var triggerSys = SEntMan.System<TriggerSystem>();
+        while (Target != null && triggerSys.GetRemainingTime(SEntMan.GetEntity(Target.Value))?.TotalSeconds >= 0.0)
         {
             await RunTicks(10);
         }
diff --git a/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs b/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs
deleted file mode 100644 (file)
index aa6c5ba..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Server.AlertLevel.Systems;
-
-namespace Content.Server.AlertLevel;
-/// <summary>
-/// This component is for changing the alert level of the station when triggered.
-/// </summary>
-[RegisterComponent, Access(typeof(AlertLevelChangeOnTriggerSystem))]
-public sealed partial class AlertLevelChangeOnTriggerComponent : Component
-{
-    ///<summary>
-    ///The alert level to change to when triggered.
-    ///</summary>
-    [DataField]
-    public string Level = "blue";
-
-    /// <summary>
-    ///Whether to play the sound when the alert level changes.
-    /// </summary>
-    [DataField]
-    public bool PlaySound = true;
-
-    /// <summary>
-    ///Whether to say the announcement when the alert level changes.
-    /// </summary>
-    [DataField]
-    public bool Announce = true;
-
-    /// <summary>
-    ///Force the alert change. This applies if the alert level is not selectable or not.
-    /// </summary>
-    [DataField]
-    public bool Force = false;
-}
index 56843094a1af3706959674220b32d70506e2dec1..a88957913f5a38f38413fd7e015b721bf1905a15 100644 (file)
@@ -5,13 +5,13 @@ using Content.Server.Animals.Components;
 using Content.Server.Mind;
 using Content.Server.Popups;
 using Content.Server.Radio;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
 using Content.Server.Vocalization.Systems;
 using Content.Shared.Animals.Components;
 using Content.Shared.Animals.Systems;
 using Content.Shared.Database;
 using Content.Shared.Mobs.Systems;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
 using Content.Shared.Whitelist;
 using Robust.Shared.Network;
 using Robust.Shared.Random;
diff --git a/Content.Server/Chat/SpeakOnTriggerComponent.cs b/Content.Server/Chat/SpeakOnTriggerComponent.cs
deleted file mode 100644 (file)
index d879cbf..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Shared.Dataset;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Chat;
-
-/// <summary>
-///     Makes the entity speak when triggered. If the item has UseDelay component, the system will respect that cooldown.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SpeakOnTriggerComponent : Component
-{
-    /// <summary>
-    ///     The identifier for the dataset prototype containing messages to be spoken by this entity.
-    /// </summary>
-    [DataField(required: true)]
-    public ProtoId<LocalizedDatasetPrototype> Pack = string.Empty;
-}
index 9ae50a8c97e0d44148fcdf2e97d30c5cf3c14214..dca2959f98ec68e7b9b0b73e545f9d392b4914e2 100644 (file)
@@ -67,6 +67,9 @@ public sealed class SuicideSystem : EntitySystem
         if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, CannotSuicideTag))
             return false;
 
+        // TODO: fix this
+        // This is a handled event, but the result is never used
+        // It looks like TriggerOnMobstateChange is supposed to prevent you from suiciding
         var suicideEvent = new SuicideEvent(victim);
         RaiseLocalEvent(victim, suicideEvent);
 
index 0b3a9c0a662b131b1e8b31d5d186e3fa0bb14c91..d49da57801472997932d5d78707c22ca62c29629 100644 (file)
@@ -60,11 +60,6 @@ public sealed partial class ChatSystem : SharedChatSystem
     [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
     [Dependency] private readonly ExamineSystemShared _examineSystem = default!;
 
-    public const int VoiceRange = 10; // how far voice goes in world units
-    public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
-    public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
-    public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg";
-
     private bool _loocEnabled = true;
     private bool _deadLoocEnabled;
     private bool _critLoocEnabled;
diff --git a/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs b/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs
deleted file mode 100644 (file)
index 8a0ee51..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Damage;
-using Content.Shared.Damage.Components;
-
-namespace Content.Server.Damage.Systems;
-
-// System for damage that occurs on specific trigger, towards the user..
-public sealed class DamageUserOnTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
-
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<DamageUserOnTriggerComponent, TriggerEvent>(OnTrigger);
-    }
-
-    private void OnTrigger(EntityUid uid, DamageUserOnTriggerComponent component, TriggerEvent args)
-    {
-        if (args.User is null)
-            return;
-
-        args.Handled |= OnDamageTrigger(uid, args.User.Value, component);
-    }
-
-    private bool OnDamageTrigger(EntityUid source, EntityUid target, DamageUserOnTriggerComponent? component = null)
-    {
-        if (!Resolve(source, ref component))
-        {
-            return false;
-        }
-
-        var damage = new DamageSpecifier(component.Damage);
-        var ev = new BeforeDamageUserOnTriggerEvent(damage, target);
-        RaiseLocalEvent(source, ev);
-
-        return _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: source) is not null;
-    }
-}
-
-public sealed class BeforeDamageUserOnTriggerEvent : EntityEventArgs
-{
-    public DamageSpecifier Damage { get; set;  }
-    public EntityUid Tripper { get; }
-
-    public BeforeDamageUserOnTriggerEvent(DamageSpecifier damage, EntityUid target)
-    {
-        Damage = damage;
-        Tripper = target;
-    }
-}
index 1e9caece9456792c8750e4e6b4cb9f22ed0cb518..5c589d21311efeeac178786246561b62a5febd23 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Defusable.Components;
-using Content.Server.Explosion.Components;
 using Content.Server.Explosion.EntitySystems;
 using Content.Server.Popups;
 using Content.Server.Wires;
@@ -8,13 +7,13 @@ using Content.Shared.Construction.Components;
 using Content.Shared.Database;
 using Content.Shared.Defusable;
 using Content.Shared.Examine;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Explosion.Components.OnTrigger;
 using Content.Shared.Popups;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.Trigger.Systems;
 using Content.Shared.Verbs;
 using Content.Shared.Wires;
 using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
 using Robust.Shared.Audio.Systems;
 
 namespace Content.Server.Defusable.Systems;
@@ -74,12 +73,13 @@ public sealed class DefusableSystem : SharedDefusableSystem
             {
                 args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
             }
-            else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
+            else if (comp.Activated)
             {
-                if (comp.DisplayTime)
+                var remaining = _trigger.GetRemainingTime(uid);
+                if (comp.DisplayTime && remaining != null)
                 {
                     args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
-                        ("time", MathF.Floor(activeComp.TimeRemaining))));
+                        ("time", Math.Floor(remaining.Value.TotalSeconds))));
                 }
                 else
                 {
@@ -139,16 +139,9 @@ public sealed class DefusableSystem : SharedDefusableSystem
         SetActivated(comp, true);
 
         _popup.PopupEntity(Loc.GetString("defusable-popup-begun", ("name", uid)), uid);
-        if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
+        if (TryComp<TimerTriggerComponent>(uid, out var timerTrigger))
         {
-            _trigger.HandleTimerTrigger(
-                uid,
-                user,
-                timerTrigger.Delay,
-                timerTrigger.BeepInterval,
-                timerTrigger.InitialBeepDelay,
-                timerTrigger.BeepSound
-            );
+            _trigger.ActivateTimerTrigger((uid, timerTrigger));
         }
 
         RaiseLocalEvent(uid, new BombArmedEvent(uid));
@@ -168,7 +161,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
 
         RaiseLocalEvent(uid, new BombDetonatedEvent(uid));
 
-        _explosion.TriggerExplosive(uid, user:detonator);
+        _explosion.TriggerExplosive(uid, user: detonator);
         QueueDel(uid);
 
         _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
@@ -188,7 +181,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
         {
             SetUsable(comp, false);
             RemComp<ExplodeOnTriggerComponent>(uid);
-            RemComp<OnUseTimerTriggerComponent>(uid);
+            RemComp<TimerTriggerComponent>(uid);
         }
         RemComp<ActiveTimerTriggerComponent>(uid);
 
@@ -246,7 +239,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
         if (comp is not { Activated: true, DelayWireUsed: false })
             return;
 
-        _trigger.TryDelay(wire.Owner, 30f);
+        _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(30));
         _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
         comp.DelayWireUsed = true;
     }
@@ -268,7 +261,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
         if (comp is { Activated: true, ProceedWireUsed: false })
         {
             comp.ProceedWireUsed = true;
-            _trigger.TryDelay(wire.Owner, -15f);
+            _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(-15));
         }
 
         _popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
@@ -298,7 +291,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
         {
             if (!comp.ActivatedWireUsed)
             {
-                _trigger.TryDelay(wire.Owner, 30f);
+                _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(30));
                 _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
                 comp.ActivatedWireUsed = true;
             }
index 82d5ffcb9acdd977bd3e3796ed86c470305b2594..b4a79c6830bd84766f94bfdcfb6cd969bfdd7357 100644 (file)
@@ -1,4 +1,5 @@
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using Content.Server.Administration.Logs;
 using Content.Server.Atmos.EntitySystems;
 using Content.Server.Body.Systems;
@@ -14,15 +15,13 @@ using Content.Shared.Damage;
 using Content.Shared.Database;
 using Content.Shared.Destructible;
 using Content.Shared.FixedPoint;
+using Content.Shared.Humanoid;
+using Content.Shared.Trigger.Systems;
 using JetBrains.Annotations;
 using Robust.Server.Audio;
-using Robust.Server.GameObjects;
 using Robust.Shared.Containers;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
-using System.Linq;
-using Content.Shared.Humanoid;
-using Robust.Shared.Player;
 
 namespace Content.Server.Destructible
 {
index 97a5f8b7ef5e8698c1b459aa11ccdc68860f3443..cf337ac76a7f92b75f10a8403169849cd5b1d7e1 100644 (file)
@@ -5,6 +5,6 @@ public sealed partial class TimerStartBehavior : IThresholdBehavior
 {
     public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
     {
-        system.TriggerSystem.StartTimer(owner, cause);
+        system.TriggerSystem.ActivateTimerTrigger(owner, cause);
     }
 }
index 03bdb8ff6962ee997f84a8603d65b7b5630620f2..66da79063cc7a84b69309fe5857f831c130fa2b4 100644 (file)
@@ -1,10 +1,18 @@
-namespace Content.Server.Destructible.Thresholds.Behaviors;
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Server.Destructible.Thresholds.Behaviors;
 
 [DataDefinition]
 public sealed partial class TriggerBehavior : IThresholdBehavior
 {
+    /// <summary>
+    /// The trigger key to use when triggering.
+    /// </summary>
+    [DataField]
+    public string? KeyOut { get; set; } = TriggerSystem.DefaultTriggerKey;
+
     public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
     {
-        system.TriggerSystem.Trigger(owner, cause);
+        system.TriggerSystem.Trigger(owner, cause, KeyOut);
     }
 }
index d957f0171e71fbc9a1d04672c8e47fcc87f1d7bb..ff20ac4d8d85da70aa3de632aaaaab5e5d316dfc 100644 (file)
@@ -1,4 +1,6 @@
 using Content.Server.DeviceLinking.Components;
+using Content.Server.DeviceNetwork;
+using Content.Server.DeviceNetwork.Components;
 using Content.Server.DeviceNetwork.Systems;
 using Content.Shared.DeviceLinking;
 using Content.Shared.DeviceLinking.Events;
index 45e4d2175070699ad6270f9cf03552ad884e678f..7743a97d72dc9716f97418456ca80ed352d0cbf1 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.DeviceLinking.Components;
-using Content.Server.DeviceNetwork;
 using Content.Shared.DeviceLinking;
 using Content.Shared.DeviceLinking.Events;
 using Content.Shared.DeviceNetwork;
index a5091508ed7c1e5ab5b516c440a47a1b9e61d383..7d684d1cd5935ae65b8eaaba7b617ce1719d90cc 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Server.Administration.Logs;
 using Content.Server.DeviceLinking.Components;
-using Content.Server.Explosion.EntitySystems;
 using Content.Shared.Database;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Timing;
@@ -10,7 +9,6 @@ namespace Content.Server.DeviceLinking.Systems;
 public sealed class SignallerSystem : EntitySystem
 {
     [Dependency] private readonly DeviceLinkSystem _link = default!;
-    [Dependency] private readonly UseDelaySystem _useDelay = default!;
     [Dependency] private readonly IAdminLogManager _adminLogger = default!;
 
     public override void Initialize()
@@ -19,7 +17,6 @@ public sealed class SignallerSystem : EntitySystem
 
         SubscribeLocalEvent<SignallerComponent, ComponentInit>(OnInit);
         SubscribeLocalEvent<SignallerComponent, UseInHandEvent>(OnUseInHand);
-        SubscribeLocalEvent<SignallerComponent, TriggerEvent>(OnTrigger);
     }
 
     private void OnInit(EntityUid uid, SignallerComponent component, ComponentInit args)
@@ -36,16 +33,4 @@ public sealed class SignallerSystem : EntitySystem
         _link.InvokePort(uid, component.Port);
         args.Handled = true;
     }
-
-    private void OnTrigger(EntityUid uid, SignallerComponent component, TriggerEvent args)
-    {
-        if (!TryComp(uid, out UseDelayComponent? useDelay)
-            // if on cooldown, do nothing
-            // and set cooldown to prevent clocks
-            || !_useDelay.TryResetDelay((uid, useDelay), true))
-            return;
-
-        _link.InvokePort(uid, component.Port);
-        args.Handled = true;
-    }
 }
index bb9e71cfc333992266ea8aa184b0aae7f07bc833..05dfffa4a90fe1e9f2063c325cbaa76211049c54 100644 (file)
@@ -58,7 +58,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
     [Dependency] private readonly MetaDataSystem _metaData = default!;
     [Dependency] private readonly TurfSystem _turf = default!;
 
-    private static readonly ProtoId<StatusEffectPrototype> StatusEffectKey = "Electrocution";
+    private static readonly ProtoId<StatusEffectPrototype> StatusKeyIn = "Electrocution";
     private static readonly ProtoId<DamageTypePrototype> DamageType = "Shock";
     private static readonly ProtoId<TagPrototype> WindowTag = "Window";
 
@@ -386,12 +386,12 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
         }
 
         if (!Resolve(uid, ref statusEffects, false) ||
-            !_statusEffects.CanApplyEffect(uid, StatusEffectKey, statusEffects))
+            !_statusEffects.CanApplyEffect(uid, StatusKeyIn, statusEffects))
         {
             return false;
         }
 
-        if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh, statusEffects))
+        if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusKeyIn, time, refresh, statusEffects))
             return false;
 
         var shouldStun = siemensCoefficient > 0.5f;
diff --git a/Content.Server/Emp/EmpOnTriggerComponent.cs b/Content.Server/Emp/EmpOnTriggerComponent.cs
deleted file mode 100644 (file)
index fac509e..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Content.Server.Emp;
-
-/// <summary>
-/// Upon being triggered will EMP area around it.
-/// </summary>
-[RegisterComponent]
-[Access(typeof(EmpSystem))]
-public sealed partial class EmpOnTriggerComponent : Component
-{
-    [DataField("range"), ViewVariables(VVAccess.ReadWrite)]
-    public float Range = 1.0f;
-
-    /// <summary>
-    /// How much energy will be consumed per battery in range
-    /// </summary>
-    [DataField("energyConsumption"), ViewVariables(VVAccess.ReadWrite)]
-    public float EnergyConsumption;
-
-    /// <summary>
-    /// How long it disables targets in seconds
-    /// </summary>
-    [DataField("disableDuration"), ViewVariables(VVAccess.ReadWrite)]
-    public float DisableDuration = 60f;
-}
index 4b5143aa40027605d5b09e4e1a3f9fa99bdfc714..b8bfc63afefd3c373e4024dfcfc483c1c723ed29 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.Explosion.EntitySystems;
 using Content.Server.Power.EntitySystems;
 using Content.Server.Radio;
 using Content.Server.SurveillanceCamera;
@@ -20,7 +19,6 @@ public sealed class EmpSystem : SharedEmpSystem
     {
         base.Initialize();
         SubscribeLocalEvent<EmpDisabledComponent, ExaminedEvent>(OnExamine);
-        SubscribeLocalEvent<EmpOnTriggerComponent, TriggerEvent>(HandleEmpTrigger);
 
         SubscribeLocalEvent<EmpDisabledComponent, RadioSendAttemptEvent>(OnRadioSendAttempt);
         SubscribeLocalEvent<EmpDisabledComponent, RadioReceiveAttemptEvent>(OnRadioReceiveAttempt);
@@ -28,14 +26,7 @@ public sealed class EmpSystem : SharedEmpSystem
         SubscribeLocalEvent<EmpDisabledComponent, SurveillanceCameraSetActiveAttemptEvent>(OnCameraSetActive);
     }
 
-    /// <summary>
-    ///   Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
-    /// </summary>
-    /// <param name="coordinates">The location to trigger the EMP pulse at.</param>
-    /// <param name="range">The range of the EMP pulse.</param>
-    /// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
-    /// <param name="duration">The duration of the EMP effects.</param>
-    public void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
+    public override void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
     {
         foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
         {
@@ -118,12 +109,6 @@ public sealed class EmpSystem : SharedEmpSystem
         args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine"));
     }
 
-    private void HandleEmpTrigger(EntityUid uid, EmpOnTriggerComponent comp, TriggerEvent args)
-    {
-        EmpPulse(_transform.GetMapCoordinates(uid), comp.Range, comp.EnergyConsumption, comp.DisableDuration);
-        args.Handled = true;
-    }
-
     private void OnRadioSendAttempt(EntityUid uid, EmpDisabledComponent component, ref RadioSendAttemptEvent args)
     {
         args.Cancelled = true;
diff --git a/Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs b/Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs
deleted file mode 100644 (file)
index 9d80734..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-[RegisterComponent]
-public sealed partial class ActiveTriggerOnTimedCollideComponent : Component { }
diff --git a/Content.Server/Explosion/Components/AutomatedTimerComponent.cs b/Content.Server/Explosion/Components/AutomatedTimerComponent.cs
deleted file mode 100644 (file)
index c01aeb9..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-///     Disallows starting the timer by hand, must be stuck or triggered by a system using <c>StartTimer</c>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class AutomatedTimerComponent : Component
-{
-}
diff --git a/Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs
deleted file mode 100644 (file)
index d83b57c..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Will anchor the attached entity upon a <see cref="TriggerEvent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class AnchorOnTriggerComponent : Component
-{
-    [DataField("removeOnTrigger")]
-    public bool RemoveOnTrigger = true;
-}
diff --git a/Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs
deleted file mode 100644 (file)
index 9846cad..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Will delete the attached entity upon a <see cref="TriggerEvent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class DeleteOnTriggerComponent : Component
-{
-}
diff --git a/Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs
deleted file mode 100644 (file)
index da58467..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Gibs on trigger, self explanatory.
-/// Also in case of an implant using this, gibs the implant user instead.
-/// </summary>
-[RegisterComponent]
-public sealed partial class GibOnTriggerComponent : Component
-{
-    /// <summary>
-    /// Should gibbing also delete the owners items?
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("deleteItems")]
-    public bool DeleteItems = false;
-}
diff --git a/Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs
deleted file mode 100644 (file)
index 7f9f16a..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Audio;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Will play sound from the attached entity upon a <see cref="TriggerEvent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SoundOnTriggerComponent : Component
-{
-    [DataField("removeOnTrigger")]
-    public bool RemoveOnTrigger = true;
-
-    [DataField("sound")]
-    public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Effects/Grenades/supermatter_start.ogg");
-}
diff --git a/Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs b/Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs
deleted file mode 100644 (file)
index a63d6fc..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Explosion.Components.OnTrigger;
-
-/// <summary>
-/// After being triggered applies the specified components and runs triggers again.
-/// </summary>
-[RegisterComponent, AutoGenerateComponentPause]
-public sealed partial class TwoStageTriggerComponent : Component
-{
-    /// <summary>
-    /// How long it takes for the second stage to be triggered.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("triggerDelay")]
-    public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10);
-
-    /// <summary>
-    /// This list of components that will be added for the second trigger.
-    /// </summary>
-    [DataField("components", required: true)]
-    public ComponentRegistry SecondStageComponents = new();
-
-    [DataField("nextTriggerTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
-    [AutoPausedField]
-    public TimeSpan? NextTriggerTime;
-
-    [DataField("triggered")]
-    public bool Triggered = false;
-
-    [DataField("ComponentsIsLoaded")]
-    public bool ComponentsIsLoaded = false;
-}
index 58d687e02588b1769f6d35e7a475695231c09abe..20a73b46b52a3805805f15f787e024babbf789e8 100644 (file)
@@ -45,4 +45,10 @@ public sealed partial class ProjectileGrenadeComponent : Component
     /// </summary>
     [DataField]
     public float MaxVelocity = 6f;
+
+    /// <summary>
+    /// The trigger key that will activate the grenade.
+    /// </summary>
+    [DataField]
+    public string TriggerKey = "timer";
 }
diff --git a/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs b/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs
deleted file mode 100644 (file)
index a553cc0..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// A component that electrocutes an entity having this component when a trigger is triggered.
-/// </summary>
-[RegisterComponent, AutoGenerateComponentPause]
-[Access(typeof(TriggerSystem))]
-public sealed partial class ShockOnTriggerComponent : Component
-{
-    /// <summary>
-    /// The force of an electric shock when the trigger is triggered.
-    /// </summary>
-    [DataField]
-    public int Damage = 5;
-
-    /// <summary>
-    /// Duration of electric shock when the trigger is triggered.
-    /// </summary>
-    [DataField]
-    public TimeSpan Duration = TimeSpan.FromSeconds(2);
-
-    /// <summary>
-    /// The minimum delay between repeating triggers.
-    /// </summary>
-    [DataField]
-    public TimeSpan Cooldown = TimeSpan.FromSeconds(4);
-
-    /// <summary>
-    /// When can the trigger run again?
-    /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
-    [AutoPausedField]
-    public TimeSpan NextTrigger = TimeSpan.Zero;
-}
diff --git a/Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs b/Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs
deleted file mode 100644 (file)
index c28ec7f..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-///     Spawns a protoype when triggered.
-/// </summary>
-[RegisterComponent, Access(typeof(TriggerSystem))]
-public sealed partial class SpawnOnTriggerComponent : Component
-{
-    /// <summary>
-    ///     The prototype to spawn.
-    /// </summary>
-    [DataField(required: true)]
-    public EntProtoId Proto = string.Empty;
-
-    /// <summary>
-    ///     Use MapCoordinates for spawning?
-    ///     Set to true if you don't want the new entity parented to the spawner.
-    /// </summary>
-    [DataField]
-    public bool mapCoords;
-}
diff --git a/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs b/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs
deleted file mode 100644 (file)
index 9adc6da..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Explosion.Components
-{
-    /// <summary>
-    /// Sends a trigger when signal is received.
-    /// </summary>
-    [RegisterComponent]
-    public sealed partial class TimerStartOnSignalComponent : Component
-    {
-        [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
-        public string Port = "Timer";
-    }
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs b/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs
deleted file mode 100644 (file)
index 1531c74..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers on click.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnActivateComponent : Component { }
diff --git a/Content.Server/Explosion/Components/TriggerOnCollideComponent.cs b/Content.Server/Explosion/Components/TriggerOnCollideComponent.cs
deleted file mode 100644 (file)
index 28c2ed8..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-///     Triggers when colliding with another entity.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnCollideComponent : Component
-{
-    /// <summary>
-    ///     The fixture with which to collide.
-    /// </summary>
-    [DataField(required: true)]
-    public string FixtureID = string.Empty;
-
-    /// <summary>
-    ///     Doesn't trigger if the other colliding fixture is nonhard.
-    /// </summary>
-    [DataField]
-    public bool IgnoreOtherNonHard = true;
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs b/Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs
deleted file mode 100644 (file)
index a6cdda2..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.Mobs;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Use where you want something to trigger on mobstate change
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnMobstateChangeComponent : Component
-{
-    /// <summary>
-    /// What state should trigger this?
-    /// </summary>
-    [ViewVariables]
-    [DataField("mobState", required: true)]
-    public List<MobState> MobState = new();
-
-    /// <summary>
-    /// If true, prevents suicide attempts for the trigger to prevent cheese.
-    /// </summary>
-    [ViewVariables]
-    [DataField("preventSuicide")]
-    public bool PreventSuicide = false;
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs b/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs
deleted file mode 100644 (file)
index 4f3fb47..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Explosion;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Physics;
-using Robust.Shared.Physics.Collision.Shapes;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Explosion.Components
-{
-
-    /// <summary>
-    /// Raises a <see cref="TriggerEvent"/> whenever an entity collides with a fixture attached to the owner of this component.
-    /// </summary>
-    [RegisterComponent, AutoGenerateComponentPause]
-    public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent
-    {
-        public const string FixtureID = "trigger-on-proximity-fixture";
-
-        [ViewVariables]
-        public readonly Dictionary<EntityUid, PhysicsComponent> Colliding = new();
-
-        /// <summary>
-        /// What is the shape of the proximity fixture?
-        /// </summary>
-        [ViewVariables]
-        [DataField("shape")]
-        public IPhysShape Shape = new PhysShapeCircle(2f);
-
-        /// <summary>
-        /// How long the the proximity trigger animation plays for.
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("animationDuration")]
-        public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
-
-        /// <summary>
-        /// Whether the entity needs to be anchored for the proximity to work.
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("requiresAnchored")]
-        public bool RequiresAnchored = true;
-
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("enabled")]
-        public bool Enabled = true;
-
-        /// <summary>
-        /// The minimum delay between repeating triggers.
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("cooldown")]
-        public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
-
-        /// <summary>
-        /// When can the trigger run again?
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("nextTrigger", customTypeSerializer: typeof(TimeOffsetSerializer))]
-        [AutoPausedField]
-        public TimeSpan NextTrigger = TimeSpan.Zero;
-
-        /// <summary>
-        /// When will the visual state be updated again after activation?
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("nextVisualUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
-        [AutoPausedField]
-        public TimeSpan NextVisualUpdate = TimeSpan.Zero;
-
-        /// <summary>
-        /// What speed should the other object be moving at to trigger the proximity fixture?
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("triggerSpeed")]
-        public float TriggerSpeed = 3.5f;
-
-        /// <summary>
-        /// If this proximity is triggered should we continually repeat it?
-        /// </summary>
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("repeating")]
-        public bool Repeating = true;
-
-        /// <summary>
-        /// What layer is the trigger fixture on?
-        /// </summary>
-        [ViewVariables]
-        [DataField("layer", customTypeSerializer: typeof(FlagSerializer<CollisionLayer>))]
-        public int Layer = (int) (CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable);
-    }
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnSignalComponent.cs b/Content.Server/Explosion/Components/TriggerOnSignalComponent.cs
deleted file mode 100644 (file)
index fb74956..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Explosion.Components
-{
-    /// <summary>
-    /// Sends a trigger when signal is received.
-    /// </summary>
-    [RegisterComponent]
-    public sealed partial class TriggerOnSignalComponent : Component
-    {
-        [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
-        public string Port = "Trigger";
-    }
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs b/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs
deleted file mode 100644 (file)
index 16632b9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-[RegisterComponent]
-public sealed partial class TriggerOnSlipComponent : Component
-{
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs b/Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs
deleted file mode 100644 (file)
index 704ff41..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// calls the trigger when the object is initialized
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnSpawnComponent : Component
-{
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs b/Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs
deleted file mode 100644 (file)
index 49e99ff..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// This is used for entities that want the more generic 'trigger' behavior after a step trigger occurs.
-/// Not done by default, since it's not useful for everything and might cause weird behavior. But it is useful for a lot of stuff like mousetraps.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnStepTriggerComponent : Component
-{
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs b/Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs
deleted file mode 100644 (file)
index d53bcbc..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers when the entity is overlapped for the specified duration.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnTimedCollideComponent : Component
-{
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("threshold")]
-    public float Threshold;
-
-    /// <summary>
-    /// A collection of entities that are colliding with this, and their own unique accumulator.
-    /// </summary>
-    [ViewVariables]
-    public readonly Dictionary<EntityUid, float> Colliding = new();
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnUseComponent.cs b/Content.Server/Explosion/Components/TriggerOnUseComponent.cs
deleted file mode 100644 (file)
index 2b44d2f..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers on use in hand.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnUseComponent : Component { }
diff --git a/Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs b/Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs
deleted file mode 100644 (file)
index 7d07f49..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Content.Server.Explosion.Components
-{
-    /// <summary>
-    /// Sends a trigger when the keyphrase is heard
-    /// </summary>
-    [RegisterComponent]
-    public sealed partial class TriggerOnVoiceComponent : Component
-    {
-        public bool IsListening => IsRecording || !string.IsNullOrWhiteSpace(KeyPhrase);
-
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("keyPhrase")]
-        public string? KeyPhrase;
-
-        [ViewVariables(VVAccess.ReadWrite)]
-        [DataField("listenRange")]
-        public int ListenRange { get; private set; } = 4;
-
-        [DataField("isRecording")]
-        public bool IsRecording = false;
-
-        [DataField("minLength")]
-        public int MinLength = 3;
-
-        [DataField("maxLength")]
-        public int MaxLength = 50;
-    }
-}
diff --git a/Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs b/Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs
deleted file mode 100644 (file)
index ad39b9c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers a gun when attempting to shoot while it's empty
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerWhenEmptyComponent : Component
-{
-}
diff --git a/Content.Server/Explosion/Components/TriggerWhitelistComponent.cs b/Content.Server/Explosion/Components/TriggerWhitelistComponent.cs
deleted file mode 100644 (file)
index 80becf1..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using Content.Shared.Whitelist;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Checks if the user of a Trigger satisfies a whitelist and blacklist condition.
-/// Cancels the trigger otherwise.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerWhitelistComponent : Component
-{
-    /// <summary>
-    /// Whitelist for what entites can cause this trigger.
-    /// </summary>
-    [DataField]
-    public EntityWhitelist? Whitelist;
-
-    /// <summary>
-    /// Blacklist for what entites can cause this trigger.
-    /// </summary>
-    [DataField]
-    public EntityWhitelist? Blacklist;
-}
index 6953010e46d0dfae00437752f647a93c991a2175..4b93f22cd12bcb0e8e15e39ce76a0fd287db1d0f 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Explosion.Components;
 using Content.Server.Weapons.Ranged.Systems;
+using Content.Shared.Trigger;
 using Robust.Server.GameObjects;
 using Robust.Shared.Containers;
 using Robust.Shared.Map;
@@ -45,6 +46,9 @@ public sealed class ProjectileGrenadeSystem : EntitySystem
     /// </summary>
     private void OnFragTrigger(Entity<ProjectileGrenadeComponent> entity, ref TriggerEvent args)
     {
+        if (args.Key != entity.Comp.TriggerKey)
+            return;
+
         FragmentIntoProjectiles(entity.Owner, entity.Comp);
         args.Handled = true;
     }
diff --git a/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs
deleted file mode 100644 (file)
index ae3f84f..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-using Content.Server.Atmos.EntitySystems;
-using Content.Shared.Explosion.Components.OnTrigger;
-using Content.Shared.Explosion.EntitySystems;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-/// <summary>
-/// Releases a gas mixture to the atmosphere when triggered.
-/// Can also release gas over a set timespan to prevent trolling people
-/// with the instant-wall-of-pressure-inator.
-/// </summary>
-public sealed partial class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem
-{
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
-    [Dependency] private readonly IGameTiming _timing = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<ReleaseGasOnTriggerComponent, TriggerEvent>(OnTrigger);
-    }
-
-    /// <summary>
-    /// Shrimply sets the component to active when triggered, allowing it to release over time.
-    /// </summary>
-    private void OnTrigger(Entity<ReleaseGasOnTriggerComponent> ent, ref TriggerEvent args)
-    {
-        ent.Comp.Active = true;
-        ent.Comp.NextReleaseTime = _timing.CurTime;
-        ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles;
-        UpdateAppearance(ent.Owner, true);
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        var curTime = _timing.CurTime;
-        var query = EntityQueryEnumerator<ReleaseGasOnTriggerComponent>();
-
-        while (query.MoveNext(out var uid, out var comp))
-        {
-            if (!comp.Active || comp.NextReleaseTime > curTime)
-                continue;
-
-            var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction);
-            var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
-
-            if (environment == null)
-            {
-                UpdateAppearance(uid, false);
-                RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
-                continue;
-            }
-
-            _atmosphereSystem.Merge(environment, giverGasMix);
-            comp.NextReleaseTime += comp.ReleaseInterval;
-
-            if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit ||
-                comp.Air.TotalMoles <= 0)
-            {
-                UpdateAppearance(uid, false);
-                RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
-                continue;
-            }
-        }
-    }
-
-    private void UpdateAppearance(Entity<AppearanceComponent?> entity, bool state)
-    {
-        if (!Resolve(entity, ref entity.Comp, false))
-            return;
-
-        _appearance.SetData(entity, ReleaseGasOnTriggerVisuals.Key, state);
-    }
-}
diff --git a/Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs
deleted file mode 100644 (file)
index 9e595c5..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-using Content.Shared.Explosion.Components.OnTrigger;
-using Content.Shared.Explosion.EntitySystems;
-using Content.Shared.RepulseAttract;
-using Content.Shared.Timing;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed class RepulseAttractOnTriggerSystem : SharedRepulseAttractOnTriggerSystem
-{
-    [Dependency] private readonly RepulseAttractSystem _repulse = default!;
-    [Dependency] private readonly SharedTransformSystem _transform = default!;
-    [Dependency] private readonly UseDelaySystem _delay = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<SharedRepulseAttractOnTriggerComponent, TriggerEvent>(OnTrigger);
-    }
-
-    private void OnTrigger(Entity<SharedRepulseAttractOnTriggerComponent> ent, ref TriggerEvent args)
-    {
-        if (_delay.IsDelayed(ent.Owner))
-            return;
-
-        var position = _transform.GetMapCoordinates(ent);
-        _repulse.TryRepulseAttract(position, args.User, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask);
-    }
-}
index 2657ba344945446b52da65a07b33e333063a3e53..5f7df28beb2ceac363a809b1dc301ff710aa0694 100644 (file)
@@ -1,5 +1,8 @@
 using Content.Shared.Explosion.Components;
 using Content.Shared.Throwing;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Systems;
+using Content.Shared.Trigger.Components;
 using Robust.Server.GameObjects;
 using Robust.Shared.Containers;
 using Robust.Shared.Map;
@@ -15,6 +18,7 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
     [Dependency] private readonly TransformSystem _transformSystem = default!;
+    [Dependency] private readonly TriggerSystem _trigger = default!;
 
     public override void Initialize()
     {
@@ -30,6 +34,9 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
     /// </summary>
     private void OnScatteringTrigger(Entity<ScatteringGrenadeComponent> entity, ref TriggerEvent args)
     {
+        if (args.Key != entity.Comp.TriggerKey)
+            return;
+
         entity.Comp.IsTriggered = true;
         args.Handled = true;
     }
@@ -76,13 +83,12 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
 
                     _throwingSystem.TryThrow(contentUid, direction, component.Velocity);
 
-                    if (component.TriggerContents)
+                    if (component.TriggerContents && TryComp<TimerTriggerComponent>(contentUid, out var contentTimer))
                     {
                         additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax);
-                        var contentTimer = EnsureComp<ActiveTimerTriggerComponent>(contentUid);
-                        contentTimer.TimeRemaining = component.DelayBeforeTriggerContents + additionalIntervalDelay;
-                        var ev = new ActiveTimerTriggerEvent(contentUid, uid);
-                        RaiseLocalEvent(contentUid, ref ev);
+
+                        _trigger.SetDelay((contentUid, contentTimer), TimeSpan.FromSeconds(component.DelayBeforeTriggerContents + additionalIntervalDelay));
+                        _trigger.ActivateTimerTrigger((contentUid, contentTimer));
                     }
                 }
 
diff --git a/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs
deleted file mode 100644 (file)
index 19335d3..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-using Content.Shared.Explosion.Components;
-using Content.Shared.Explosion.EntitySystems;
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Spreader;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Coordinates.Helpers;
-using Content.Shared.Maps;
-using Robust.Server.GameObjects;
-using Robust.Shared.Map;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-/// <summary>
-/// Handles creating smoke when <see cref="SmokeOnTriggerComponent"/> is triggered.
-/// </summary>
-public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
-{
-    [Dependency] private readonly IMapManager _mapMan = default!;
-    [Dependency] private readonly SharedMapSystem _map = default!;
-    [Dependency] private readonly SmokeSystem _smoke = default!;
-    [Dependency] private readonly TransformSystem _transform = default!;
-    [Dependency] private readonly SpreaderSystem _spreader = default!;
-    [Dependency] private readonly TurfSystem _turf = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<SmokeOnTriggerComponent, TriggerEvent>(OnTrigger);
-    }
-
-    private void OnTrigger(EntityUid uid, SmokeOnTriggerComponent comp, TriggerEvent args)
-    {
-        var xform = Transform(uid);
-        var mapCoords = _transform.GetMapCoordinates(uid, xform);
-        if (!_mapMan.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
-            !_map.TryGetTileRef(gridUid, grid, xform.Coordinates, out var tileRef) ||
-            tileRef.Tile.IsEmpty)
-        {
-            return;
-        }
-
-        if (_spreader.RequiresFloorToSpread(comp.SmokePrototype.ToString()) && _turf.IsSpace(tileRef))
-            return;
-
-        var coords = _map.MapToGrid(gridUid, mapCoords);
-        var ent = Spawn(comp.SmokePrototype, coords.SnapToGrid());
-        if (!TryComp<SmokeComponent>(ent, out var smoke))
-        {
-            Log.Error($"Smoke prototype {comp.SmokePrototype} was missing SmokeComponent");
-            Del(ent);
-            return;
-        }
-
-        _smoke.StartSmoke(ent, comp.Solution, comp.Duration, comp.SpreadAmount, smoke);
-    }
-}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs
deleted file mode 100644 (file)
index d06e9fa..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-using Content.Server.Explosion.Components;
-using Content.Shared.Examine;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Popups;
-using Content.Shared.Sticky;
-using Content.Shared.Verbs;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed partial class TriggerSystem
-{
-    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
-
-    private void InitializeOnUse()
-    {
-        SubscribeLocalEvent<OnUseTimerTriggerComponent, UseInHandEvent>(OnTimerUse);
-        SubscribeLocalEvent<OnUseTimerTriggerComponent, ExaminedEvent>(OnExamined);
-        SubscribeLocalEvent<OnUseTimerTriggerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
-        SubscribeLocalEvent<OnUseTimerTriggerComponent, EntityStuckEvent>(OnStuck);
-        SubscribeLocalEvent<RandomTimerTriggerComponent, MapInitEvent>(OnRandomTimerTriggerMapInit);
-    }
-
-    private void OnStuck(EntityUid uid, OnUseTimerTriggerComponent component, ref EntityStuckEvent args)
-    {
-        if (!component.StartOnStick)
-            return;
-
-        StartTimer((uid, component), args.User);
-    }
-
-    private void OnExamined(EntityUid uid, OnUseTimerTriggerComponent component, ExaminedEvent args)
-    {
-        if (args.IsInDetailsRange && component.Examinable)
-            args.PushText(Loc.GetString("examine-trigger-timer", ("time", component.Delay)));
-    }
-
-    /// <summary>
-    ///     Add an alt-click interaction that cycles through delays.
-    /// </summary>
-    private void OnGetAltVerbs(EntityUid uid, OnUseTimerTriggerComponent component, GetVerbsEvent<AlternativeVerb> args)
-    {
-        if (!args.CanInteract || !args.CanAccess || args.Hands == null)
-            return;
-
-        if (component.UseVerbInstead)
-        {
-            args.Verbs.Add(new AlternativeVerb()
-            {
-                Text = Loc.GetString("verb-start-detonation"),
-                Act = () => StartTimer((uid, component), args.User),
-                Priority = 2
-            });
-        }
-
-        if (component.AllowToggleStartOnStick)
-        {
-            args.Verbs.Add(new AlternativeVerb()
-            {
-                Text = Loc.GetString("verb-toggle-start-on-stick"),
-                Act = () => ToggleStartOnStick(uid, args.User, component)
-            });
-        }
-
-        if (component.DelayOptions == null || component.DelayOptions.Count == 1)
-            return;
-
-        args.Verbs.Add(new AlternativeVerb()
-        {
-            Category = TimerOptions,
-            Text = Loc.GetString("verb-trigger-timer-cycle"),
-            Act = () => CycleDelay(component, args.User),
-            Priority = 1
-        });
-
-        foreach (var option in component.DelayOptions)
-        {
-            if (MathHelper.CloseTo(option, component.Delay))
-            {
-                args.Verbs.Add(new AlternativeVerb()
-                {
-                    Category = TimerOptions,
-                    Text = Loc.GetString("verb-trigger-timer-set-current", ("time", option)),
-                    Disabled = true,
-                    Priority = (int) (-100 * option)
-                });
-                continue;
-            }
-
-            args.Verbs.Add(new AlternativeVerb()
-            {
-                Category = TimerOptions,
-                Text = Loc.GetString("verb-trigger-timer-set", ("time", option)),
-                Priority = (int) (-100 * option),
-
-                Act = () =>
-                {
-                    component.Delay = option;
-                    _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", option)), args.User, args.User);
-                },
-            });
-        }
-    }
-
-    private void OnRandomTimerTriggerMapInit(Entity<RandomTimerTriggerComponent> ent, ref MapInitEvent args)
-    {
-        var (_, comp) = ent;
-
-        if (!TryComp<OnUseTimerTriggerComponent>(ent, out var timerTriggerComp))
-            return;
-
-        timerTriggerComp.Delay = _random.NextFloat(comp.Min, comp.Max);
-    }
-
-    private void CycleDelay(OnUseTimerTriggerComponent component, EntityUid user)
-    {
-        if (component.DelayOptions == null || component.DelayOptions.Count == 1)
-            return;
-
-        // This is somewhat inefficient, but its good enough. This is run rarely, and the lists should be short.
-
-        component.DelayOptions.Sort();
-
-        if (component.DelayOptions[^1] <= component.Delay)
-        {
-            component.Delay = component.DelayOptions[0];
-            _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", component.Delay)), user, user);
-            return;
-        }
-
-        foreach (var option in component.DelayOptions)
-        {
-            if (option > component.Delay)
-            {
-                component.Delay = option;
-                _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", option)), user, user);
-                return;
-            }
-        }
-    }
-
-    private void ToggleStartOnStick(EntityUid grenade, EntityUid user, OnUseTimerTriggerComponent comp)
-    {
-        if (comp.StartOnStick)
-        {
-            comp.StartOnStick = false;
-            _popupSystem.PopupEntity(Loc.GetString("popup-start-on-stick-off"), grenade, user);
-        }
-        else
-        {
-            comp.StartOnStick = true;
-            _popupSystem.PopupEntity(Loc.GetString("popup-start-on-stick-on"), grenade, user);
-        }
-    }
-
-    private void OnTimerUse(EntityUid uid, OnUseTimerTriggerComponent component, UseInHandEvent args)
-    {
-        if (args.Handled || HasComp<AutomatedTimerComponent>(uid) || component.UseVerbInstead)
-            return;
-
-        if (component.DoPopup)
-            _popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
-
-        StartTimer((uid, component), args.User);
-
-        args.Handled = true;
-    }
-
-    public static VerbCategory TimerOptions = new("verb-categories-timer", "/Textures/Interface/VerbIcons/clock.svg.192dpi.png");
-}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs
deleted file mode 100644 (file)
index 426bade..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-using Content.Server.Explosion.Components;
-using Content.Shared.Trigger;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Events;
-using Robust.Shared.Utility;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed partial class TriggerSystem
-{
-    [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-
-    private void InitializeProximity()
-    {
-        SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide);
-        SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide);
-        SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit);
-        SubscribeLocalEvent<TriggerOnProximityComponent, ComponentShutdown>(OnProximityShutdown);
-        // Shouldn't need re-anchoring.
-        SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor);
-    }
-
-    private void OnProximityAnchor(EntityUid uid, TriggerOnProximityComponent component, ref AnchorStateChangedEvent args)
-    {
-        component.Enabled = !component.RequiresAnchored ||
-                            args.Anchored;
-
-        SetProximityAppearance(uid, component);
-
-        if (!component.Enabled)
-        {
-            component.Colliding.Clear();
-        }
-        // Re-check for contacts as we cleared them.
-        else if (TryComp<PhysicsComponent>(uid, out var body))
-        {
-            _broadphase.RegenerateContacts((uid, body));
-        }
-    }
-
-    private void OnProximityShutdown(EntityUid uid, TriggerOnProximityComponent component, ComponentShutdown args)
-    {
-        component.Colliding.Clear();
-    }
-
-    private void OnMapInit(EntityUid uid, TriggerOnProximityComponent component, MapInitEvent args)
-    {
-        component.Enabled = !component.RequiresAnchored ||
-                            Transform(uid).Anchored;
-
-        SetProximityAppearance(uid, component);
-
-        if (!TryComp<PhysicsComponent>(uid, out var body))
-            return;
-
-        _fixtures.TryCreateFixture(
-            uid,
-            component.Shape,
-            TriggerOnProximityComponent.FixtureID,
-            hard: false,
-            body: body,
-            collisionLayer: component.Layer);
-    }
-
-    private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args)
-    {
-        if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
-            return;
-
-        component.Colliding[args.OtherEntity] = args.OtherBody;
-    }
-
-    private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args)
-    {
-        if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
-            return;
-
-        component.Colliding.Remove(args.OtherEntity);
-    }
-
-    private void SetProximityAppearance(EntityUid uid, TriggerOnProximityComponent component)
-    {
-        if (TryComp(uid, out AppearanceComponent? appearance))
-        {
-            _appearance.SetData(uid, ProximityTriggerVisualState.State, component.Enabled ? ProximityTriggerVisuals.Inactive : ProximityTriggerVisuals.Off, appearance);
-        }
-    }
-
-    private void Activate(EntityUid uid, EntityUid user, TriggerOnProximityComponent component)
-    {
-        DebugTools.Assert(component.Enabled);
-
-        var curTime = _timing.CurTime;
-
-        if (!component.Repeating)
-        {
-            component.Enabled = false;
-            component.Colliding.Clear();
-        }
-        else
-        {
-            component.NextTrigger = curTime + component.Cooldown;
-        }
-
-        // Queue a visual update for when the animation is complete.
-        component.NextVisualUpdate = curTime + component.AnimationDuration;
-
-        if (TryComp(uid, out AppearanceComponent? appearance))
-        {
-            _appearance.SetData(uid, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active, appearance);
-        }
-
-        Trigger(uid, user);
-    }
-
-    private void UpdateProximity()
-    {
-        var curTime = _timing.CurTime;
-
-        var query = EntityQueryEnumerator<TriggerOnProximityComponent>();
-        while (query.MoveNext(out var uid, out var trigger))
-        {
-            if (curTime >= trigger.NextVisualUpdate)
-            {
-                // Update the visual state once the animation is done.
-                trigger.NextVisualUpdate = TimeSpan.MaxValue;
-                SetProximityAppearance(uid, trigger);
-            }
-
-            if (!trigger.Enabled)
-                continue;
-
-            if (curTime < trigger.NextTrigger)
-                // The trigger's on cooldown.
-                continue;
-
-            // Check for anything colliding and moving fast enough.
-            foreach (var (collidingUid, colliding) in trigger.Colliding)
-            {
-                if (Deleted(collidingUid))
-                    continue;
-
-                if (colliding.LinearVelocity.Length() < trigger.TriggerSpeed)
-                    continue;
-
-                // Trigger!
-                Activate(uid, collidingUid, trigger);
-                break;
-            }
-        }
-    }
-}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs
deleted file mode 100644 (file)
index 99e8c97..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-using Content.Server.DeviceLinking.Systems;
-using Content.Server.Explosion.Components;
-using Content.Shared.DeviceLinking.Events;
-
-namespace Content.Server.Explosion.EntitySystems
-{
-    public sealed partial class TriggerSystem
-    {
-        [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
-        private void InitializeSignal()
-        {
-            SubscribeLocalEvent<TriggerOnSignalComponent,SignalReceivedEvent>(OnSignalReceived);
-            SubscribeLocalEvent<TriggerOnSignalComponent,ComponentInit>(OnInit);
-
-            SubscribeLocalEvent<TimerStartOnSignalComponent,SignalReceivedEvent>(OnTimerSignalReceived);
-            SubscribeLocalEvent<TimerStartOnSignalComponent,ComponentInit>(OnTimerSignalInit);
-        }
-
-        private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, ref SignalReceivedEvent args)
-        {
-            if (args.Port != component.Port)
-                return;
-
-            Trigger(uid, args.Trigger);
-        }
-        private void OnInit(EntityUid uid, TriggerOnSignalComponent component, ComponentInit args)
-        {
-            _signalSystem.EnsureSinkPorts(uid, component.Port);
-        }
-
-        private void OnTimerSignalReceived(EntityUid uid, TimerStartOnSignalComponent component, ref SignalReceivedEvent args)
-        {
-            if (args.Port != component.Port)
-                return;
-
-            StartTimer(uid, args.Trigger);
-        }
-        private void OnTimerSignalInit(EntityUid uid, TimerStartOnSignalComponent component, ComponentInit args)
-        {
-            _signalSystem.EnsureSinkPorts(uid, component.Port);
-        }
-    }
-}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs
deleted file mode 100644 (file)
index ea10b7c..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-using System.Linq;
-using Content.Server.Explosion.Components;
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Physics.Events;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed partial class TriggerSystem
-{
-    private void InitializeTimedCollide()
-    {
-        SubscribeLocalEvent<TriggerOnTimedCollideComponent, StartCollideEvent>(OnTimerCollide);
-        SubscribeLocalEvent<TriggerOnTimedCollideComponent, EndCollideEvent>(OnTimerEndCollide);
-        SubscribeLocalEvent<TriggerOnTimedCollideComponent, ComponentRemove>(OnComponentRemove);
-    }
-
-    private void OnTimerCollide(EntityUid uid, TriggerOnTimedCollideComponent component, ref StartCollideEvent args)
-    {
-        //Ensures the entity trigger will have an active component
-        EnsureComp<ActiveTriggerOnTimedCollideComponent>(uid);
-        var otherUID = args.OtherEntity;
-        if (component.Colliding.ContainsKey(otherUID))
-            return;
-        component.Colliding.Add(otherUID, 0);
-    }
-
-    private void OnTimerEndCollide(EntityUid uid, TriggerOnTimedCollideComponent component, ref EndCollideEvent args)
-    {
-        var otherUID = args.OtherEntity;
-        component.Colliding.Remove(otherUID);
-
-        if (component.Colliding.Count == 0 && HasComp<ActiveTriggerOnTimedCollideComponent>(uid))
-            RemComp<ActiveTriggerOnTimedCollideComponent>(uid);
-    }
-
-    private void OnComponentRemove(EntityUid uid, TriggerOnTimedCollideComponent component, ComponentRemove args)
-    {
-        if (HasComp<ActiveTriggerOnTimedCollideComponent>(uid))
-            RemComp<ActiveTriggerOnTimedCollideComponent>(uid);
-    }
-
-    private void UpdateTimedCollide(float frameTime)
-    {
-        var query = EntityQueryEnumerator<ActiveTriggerOnTimedCollideComponent, TriggerOnTimedCollideComponent>();
-        while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide))
-        {
-            foreach (var (collidingEntity, collidingTimer) in triggerOnTimedCollide.Colliding)
-            {
-                triggerOnTimedCollide.Colliding[collidingEntity] += frameTime;
-                if (collidingTimer > triggerOnTimedCollide.Threshold)
-                {
-                    RaiseLocalEvent(uid, new TriggerEvent(uid, collidingEntity), true);
-                    triggerOnTimedCollide.Colliding[collidingEntity] -= triggerOnTimedCollide.Threshold;
-                }
-            }
-        }
-    }
-}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs
deleted file mode 100644 (file)
index a96508e..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-using Content.Server.Explosion.Components;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
-using Content.Shared.Database;
-using Content.Shared.Examine;
-using Content.Shared.Verbs;
-
-namespace Content.Server.Explosion.EntitySystems
-{
-    public sealed partial class TriggerSystem
-    {
-        private void InitializeVoice()
-        {
-            SubscribeLocalEvent<TriggerOnVoiceComponent, ComponentInit>(OnVoiceInit);
-            SubscribeLocalEvent<TriggerOnVoiceComponent, ExaminedEvent>(OnVoiceExamine);
-            SubscribeLocalEvent<TriggerOnVoiceComponent, GetVerbsEvent<AlternativeVerb>>(OnVoiceGetAltVerbs);
-            SubscribeLocalEvent<TriggerOnVoiceComponent, ListenEvent>(OnListen);
-        }
-
-        private void OnVoiceInit(EntityUid uid, TriggerOnVoiceComponent component, ComponentInit args)
-        {
-            if (component.IsListening)
-                EnsureComp<ActiveListenerComponent>(uid).Range = component.ListenRange;
-            else
-                RemCompDeferred<ActiveListenerComponent>(uid);
-        }
-
-        private void OnListen(Entity<TriggerOnVoiceComponent> ent, ref ListenEvent args)
-        {
-            var component = ent.Comp;
-            var message = args.Message.Trim();
-
-            if (component.IsRecording)
-            {
-                var ev = new ListenAttemptEvent(args.Source);
-                RaiseLocalEvent(ent, ev);
-
-                if (ev.Cancelled)
-                    return;
-
-                if (message.Length >= component.MinLength && message.Length <= component.MaxLength)
-                    FinishRecording(ent, args.Source, args.Message);
-                else if (message.Length > component.MaxLength)
-                    _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-record-failed-too-long"), ent);
-                else if (message.Length < component.MinLength)
-                    _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-record-failed-too-short"), ent);
-
-                return;
-            }
-
-            if (!string.IsNullOrWhiteSpace(component.KeyPhrase) && message.IndexOf(component.KeyPhrase, StringComparison.InvariantCultureIgnoreCase) is var index and >= 0 )
-            {
-                _adminLogger.Add(LogType.Trigger, LogImpact.Medium,
-                        $"A voice-trigger on {ToPrettyString(ent):entity} was triggered by {ToPrettyString(args.Source):speaker} speaking the key-phrase {component.KeyPhrase}.");
-                Trigger(ent, args.Source);
-
-                var messageWithoutPhrase = message.Remove(index, component.KeyPhrase.Length).Trim();
-
-                var voice = new VoiceTriggeredEvent(args.Source, message, messageWithoutPhrase);
-                RaiseLocalEvent(ent, ref voice);
-            }
-        }
-
-        private void OnVoiceGetAltVerbs(Entity<TriggerOnVoiceComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
-        {
-            if (!args.CanInteract || !args.CanAccess)
-                return;
-
-            var component = ent.Comp;
-
-            var @event = args;
-            args.Verbs.Add(new AlternativeVerb()
-            {
-                Text = Loc.GetString(component.IsRecording ? "verb-trigger-voice-stop" : "verb-trigger-voice-record"),
-                Act = () =>
-                {
-                    if (component.IsRecording)
-                        StopRecording(ent);
-                    else
-                        StartRecording(ent, @event.User);
-                },
-                Priority = 1
-            });
-
-            if (string.IsNullOrWhiteSpace(component.KeyPhrase))
-                return;
-
-            args.Verbs.Add(new AlternativeVerb()
-            {
-                Text = Loc.GetString("verb-trigger-voice-clear"),
-                Act = () =>
-                {
-                    component.KeyPhrase = null;
-                    component.IsRecording = false;
-                    RemComp<ActiveListenerComponent>(ent);
-                }
-            });
-        }
-
-        public void StartRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid user)
-        {
-            var component = ent.Comp;
-            component.IsRecording = true;
-            EnsureComp<ActiveListenerComponent>(ent).Range = component.ListenRange;
-
-            _adminLogger.Add(LogType.Trigger, LogImpact.Low,
-                    $"A voice-trigger on {ToPrettyString(ent):entity} has started recording. User: {ToPrettyString(user):user}");
-
-            _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-start-recording"), ent);
-        }
-
-        public void StopRecording(Entity<TriggerOnVoiceComponent> ent)
-        {
-            var component = ent.Comp;
-            component.IsRecording = false;
-            if (string.IsNullOrWhiteSpace(component.KeyPhrase))
-                RemComp<ActiveListenerComponent>(ent);
-
-            _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-stop-recording"), ent);
-        }
-
-        public void FinishRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid source, string message)
-        {
-            var component = ent.Comp;
-            component.KeyPhrase = message;
-            component.IsRecording = false;
-
-            _adminLogger.Add(LogType.Trigger, LogImpact.Low,
-                    $"A voice-trigger on {ToPrettyString(ent):entity} has recorded a new keyphrase: '{component.KeyPhrase}'. Recorded from {ToPrettyString(source):speaker}");
-
-            _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-recorded", ("keyphrase", component.KeyPhrase!)), ent);
-        }
-
-        private void OnVoiceExamine(EntityUid uid, TriggerOnVoiceComponent component, ExaminedEvent args)
-        {
-            if (args.IsInDetailsRange)
-            {
-                args.PushText(string.IsNullOrWhiteSpace(component.KeyPhrase)
-                    ? Loc.GetString("trigger-voice-uninitialized")
-                    : Loc.GetString("examine-trigger-voice", ("keyphrase", component.KeyPhrase)));
-            }
-        }
-    }
-}
-
-
-/// <summary>
-///    Raised when a voice trigger is activated, containing the message that triggered it.
-/// </summary>
-/// <param name="Source"> The EntityUid of the entity sending the message</param>
-/// <param name="Message"> The contents of the message</param>
-/// <param name="MessageWithoutPhrase"> The message without the phrase that triggered it.</param>
-[ByRefEvent]
-public readonly record struct VoiceTriggeredEvent(EntityUid Source, string Message, string MessageWithoutPhrase);
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.cs
deleted file mode 100644 (file)
index f3a3f0c..0000000
+++ /dev/null
@@ -1,454 +0,0 @@
-using Content.Server.Administration.Logs;
-using Content.Server.Body.Systems;
-using Content.Server.Explosion.Components;
-using Content.Shared.Flash;
-using Content.Server.Electrocution;
-using Content.Server.Pinpointer;
-using Content.Shared.Chemistry.EntitySystems;
-using Content.Shared.Flash.Components;
-using Content.Server.Radio.EntitySystems;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.Components.SolutionManager;
-using Content.Shared.Database;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Explosion.Components.OnTrigger;
-using Content.Shared.Implants.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Inventory;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Payload.Components;
-using Content.Shared.Radio;
-using Content.Shared.Slippery;
-using Content.Shared.StepTrigger.Systems;
-using Content.Shared.Trigger;
-using Content.Shared.Weapons.Ranged.Events;
-using Content.Shared.Whitelist;
-using JetBrains.Annotations;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Containers;
-using Robust.Shared.Physics.Events;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Explosion.EntitySystems
-{
-    /// <summary>
-    /// Raised whenever something is Triggered on the entity.
-    /// </summary>
-    public sealed class TriggerEvent : HandledEntityEventArgs
-    {
-        public EntityUid Triggered { get; }
-        public EntityUid? User { get; }
-
-        public TriggerEvent(EntityUid triggered, EntityUid? user = null)
-        {
-            Triggered = triggered;
-            User = user;
-        }
-    }
-
-    /// <summary>
-    /// Raised before a trigger is activated.
-    /// </summary>
-    [ByRefEvent]
-    public record struct BeforeTriggerEvent(EntityUid Triggered, EntityUid? User, bool Cancelled = false);
-
-    /// <summary>
-    /// Raised when timer trigger becomes active.
-    /// </summary>
-    [ByRefEvent]
-    public readonly record struct ActiveTimerTriggerEvent(EntityUid Triggered, EntityUid? User);
-
-    [UsedImplicitly]
-    public sealed partial class TriggerSystem : EntitySystem
-    {
-        [Dependency] private readonly ExplosionSystem _explosions = default!;
-        [Dependency] private readonly FixtureSystem _fixtures = default!;
-        [Dependency] private readonly SharedFlashSystem _flashSystem = default!;
-        [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
-        [Dependency] private readonly IAdminLogManager _adminLogger = default!;
-        [Dependency] private readonly SharedContainerSystem _container = default!;
-        [Dependency] private readonly BodySystem _body = default!;
-        [Dependency] private readonly SharedAudioSystem _audio = default!;
-        [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
-        [Dependency] private readonly NavMapSystem _navMap = default!;
-        [Dependency] private readonly RadioSystem _radioSystem = default!;
-        [Dependency] private readonly IRobustRandom _random = default!;
-        [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-        [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
-        [Dependency] private readonly InventorySystem _inventory = default!;
-        [Dependency] private readonly ElectrocutionSystem _electrocution = default!;
-        [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
-
-        public override void Initialize()
-        {
-            base.Initialize();
-
-            InitializeProximity();
-            InitializeOnUse();
-            InitializeSignal();
-            InitializeTimedCollide();
-            InitializeVoice();
-            InitializeMobstate();
-
-            SubscribeLocalEvent<TriggerOnSpawnComponent, MapInitEvent>(OnSpawnTriggered);
-            SubscribeLocalEvent<TriggerOnCollideComponent, StartCollideEvent>(OnTriggerCollide);
-            SubscribeLocalEvent<TriggerOnActivateComponent, ActivateInWorldEvent>(OnActivate);
-            SubscribeLocalEvent<TriggerOnUseComponent, UseInHandEvent>(OnUse);
-            SubscribeLocalEvent<TriggerImplantActionComponent, ActivateImplantEvent>(OnImplantTrigger);
-            SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered);
-            SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlipTriggered);
-            SubscribeLocalEvent<TriggerWhenEmptyComponent, OnEmptyGunShotEvent>(OnEmptyTriggered);
-            SubscribeLocalEvent<RepeatingTriggerComponent, MapInitEvent>(OnRepeatInit);
-
-            SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(OnSpawnTrigger);
-            SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteTrigger);
-            SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(HandleExplodeTrigger);
-            SubscribeLocalEvent<FlashOnTriggerComponent, TriggerEvent>(HandleFlashTrigger);
-            SubscribeLocalEvent<GibOnTriggerComponent, TriggerEvent>(HandleGibTrigger);
-
-            SubscribeLocalEvent<AnchorOnTriggerComponent, TriggerEvent>(OnAnchorTrigger);
-            SubscribeLocalEvent<SoundOnTriggerComponent, TriggerEvent>(OnSoundTrigger);
-            SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(HandleShockTrigger);
-            SubscribeLocalEvent<RattleComponent, TriggerEvent>(HandleRattleTrigger);
-
-            SubscribeLocalEvent<TriggerWhitelistComponent, BeforeTriggerEvent>(HandleWhitelist);
-        }
-
-        private void HandleWhitelist(Entity<TriggerWhitelistComponent> ent, ref BeforeTriggerEvent args)
-        {
-            args.Cancelled = !_whitelist.CheckBoth(args.User, ent.Comp.Blacklist, ent.Comp.Whitelist);
-        }
-
-        private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, TriggerEvent args)
-        {
-            if (component.RemoveOnTrigger) // if the component gets removed when it's triggered
-            {
-                var xform = Transform(uid);
-                _audio.PlayPvs(component.Sound, xform.Coordinates); // play the sound at its last known coordinates
-            }
-            else // if the component doesn't get removed when triggered
-            {
-                _audio.PlayPvs(component.Sound, uid); // have the sound follow the entity itself
-            }
-        }
-
-        private void HandleShockTrigger(Entity<ShockOnTriggerComponent> shockOnTrigger, ref TriggerEvent args)
-        {
-            if (!_container.TryGetContainingContainer(shockOnTrigger.Owner, out var container))
-                return;
-
-            var containerEnt = container.Owner;
-            var curTime = _timing.CurTime;
-
-            if (curTime < shockOnTrigger.Comp.NextTrigger)
-            {
-                // The trigger's on cooldown.
-                return;
-            }
-
-            _electrocution.TryDoElectrocution(containerEnt, null, shockOnTrigger.Comp.Damage, shockOnTrigger.Comp.Duration, true, ignoreInsulation: true);
-            shockOnTrigger.Comp.NextTrigger = curTime + shockOnTrigger.Comp.Cooldown;
-        }
-
-        private void OnAnchorTrigger(EntityUid uid, AnchorOnTriggerComponent component, TriggerEvent args)
-        {
-            var xform = Transform(uid);
-
-            if (xform.Anchored)
-                return;
-
-            _transformSystem.AnchorEntity(uid, xform);
-
-            if (component.RemoveOnTrigger)
-                RemCompDeferred<AnchorOnTriggerComponent>(uid);
-        }
-
-        private void OnSpawnTrigger(Entity<SpawnOnTriggerComponent> ent, ref TriggerEvent args)
-        {
-            var xform = Transform(ent);
-
-            if (ent.Comp.mapCoords)
-            {
-                var mapCoords = _transformSystem.GetMapCoordinates(ent, xform);
-                Spawn(ent.Comp.Proto, mapCoords);
-            }
-            else
-            {
-                var coords = xform.Coordinates;
-                if (!coords.IsValid(EntityManager))
-                    return;
-                Spawn(ent.Comp.Proto, coords);
-
-            }
-        }
-
-        private void HandleExplodeTrigger(EntityUid uid, ExplodeOnTriggerComponent component, TriggerEvent args)
-        {
-            _explosions.TriggerExplosive(uid, user: args.User);
-            args.Handled = true;
-        }
-
-        private void HandleFlashTrigger(EntityUid uid, FlashOnTriggerComponent component, TriggerEvent args)
-        {
-            _flashSystem.FlashArea(uid, args.User, component.Range, component.Duration, probability: component.Probability);
-            args.Handled = true;
-        }
-
-        private void HandleDeleteTrigger(EntityUid uid, DeleteOnTriggerComponent component, TriggerEvent args)
-        {
-            QueueDel(uid);
-            args.Handled = true;
-        }
-
-        private void HandleGibTrigger(EntityUid uid, GibOnTriggerComponent component, TriggerEvent args)
-        {
-            if (!TryComp(uid, out TransformComponent? xform))
-                return;
-            if (component.DeleteItems)
-            {
-                var items = _inventory.GetHandOrInventoryEntities(xform.ParentUid);
-                foreach (var item in items)
-                {
-                    Del(item);
-                }
-            }
-            _body.GibBody(xform.ParentUid, true);
-            args.Handled = true;
-        }
-
-
-        private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args)
-        {
-            if (!TryComp<SubdermalImplantComponent>(uid, out var implanted))
-                return;
-
-            if (implanted.ImplantedEntity == null)
-                return;
-
-            // Gets location of the implant
-            var posText = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString(uid));
-            var critMessage = Loc.GetString(component.CritMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText));
-            var deathMessage = Loc.GetString(component.DeathMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText));
-
-            if (!TryComp<MobStateComponent>(implanted.ImplantedEntity, out var mobstate))
-                return;
-
-            // Sends a message to the radio channel specified by the implant
-            if (mobstate.CurrentState == MobState.Critical)
-                _radioSystem.SendRadioMessage(uid, critMessage, _prototypeManager.Index<RadioChannelPrototype>(component.RadioChannel), uid);
-            if (mobstate.CurrentState == MobState.Dead)
-                _radioSystem.SendRadioMessage(uid, deathMessage, _prototypeManager.Index<RadioChannelPrototype>(component.RadioChannel), uid);
-
-            args.Handled = true;
-        }
-
-        private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args)
-        {
-            if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard))
-                Trigger(uid, args.OtherEntity);
-        }
-
-        private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args)
-        {
-            Trigger(uid);
-        }
-
-        private void OnActivate(EntityUid uid, TriggerOnActivateComponent component, ActivateInWorldEvent args)
-        {
-            if (args.Handled || !args.Complex)
-                return;
-
-            Trigger(uid, args.User);
-            args.Handled = true;
-        }
-
-        private void OnUse(Entity<TriggerOnUseComponent> ent, ref UseInHandEvent args)
-        {
-            if (args.Handled)
-                return;
-
-            Trigger(ent.Owner, args.User);
-            args.Handled = true;
-        }
-
-        private void OnImplantTrigger(EntityUid uid, TriggerImplantActionComponent component, ActivateImplantEvent args)
-        {
-            args.Handled = Trigger(uid);
-        }
-
-        private void OnStepTriggered(EntityUid uid, TriggerOnStepTriggerComponent component, ref StepTriggeredOffEvent args)
-        {
-            Trigger(uid, args.Tripper);
-        }
-
-        private void OnSlipTriggered(EntityUid uid, TriggerOnSlipComponent component, ref SlipEvent args)
-        {
-            Trigger(uid, args.Slipped);
-        }
-
-        private void OnEmptyTriggered(EntityUid uid, TriggerWhenEmptyComponent component, ref OnEmptyGunShotEvent args)
-        {
-            Trigger(uid, args.EmptyGun);
-        }
-
-        private void OnRepeatInit(Entity<RepeatingTriggerComponent> ent, ref MapInitEvent args)
-        {
-            ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay;
-        }
-
-        public bool Trigger(EntityUid trigger, EntityUid? user = null)
-        {
-            var beforeTriggerEvent = new BeforeTriggerEvent(trigger, user);
-            RaiseLocalEvent(trigger, ref beforeTriggerEvent);
-            if (beforeTriggerEvent.Cancelled)
-                return false;
-
-            var triggerEvent = new TriggerEvent(trigger, user);
-            EntityManager.EventBus.RaiseLocalEvent(trigger, triggerEvent, true);
-            return triggerEvent.Handled;
-        }
-
-        public void TryDelay(EntityUid uid, float amount, ActiveTimerTriggerComponent? comp = null)
-        {
-            if (!Resolve(uid, ref comp, false))
-                return;
-
-            comp.TimeRemaining += amount;
-        }
-
-        /// <summary>
-        /// Start the timer for triggering the device.
-        /// </summary>
-        public void StartTimer(Entity<OnUseTimerTriggerComponent?> ent, EntityUid? user)
-        {
-            if (!Resolve(ent, ref ent.Comp, false))
-                return;
-
-            var comp = ent.Comp;
-            HandleTimerTrigger(ent, user, comp.Delay, comp.BeepInterval, comp.InitialBeepDelay, comp.BeepSound);
-        }
-
-        public void HandleTimerTrigger(EntityUid uid, EntityUid? user, float delay, float beepInterval, float? initialBeepDelay, SoundSpecifier? beepSound)
-        {
-            if (delay <= 0)
-            {
-                RemComp<ActiveTimerTriggerComponent>(uid);
-                Trigger(uid, user);
-                return;
-            }
-
-            if (HasComp<ActiveTimerTriggerComponent>(uid))
-                return;
-
-            if (user != null)
-            {
-                // Check if entity is bomb/mod. grenade/etc
-                if (_container.TryGetContainer(uid, "payload", out BaseContainer? container) &&
-                    container.ContainedEntities.Count > 0 &&
-                    TryComp(container.ContainedEntities[0], out ChemicalPayloadComponent? chemicalPayloadComponent))
-                {
-                    // If a beaker is missing, the entity won't explode, so no reason to log it
-                    if (chemicalPayloadComponent?.BeakerSlotA.Item is not { } beakerA ||
-                        chemicalPayloadComponent?.BeakerSlotB.Item is not { } beakerB ||
-                        !TryComp(beakerA, out SolutionContainerManagerComponent? containerA) ||
-                        !TryComp(beakerB, out SolutionContainerManagerComponent? containerB) ||
-                        !TryComp(beakerA, out FitsInDispenserComponent? fitsA) ||
-                        !TryComp(beakerB, out FitsInDispenserComponent? fitsB) ||
-                        !_solutionContainerSystem.TryGetSolution((beakerA, containerA), fitsA.Solution, out _, out var solutionA) ||
-                        !_solutionContainerSystem.TryGetSolution((beakerB, containerB), fitsB.Solution, out _, out var solutionB))
-                        return;
-
-                    _adminLogger.Add(LogType.Trigger,
-                        $"{ToPrettyString(user.Value):user} started a {delay} second timer trigger on entity {ToPrettyString(uid):timer}, which contains {SharedSolutionContainerSystem.ToPrettyString(solutionA)} in one beaker and {SharedSolutionContainerSystem.ToPrettyString(solutionB)} in the other.");
-                }
-                else
-                {
-                    _adminLogger.Add(LogType.Trigger,
-                        $"{ToPrettyString(user.Value):user} started a {delay} second timer trigger on entity {ToPrettyString(uid):timer}");
-                }
-
-            }
-            else
-            {
-                _adminLogger.Add(LogType.Trigger,
-                    $"{delay} second timer trigger started on entity {ToPrettyString(uid):timer}");
-            }
-
-            var active = AddComp<ActiveTimerTriggerComponent>(uid);
-            active.TimeRemaining = delay;
-            active.User = user;
-            active.BeepSound = beepSound;
-            active.BeepInterval = beepInterval;
-            active.TimeUntilBeep = initialBeepDelay == null ? active.BeepInterval : initialBeepDelay.Value;
-
-            var ev = new ActiveTimerTriggerEvent(uid, user);
-            RaiseLocalEvent(uid, ref ev);
-
-            if (TryComp<AppearanceComponent>(uid, out var appearance))
-                _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance);
-        }
-
-        public override void Update(float frameTime)
-        {
-            base.Update(frameTime);
-
-            UpdateProximity();
-            UpdateTimer(frameTime);
-            UpdateTimedCollide(frameTime);
-            UpdateRepeat();
-        }
-
-        private void UpdateTimer(float frameTime)
-        {
-            HashSet<EntityUid> toRemove = new();
-            var query = EntityQueryEnumerator<ActiveTimerTriggerComponent>();
-            while (query.MoveNext(out var uid, out var timer))
-            {
-                timer.TimeRemaining -= frameTime;
-                timer.TimeUntilBeep -= frameTime;
-
-                if (timer.TimeRemaining <= 0)
-                {
-                    Trigger(uid, timer.User);
-                    toRemove.Add(uid);
-                    continue;
-                }
-
-                if (timer.BeepSound == null || timer.TimeUntilBeep > 0)
-                    continue;
-
-                timer.TimeUntilBeep += timer.BeepInterval;
-                _audio.PlayPvs(timer.BeepSound, uid, timer.BeepSound.Params);
-            }
-
-            foreach (var uid in toRemove)
-            {
-                RemComp<ActiveTimerTriggerComponent>(uid);
-
-                // In case this is a re-usable grenade, un-prime it.
-                if (TryComp<AppearanceComponent>(uid, out var appearance))
-                    _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
-            }
-        }
-
-        private void UpdateRepeat()
-        {
-            var now = _timing.CurTime;
-            var query = EntityQueryEnumerator<RepeatingTriggerComponent>();
-            while (query.MoveNext(out var uid, out var comp))
-            {
-                if (comp.NextTrigger > now)
-                    continue;
-
-                comp.NextTrigger = now + comp.Delay;
-                Trigger(uid);
-            }
-        }
-    }
-}
diff --git a/Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs b/Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs
deleted file mode 100644 (file)
index 8488fd1..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-using Robust.Shared.Timing;
-using Robust.Shared.Serialization.Manager;
-using Content.Server.Explosion.Components.OnTrigger;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed class TwoStageTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] private readonly ISerializationManager _serializationManager = default!;
-    [Dependency] private readonly TriggerSystem _triggerSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<TwoStageTriggerComponent, TriggerEvent>(OnTrigger);
-    }
-
-    private void OnTrigger(EntityUid uid, TwoStageTriggerComponent component, TriggerEvent args)
-    {
-        if (component.Triggered)
-            return;
-
-        component.Triggered = true;
-        component.NextTriggerTime = _timing.CurTime + component.TriggerDelay;
-    }
-
-    private void LoadComponents(EntityUid uid, TwoStageTriggerComponent component)
-    {
-        foreach (var (name, entry) in component.SecondStageComponents)
-        {
-            var comp = (Component) Factory.GetComponent(name);
-            var temp = (object)comp;
-
-            if (EntityManager.TryGetComponent(uid, entry.Component.GetType(), out var c))
-                RemComp(uid, c);
-
-            _serializationManager.CopyTo(entry.Component, ref temp);
-            AddComp(uid, comp);
-        }
-        component.ComponentsIsLoaded = true;
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        var enumerator = EntityQueryEnumerator<TwoStageTriggerComponent>();
-        while (enumerator.MoveNext(out var uid, out var component))
-        {
-            if (!component.Triggered)
-                continue;
-
-            if (!component.ComponentsIsLoaded)
-                LoadComponents(uid, component);
-
-            if (_timing.CurTime < component.NextTriggerTime)
-                continue;
-
-            component.NextTriggerTime = null;
-            _triggerSystem.Trigger(uid);
-        }
-    }
-}
diff --git a/Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs b/Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs
deleted file mode 100644 (file)
index 11fb015..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.GhostKick;
-
-[RegisterComponent]
-public sealed partial class GhostKickUserOnTriggerComponent : Component
-{
-
-}
diff --git a/Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs b/Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs
deleted file mode 100644 (file)
index 2a5d906..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Player;
-
-namespace Content.Server.GhostKick;
-
-public sealed class GhostKickUserOnTriggerSystem : EntitySystem
-{
-    [Dependency] private readonly GhostKickManager _ghostKickManager = default!;
-
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<GhostKickUserOnTriggerComponent, TriggerEvent>(HandleMineTriggered);
-    }
-
-    private void HandleMineTriggered(EntityUid uid, GhostKickUserOnTriggerComponent userOnTriggerComponent, TriggerEvent args)
-    {
-        if (!TryComp(args.User, out ActorComponent? actor))
-            return;
-
-        _ghostKickManager.DoDisconnect(
-            actor.PlayerSession.Channel,
-            "Tripped over a kick mine, crashed through the fourth wall");
-
-        args.Handled = true;
-    }
-}
index f2bd0e05adf7db1541e8ff5800b4d65f9340380c..884fb3ae7119aae6d0bca20cb7fa3967a4e6f2b9 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Chat.Systems;
 using Content.Server.Popups;
 using Content.Server.Power.EntitySystems;
-using Content.Server.Speech.Components;
 using Content.Server.Telephone;
 using Content.Shared.Access.Systems;
 using Content.Shared.Audio;
@@ -12,6 +11,7 @@ using Content.Shared.Labels.Components;
 using Content.Shared.Power;
 using Content.Shared.Silicons.StationAi;
 using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
 using Content.Shared.Telephone;
 using Content.Shared.UserInterface;
 using Content.Shared.Verbs;
@@ -560,7 +560,7 @@ public sealed class HolopadSystem : SharedHolopadSystem
             entity.Comp.User = (user.Value, holopadUser);
         }
 
-        // Add the new user to PVS and sync their appearance with any 
+        // Add the new user to PVS and sync their appearance with any
         // holopads connected to the one they are using
         _pvs.AddGlobalOverride(user.Value);
         SyncHolopadHologramAppearanceWithTarget(entity, entity.Comp.User);
index 8ca33fb8cd12d6eeb1d10085b5f271e068deb0af..554fe098b0f78a1778dc5eeabb14b92a20f93e06 100644 (file)
@@ -1,61 +1,5 @@
-using Content.Server.Audio;
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Damage.Systems;
-using Content.Shared.Hands.Components;
-using Content.Shared.Hands.EntitySystems;
 using Content.Shared.HotPotato;
-using Content.Shared.Popups;
-using Content.Shared.Weapons.Melee.Events;
 
 namespace Content.Server.HotPotato;
 
-public sealed class HotPotatoSystem : SharedHotPotatoSystem
-{
-    [Dependency] private readonly SharedHandsSystem _hands = default!;
-    [Dependency] private readonly SharedPopupSystem _popup = default!;
-    [Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
-    [Dependency] private readonly DamageOnHoldingSystem _damageOnHolding = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<HotPotatoComponent, ActiveTimerTriggerEvent>(OnActiveTimer);
-        SubscribeLocalEvent<HotPotatoComponent, MeleeHitEvent>(OnMeleeHit);
-    }
-
-    private void OnActiveTimer(EntityUid uid, HotPotatoComponent comp, ref ActiveTimerTriggerEvent args)
-    {
-        EnsureComp<ActiveHotPotatoComponent>(uid);
-        comp.CanTransfer = false;
-        _ambientSound.SetAmbience(uid, true);
-        _damageOnHolding.SetEnabled(uid, true);
-        Dirty(uid, comp);
-    }
-
-    private void OnMeleeHit(EntityUid uid, HotPotatoComponent comp, MeleeHitEvent args)
-    {
-        if (!HasComp<ActiveHotPotatoComponent>(uid))
-            return;
-
-        comp.CanTransfer = true;
-        foreach (var hitEntity in args.HitEntities)
-        {
-            if (!TryComp<HandsComponent>(hitEntity, out var hands))
-                continue;
-
-            if (!_hands.IsHolding((hitEntity, hands), uid, out _) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands))
-            {
-                _popup.PopupEntity(Loc.GetString("hot-potato-passed",
-                    ("from", args.User), ("to", hitEntity)), uid, PopupType.Medium);
-                break;
-            }
-
-            _popup.PopupEntity(Loc.GetString("hot-potato-failed",
-                ("to", hitEntity)), uid, PopupType.Medium);
-
-            break;
-        }
-        comp.CanTransfer = false;
-        Dirty(uid, comp);
-    }
-}
+public sealed class HotPotatoSystem : SharedHotPotatoSystem;
diff --git a/Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs b/Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs
deleted file mode 100644 (file)
index 939198c..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.IgnitionSource;
-
-/// <summary>
-/// Ignites for a certain length of time when triggered.
-/// Requires <see cref="Shared.IgnitionSourceComponent"/> along with triggering components.
-/// </summary>
-[RegisterComponent, Access(typeof(IgniteOnTriggerSystem))]
-public sealed partial class IgniteOnTriggerComponent : Component
-{
-    /// <summary>
-    /// Once ignited, the time it will unignite at.
-    /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan IgnitedUntil = TimeSpan.Zero;
-
-    /// <summary>
-    /// How long the ignition source is active for after triggering.
-    /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan IgnitedTime = TimeSpan.FromSeconds(0.5);
-
-    /// <summary>
-    /// Sound to play when igniting.
-    /// </summary>
-    [DataField]
-    public SoundSpecifier IgniteSound = new SoundCollectionSpecifier("WelderOn");
-}
index 57d25ef8ab76c7fac3b130bc4b05729ad8e5bfa4..fdea8e9c65292c2d346dfe6cf2c69c49b8ff5f84 100644 (file)
@@ -1,9 +1,9 @@
-using Content.Server.Explosion.EntitySystems;
 using Content.Shared.Armable;
 using Content.Shared.Item.ItemToggle.Components;
 using Content.Shared.LandMines;
 using Content.Shared.Popups;
 using Content.Shared.StepTrigger.Systems;
+using Content.Shared.Trigger.Systems;
 using Robust.Shared.Audio.Systems;
 
 namespace Content.Server.LandMines;
@@ -28,15 +28,15 @@ public sealed class LandMineSystem : EntitySystem
     /// </summary>
     private void HandleStepOnTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOnEvent args)
     {
-      if (!string.IsNullOrEmpty(component.TriggerText))
-      {
-          _popupSystem.PopupCoordinates(
-              Loc.GetString(component.TriggerText, ("mine", uid)),
-              Transform(uid).Coordinates,
-              args.Tripper,
-              PopupType.LargeCaution);
-      }
-      _audioSystem.PlayPvs(component.Sound, uid);
+        if (!string.IsNullOrEmpty(component.TriggerText))
+        {
+            _popupSystem.PopupCoordinates(
+                Loc.GetString(component.TriggerText, ("mine", uid)),
+                Transform(uid).Coordinates,
+                args.Tripper,
+                PopupType.LargeCaution);
+        }
+        _audioSystem.PlayPvs(component.Sound, uid);
     }
 
     /// <summary>
@@ -44,7 +44,8 @@ public sealed class LandMineSystem : EntitySystem
     /// </summary>
     private void HandleStepOffTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOffEvent args)
     {
-        _trigger.Trigger(uid, args.Tripper);
+        // TODO: Adjust to the new trigger system
+        _trigger.Trigger(uid, args.Tripper, TriggerSystem.DefaultTriggerKey);
     }
 
     /// <summary>
diff --git a/Content.Server/Mousetrap/MousetrapSystem.cs b/Content.Server/Mousetrap/MousetrapSystem.cs
deleted file mode 100644 (file)
index 3afe858..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-using Content.Server.Damage.Systems;
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Popups;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Inventory;
-using Content.Shared.Mousetrap;
-using Content.Shared.StepTrigger;
-using Content.Shared.StepTrigger.Systems;
-using Robust.Server.GameObjects;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Player;
-
-namespace Content.Server.Mousetrap;
-
-public sealed class MousetrapSystem : EntitySystem
-{
-    [Dependency] private readonly PopupSystem _popupSystem = default!;
-    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-
-    public override void Initialize()
-    {
-        SubscribeLocalEvent<MousetrapComponent, UseInHandEvent>(OnUseInHand);
-        SubscribeLocalEvent<MousetrapComponent, BeforeDamageUserOnTriggerEvent>(BeforeDamageOnTrigger);
-        SubscribeLocalEvent<MousetrapComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
-        SubscribeLocalEvent<MousetrapComponent, TriggerEvent>(OnTrigger);
-    }
-
-    private void OnUseInHand(EntityUid uid, MousetrapComponent component, UseInHandEvent args)
-    {
-        if (args.Handled)
-            return;
-
-        component.IsActive = !component.IsActive;
-        _popupSystem.PopupEntity(component.IsActive
-            ? Loc.GetString("mousetrap-on-activate")
-            : Loc.GetString("mousetrap-on-deactivate"),
-            uid,
-            args.User);
-
-        UpdateVisuals(uid);
-
-        args.Handled = true;
-    }
-
-    private void OnStepTriggerAttempt(EntityUid uid, MousetrapComponent component, ref StepTriggerAttemptEvent args)
-    {
-        args.Continue |= component.IsActive;
-    }
-
-    private void BeforeDamageOnTrigger(EntityUid uid, MousetrapComponent component, BeforeDamageUserOnTriggerEvent args)
-    {
-        if (TryComp(args.Tripper, out PhysicsComponent? physics) && physics.Mass != 0)
-        {
-            // The idea here is inverse,
-            // Small - big damage,
-            // Large - small damage
-            // yes i punched numbers into a calculator until the graph looked right
-            var scaledDamage = -50 * Math.Atan(physics.Mass - component.MassBalance) + (25 * Math.PI);
-            args.Damage *= scaledDamage;
-        }
-    }
-
-    private void OnTrigger(EntityUid uid, MousetrapComponent component, TriggerEvent args)
-    {
-        component.IsActive = false;
-        UpdateVisuals(uid);
-    }
-
-    private void UpdateVisuals(EntityUid uid, MousetrapComponent? mousetrap = null, AppearanceComponent? appearance = null)
-    {
-        if (!Resolve(uid, ref mousetrap, ref appearance, false))
-        {
-            return;
-        }
-
-        _appearance.SetData(uid, MousetrapVisuals.Visual,
-            mousetrap.IsActive ? MousetrapVisuals.Armed : MousetrapVisuals.Unarmed, appearance);
-    }
-}
index 6594d7883bcf8199f9f6c323bbec31f0054f6b4a..c08576a5ceb3daf263a07efaf73d6e71f9be06d2 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Server.Explosion.EntitySystems;
 using Content.Server.Mind;
 using Content.Server.Objectives.Components;
 using Content.Server.Popups;
@@ -7,6 +6,7 @@ using Content.Shared.Ninja.Components;
 using Content.Shared.Ninja.Systems;
 using Content.Shared.Roles;
 using Content.Shared.Sticky;
+using Content.Shared.Trigger;
 
 namespace Content.Server.Ninja.Systems;
 
@@ -80,6 +80,9 @@ public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
     /// </summary>
     private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args)
     {
+        if (args.Key != comp.TriggerKey)
+            return;
+
         if (!TryComp<SpaceNinjaComponent>(comp.Planter, out var ninja))
             return;
 
index 5c0aca1578f9dbfc6330e0623de55ed2fdbef05b..e85393e6f7a23c33edfe8b56e49129630f8f9bae 100644 (file)
@@ -1,15 +1,15 @@
-using Content.Server.Explosion.EntitySystems;
 using Content.Server.Fluids.EntitySystems;
 using Content.Server.Nutrition.Components;
 using Content.Server.Popups;
 using Content.Shared.Containers.ItemSlots;
-using Content.Shared.Explosion.Components;
 using Content.Shared.IdentityManagement;
 using Content.Shared.Nutrition;
 using Content.Shared.Nutrition.Components;
 using Content.Shared.Nutrition.EntitySystems;
 using Content.Shared.Rejuvenate;
 using Content.Shared.Throwing;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Systems;
 using Content.Shared.Chemistry.EntitySystems;
 using JetBrains.Annotations;
 using Robust.Shared.Audio;
@@ -77,15 +77,9 @@ namespace Content.Server.Nutrition.EntitySystems
             {
                 if (_itemSlots.TryEject(uid, itemSlot, user: null, out var item))
                 {
-                    if (TryComp<OnUseTimerTriggerComponent>(item.Value, out var timerTrigger))
+                    if (TryComp<TimerTriggerComponent>(item.Value, out var timerTrigger))
                     {
-                        _trigger.HandleTimerTrigger(
-                            item.Value,
-                            null,
-                            timerTrigger.Delay,
-                            timerTrigger.BeepInterval,
-                            timerTrigger.InitialBeepDelay,
-                            timerTrigger.BeepSound);
+                        _trigger.ActivateTimerTrigger((item.Value, timerTrigger));
                     }
                 }
             }
index 18444bc590dd59bcba1973b409baef1b040bda49..11e97c5b9355a6cd29e1e90a89bb1af24e69c617 100644 (file)
@@ -1,10 +1,10 @@
 using Content.Server.Administration.Logs;
-using Content.Server.Explosion.EntitySystems;
 using Content.Shared.Chemistry.Components;
 using Content.Shared.Database;
 using Content.Shared.Examine;
 using Content.Shared.Payload.Components;
 using Content.Shared.Tag;
+using Content.Shared.Trigger;
 using Content.Shared.Chemistry.EntitySystems;
 using Robust.Shared.Containers;
 using Robust.Shared.Serialization.Manager;
@@ -54,18 +54,22 @@ public sealed class PayloadSystem : EntitySystem
 
     private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args)
     {
+        // TODO: Adjust to the new trigger system
+
         if (!TryComp(uid, out ContainerManagerComponent? contMan))
             return;
 
         // Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs.
         foreach (var ent in GetAllPayloads(uid, contMan))
         {
-            RaiseLocalEvent(ent, args, false);
+            RaiseLocalEvent(ent, ref args, false);
         }
     }
 
     private void OnTriggerTriggered(EntityUid uid, PayloadTriggerComponent component, TriggerEvent args)
     {
+        // TODO: Adjust to the new trigger system
+
         if (!component.Active)
             return;
 
@@ -75,7 +79,7 @@ public sealed class PayloadSystem : EntitySystem
         // Ensure we don't enter a trigger-loop
         DebugTools.Assert(!_tagSystem.HasTag(uid, PayloadTag));
 
-        RaiseLocalEvent(parent, args, false);
+        RaiseLocalEvent(parent, ref args);
     }
 
     private void OnEntityInserted(EntityUid uid, PayloadCaseComponent _, EntInsertedIntoContainerMessage args)
@@ -146,6 +150,7 @@ public sealed class PayloadSystem : EntitySystem
 
     private void HandleChemicalPayloadTrigger(Entity<ChemicalPayloadComponent> entity, ref TriggerEvent args)
     {
+        // TODO: Adjust to the new trigger system
         if (entity.Comp.BeakerSlotA.Item is not EntityUid beakerA
             || entity.Comp.BeakerSlotB.Item is not EntityUid beakerB
             || !TryComp(beakerA, out FitsInDispenserComponent? compA)
diff --git a/Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs b/Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs
deleted file mode 100644 (file)
index a11b4f4..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-using Content.Shared.Polymorph;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Polymorph.Components;
-
-/// <summary>
-/// Intended for use with the trigger system.
-/// Polymorphs the user of the trigger.
-/// </summary>
-[RegisterComponent]
-public sealed partial class PolymorphOnTriggerComponent : Component
-{
-    /// <summary>
-    /// Polymorph settings.
-    /// </summary>
-    [DataField(required: true)]
-    public ProtoId<PolymorphPrototype> Polymorph;
-}
diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs
deleted file mode 100644 (file)
index 452b060..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-using Content.Shared.Polymorph;
-using Content.Server.Polymorph.Components;
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Polymorph.Systems;
-
-public sealed partial class PolymorphSystem
-{
-    /// <summary>
-    /// Need to do this so we don't get a collection enumeration error in physics by polymorphing
-    /// an entity we're colliding with in case of TriggerOnCollide.
-    /// Also makes sure other trigger effects don't activate in nullspace after we have polymorphed.
-    /// </summary>
-    private Queue<(EntityUid Ent, ProtoId<PolymorphPrototype> Polymorph)> _queuedPolymorphUpdates = new();
-
-    private void InitializeTrigger()
-    {
-        SubscribeLocalEvent<PolymorphOnTriggerComponent, TriggerEvent>(OnTrigger);
-    }
-
-    private void OnTrigger(Entity<PolymorphOnTriggerComponent> ent, ref TriggerEvent args)
-    {
-        if (args.User == null)
-            return;
-
-        _queuedPolymorphUpdates.Enqueue((args.User.Value, ent.Comp.Polymorph));
-        args.Handled = true;
-    }
-
-    public void UpdateTrigger()
-    {
-        while (_queuedPolymorphUpdates.TryDequeue(out var data))
-        {
-            if (TerminatingOrDeleted(data.Item1))
-                continue;
-
-            PolymorphEntity(data.Item1, data.Item2);
-        }
-    }
-}
index 696b19199e7bda87153f7ea83b4bd32edb3ef2a1..d411eb77228b82005b0752583126a09d6d97dc22 100644 (file)
@@ -58,7 +58,6 @@ public sealed partial class PolymorphSystem : EntitySystem
         SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
 
         InitializeMap();
-        InitializeTrigger();
     }
 
     public override void Update(float frameTime)
@@ -85,8 +84,6 @@ public sealed partial class PolymorphSystem : EntitySystem
                 Revert((uid, comp));
             }
         }
-
-        UpdateTrigger();
     }
 
     private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args)
index 3829fc34d20870b8bfcb848bf57a56b242c4f013..f052c460f50ad6b97d18d6722a96a30944248e1b 100644 (file)
@@ -2,15 +2,14 @@ using System.Linq;
 using Content.Server.Chat.Systems;
 using Content.Server.Interaction;
 using Content.Server.Popups;
-using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
 using Content.Server.Radio.Components;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
 using Content.Shared.Examine;
 using Content.Shared.Interaction;
 using Content.Shared.Power;
 using Content.Shared.Radio;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
 using Content.Shared.Chat;
 using Content.Shared.Radio.Components;
 using Robust.Shared.Prototypes;
index e950d3f288742b81b061b3a4255df53ba52b1a13..debed525f6ebc750f542c4d6b419383e30f9472c 100644 (file)
@@ -119,7 +119,7 @@ public sealed partial class BorgSystem
 
         var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent)));
         Popup.PopupEntity(message, ent);
-        _trigger.StartTimer(ent.Owner, user: null);
+        _trigger.ActivateTimerTrigger(ent.Owner);
 
         // prevent a shitter borg running into people
         RemComp<InputMoverComponent>(ent);
index 0cd407000f713f7b3e33240e29bc926bd7bc341a..f33b71c54ed4892d7b7d2659b9c00f398070e58e 100644 (file)
@@ -4,7 +4,6 @@ using Content.Server.Actions;
 using Content.Server.Administration.Logs;
 using Content.Server.Administration.Managers;
 using Content.Server.DeviceNetwork.Systems;
-using Content.Server.Explosion.EntitySystems;
 using Content.Server.Hands.Systems;
 using Content.Server.PowerCell;
 using Content.Shared.Alert;
@@ -25,6 +24,7 @@ using Content.Shared.Roles;
 using Content.Shared.Silicons.Borgs;
 using Content.Shared.Silicons.Borgs.Components;
 using Content.Shared.Throwing;
+using Content.Shared.Trigger.Systems;
 using Content.Shared.Whitelist;
 using Content.Shared.Wires;
 using Robust.Server.GameObjects;
diff --git a/Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs b/Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs
deleted file mode 100644 (file)
index 5338351..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Sound.Components;
-
-namespace Content.Server.Sound.Components
-{
-    /// <summary>
-    /// Whenever a <see cref="TriggerEvent"/> is run play a sound in PVS range.
-    /// </summary>
-    [RegisterComponent]
-    public sealed partial class EmitSoundOnTriggerComponent : BaseEmitSoundComponent
-    {
-    }
-}
index 9d7e8496c3c4cb5f935d92ad3c607808a01a61ec..1720d67d024e45f588a4fdc8aa1b6d86109a447a 100644 (file)
@@ -1,6 +1,3 @@
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Sound.Components;
-using Content.Shared.UserInterface;
 using Content.Shared.Sound;
 using Content.Shared.Sound.Components;
 using Robust.Shared.Timing;
@@ -38,16 +35,9 @@ public sealed class EmitSoundSystem : SharedEmitSoundSystem
     {
         base.Initialize();
 
-        SubscribeLocalEvent<EmitSoundOnTriggerComponent, TriggerEvent>(HandleEmitSoundOnTrigger);
         SubscribeLocalEvent<SpamEmitSoundComponent, MapInitEvent>(HandleSpamEmitSoundMapInit);
     }
 
-    private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args)
-    {
-        TryEmitSound(uid, component, args.User, false);
-        args.Handled = true;
-    }
-
     private void HandleSpamEmitSoundMapInit(Entity<SpamEmitSoundComponent> entity, ref MapInitEvent args)
     {
         SpamEmitSoundReset(entity);
diff --git a/Content.Server/Speech/Components/ActiveListenerComponent.cs b/Content.Server/Speech/Components/ActiveListenerComponent.cs
deleted file mode 100644 (file)
index 4553faf..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Server.Chat.Systems;
-
-namespace Content.Server.Speech.Components;
-
-/// <summary>
-///     This component is used to relay speech events to other systems.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveListenerComponent : Component
-{
-    [DataField("range")]
-    public float Range = ChatSystem.VoiceRange;
-}
index a13eda1f73330fa5c1a455275690e7ad89899aad..6b0987751250b3cc34d4365756b82762135c451e 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Speech.Components;
+using Content.Shared.Speech;
 
 namespace Content.Server.Speech.EntitySystems;
 
index ea3569e055c9ed4d68e7d2709c66a81588beee60..17513d80e7aa091e5eacff32b4915540a91e5106 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Server.Chat.Systems;
-using Content.Server.Speech.Components;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
 
 namespace Content.Server.Speech.EntitySystems;
 
index 4deb5238a53c54517f1ac32f0ee7860b7eccb8b1..4029488159a8566ddc0cf58ab95e23c38926d16e 100644 (file)
@@ -1,6 +1,6 @@
 using Content.Server.Chat.Systems;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
 using Content.Shared.Whitelist;
 using Robust.Shared.Player;
 using static Content.Server.Chat.Systems.ChatSystem;
index 4c87707cc6b7512c6fbd99d6f59be30616749793..46f45d1286988a663fa097abbb9d9412eb837a3d 100644 (file)
@@ -3,8 +3,6 @@ using Content.Server.Administration.Logs;
 using Content.Server.Chat.Systems;
 using Content.Server.Interaction;
 using Content.Server.Power.EntitySystems;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
 using Content.Shared.Chat;
 using Content.Shared.Database;
 using Content.Shared.Labels.Components;
@@ -13,6 +11,7 @@ using Content.Shared.Power;
 using Content.Shared.Silicons.StationAi;
 using Content.Shared.Silicons.Borgs.Components;
 using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
 using Content.Shared.Telephone;
 using Robust.Server.GameObjects;
 using Robust.Shared.Audio.Systems;
similarity index 74%
rename from Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs
rename to Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs
index 0c9734b943e5524fde535b26dcadf9831c0c8870..1a3e49dd441167c1b1f580bb7bca616b168b1acd 100644 (file)
@@ -1,8 +1,9 @@
 using Content.Server.AlertLevel;
-using Content.Server.Explosion.EntitySystems;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
 using Content.Server.Station.Systems;
 
-namespace Content.Server.AlertLevel.Systems;
+namespace Content.Server.Trigger.Systems;
 
 public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem
 {
@@ -18,10 +19,14 @@ public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem
 
     private void OnTrigger(Entity<AlertLevelChangeOnTriggerComponent> ent, ref TriggerEvent args)
     {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
         var stationUid = _station.GetOwningStation(ent.Owner);
-        if (!stationUid.HasValue)
+        if (stationUid == null)
             return;
 
         _alertLevelSystem.SetLevel(stationUid.Value, ent.Comp.Level, ent.Comp.PlaySound, ent.Comp.Announce, ent.Comp.Force);
+        args.Handled = true;
     }
 }
diff --git a/Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs b/Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..0fae6ff
--- /dev/null
@@ -0,0 +1,38 @@
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Server.GhostKick;
+using Robust.Shared.Player;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed class GhostKickUserOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly GhostKickManager _ghostKickManager = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<GhostKickOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<GhostKickOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        if (!TryComp(target, out ActorComponent? actor))
+            return;
+
+        _ghostKickManager.DoDisconnect(
+            actor.PlayerSession.Channel,
+            Loc.GetString(ent.Comp.Reason));
+
+        args.Handled = true;
+    }
+}
similarity index 66%
rename from Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs
rename to Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs
index 0e9dd56622263f6e7e4eed5bb189b8593424ea95..f4d88b774a1aa31dfe1e2a6deddbb41da56532a1 100644 (file)
@@ -1,10 +1,9 @@
-using Content.Server.Explosion.EntitySystems;
 using Content.Shared.IgnitionSource;
-using Content.Shared.Timing;
-using Robust.Shared.Audio.Systems;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
 using Robust.Shared.Timing;
 
-namespace Content.Server.IgnitionSource;
+namespace Content.Server.Trigger.Systems;
 
 /// <summary>
 /// Handles igniting when triggered and stopping ignition after the delay.
@@ -13,8 +12,6 @@ public sealed class IgniteOnTriggerSystem : EntitySystem
 {
     [Dependency] private readonly IGameTiming _timing = default!;
     [Dependency] private readonly SharedIgnitionSourceSystem _source = default!;
-    [Dependency] private readonly SharedAudioSystem _audio = default!;
-    [Dependency] private readonly UseDelaySystem _useDelay = default!;
 
     public override void Initialize()
     {
@@ -23,6 +20,8 @@ public sealed class IgniteOnTriggerSystem : EntitySystem
         SubscribeLocalEvent<IgniteOnTriggerComponent, TriggerEvent>(OnTrigger);
     }
 
+    // TODO: move this into ignition source component
+    // it already has an update loop
     public override void Update(float deltaTime)
     {
         base.Update(deltaTime);
@@ -42,14 +41,18 @@ public sealed class IgniteOnTriggerSystem : EntitySystem
 
     private void OnTrigger(Entity<IgniteOnTriggerComponent> ent, ref TriggerEvent args)
     {
-        // prevent spamming sound and ignition
-        if (!TryComp(ent.Owner, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((ent.Owner, useDelay)))
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
             return;
 
-        _source.SetIgnited(ent.Owner);
-        _audio.PlayPvs(ent.Comp.IgniteSound, ent);
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
 
-        _useDelay.TryResetDelay((ent.Owner, useDelay));
+        if (target == null)
+            return;
+
+        _source.SetIgnited(target.Value);
         ent.Comp.IgnitedUntil = _timing.CurTime + ent.Comp.IgnitedTime;
+        Dirty(ent);
+
+        args.Handled = true;
     }
 }
diff --git a/Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs b/Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..8b4741b
--- /dev/null
@@ -0,0 +1,51 @@
+using Content.Server.Polymorph.Systems;
+using Content.Shared.Polymorph;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed partial class PolymorphOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly PolymorphSystem _polymorph = default!;
+
+    /// <summary>
+    /// Need to do this so we don't get a collection enumeration error in physics by polymorphing
+    /// an entity we're colliding with in case of TriggerOnCollide.
+    /// Also makes sure other trigger effects don't activate in nullspace after we have polymorphed.
+    /// </summary>
+    private Queue<(EntityUid Uid, ProtoId<PolymorphPrototype> Polymorph)> _queuedPolymorphUpdates = new();
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<PolymorphOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<PolymorphOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        _queuedPolymorphUpdates.Enqueue((target.Value, ent.Comp.Polymorph));
+        args.Handled = true;
+    }
+
+    public override void Update(float frametime)
+    {
+        while (_queuedPolymorphUpdates.TryDequeue(out var data))
+        {
+            if (TerminatingOrDeleted(data.Uid))
+                continue;
+
+            _polymorph.PolymorphEntity(data.Uid, data.Polymorph);
+        }
+    }
+}
diff --git a/Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs b/Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..963ac36
--- /dev/null
@@ -0,0 +1,49 @@
+using Content.Server.Radio.EntitySystems;
+using Content.Server.Pinpointer;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed class RattleOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly RadioSystem _radio = default!;
+    [Dependency] private readonly NavMapSystem _navMap = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<RattleOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<RattleOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        if (!TryComp<MobStateComponent>(target.Value, out var mobstate))
+            return;
+
+        args.Handled = true;
+
+        if (!ent.Comp.Messages.TryGetValue(mobstate.CurrentState, out var messageId))
+            return;
+
+        // Gets the location of the user
+        var posText = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString(target.Value));
+
+        var message = Loc.GetString(messageId, ("user", target.Value), ("position", posText));
+        // Sends a message to the radio channel specified by the implant
+        _radio.SendRadioMessage(ent.Owner, message, _prototypeManager.Index(ent.Comp.RadioChannel), ent.Owner);
+    }
+}
diff --git a/Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs b/Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..a38ca2a
--- /dev/null
@@ -0,0 +1,48 @@
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.Trigger.Systems;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem
+{
+    [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var curTime = _timing.CurTime;
+        var query = EntityQueryEnumerator<ReleaseGasOnTriggerComponent>();
+
+        while (query.MoveNext(out var uid, out var comp))
+        {
+            if (!comp.Active || comp.NextReleaseTime > curTime)
+                continue;
+
+            var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction);
+            var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
+
+            if (environment == null)
+            {
+                _appearance.SetData(uid, ReleaseGasOnTriggerVisuals.Key, false);
+                RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
+                continue;
+            }
+
+            _atmosphereSystem.Merge(environment, giverGasMix);
+            comp.NextReleaseTime += comp.ReleaseInterval;
+
+            if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit ||
+                comp.Air.TotalMoles <= 0)
+            {
+                _appearance.SetData(uid, ReleaseGasOnTriggerVisuals.Key, false);
+                RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
+            }
+        }
+    }
+}
diff --git a/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs b/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..97799b9
--- /dev/null
@@ -0,0 +1,68 @@
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Spreader;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Coordinates.Helpers;
+using Content.Shared.Maps;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Server.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.Server.Trigger.Systems;
+
+/// <summary>
+/// Handles creating smoke when <see cref="SmokeOnTriggerComponent"/> is triggered.
+/// </summary>
+public sealed class SmokeOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly IMapManager _mapMan = default!;
+    [Dependency] private readonly MapSystem _map = default!;
+    [Dependency] private readonly SmokeSystem _smoke = default!;
+    [Dependency] private readonly TransformSystem _transform = default!;
+    [Dependency] private readonly SpreaderSystem _spreader = default!;
+    [Dependency] private readonly TurfSystem _turf = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<SmokeOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<SmokeOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        // TODO: move all of this into an API function in SmokeSystem
+        var xform = Transform(target.Value);
+        var mapCoords = _transform.GetMapCoordinates(target.Value, xform);
+        if (!_mapMan.TryFindGridAt(mapCoords, out var gridUid, out var gridComp) ||
+            !_map.TryGetTileRef(gridUid, gridComp, xform.Coordinates, out var tileRef) ||
+            tileRef.Tile.IsEmpty)
+        {
+            return;
+        }
+
+        if (_spreader.RequiresFloorToSpread(ent.Comp.SmokePrototype.ToString()) && _turf.IsSpace(tileRef))
+            return;
+
+        var coords = _map.MapToGrid(gridUid, mapCoords);
+        var smoke = Spawn(ent.Comp.SmokePrototype, coords.SnapToGrid());
+        if (!TryComp<SmokeComponent>(smoke, out var smokeComp))
+        {
+            Log.Error($"Smoke prototype {ent.Comp.SmokePrototype} was missing SmokeComponent");
+            Del(smoke);
+            return;
+        }
+
+        _smoke.StartSmoke(smoke, ent.Comp.Solution, (float)ent.Comp.Duration.TotalSeconds, ent.Comp.SpreadAmount, smokeComp);
+
+        args.Handled = true;
+    }
+}
similarity index 53%
rename from Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs
rename to Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs
index 28be6a537385ec8260d7a449e834f2f844442237..6da6f707c120071850424ca0ed53a57cca847143 100644 (file)
@@ -1,13 +1,13 @@
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Timing;
+using Content.Server.Chat.Systems;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 
-namespace Content.Server.Chat.Systems;
+namespace Content.Server.Trigger.Systems;
 
 public sealed class SpeakOnTriggerSystem : EntitySystem
 {
-    [Dependency] private readonly UseDelaySystem _useDelay = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly ChatSystem _chat = default!;
@@ -15,32 +15,34 @@ public sealed class SpeakOnTriggerSystem : EntitySystem
     public override void Initialize()
     {
         base.Initialize();
+
         SubscribeLocalEvent<SpeakOnTriggerComponent, TriggerEvent>(OnTrigger);
     }
 
     private void OnTrigger(Entity<SpeakOnTriggerComponent> ent, ref TriggerEvent args)
     {
-        TrySpeak(ent);
-        args.Handled = true;
-    }
-
-    private void TrySpeak(Entity<SpeakOnTriggerComponent> ent)
-    {
-        // If it doesn't have the use delay component, still send the message.
-        if (TryComp<UseDelayComponent>(ent.Owner, out var useDelay) && _useDelay.IsDelayed((ent.Owner, useDelay)))
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
             return;
 
-        if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack))
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
             return;
 
-        var message = Loc.GetString(_random.Pick(messagePack.Values));
+        string message;
+        if (ent.Comp.Text != null)
+            message = Loc.GetString(ent.Comp.Text);
+        else
+        {
+            if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack))
+                return;
+            message = Loc.GetString(_random.Pick(messagePack.Values));
+        }
         // Chatcode moment: messages starting with "." are considered radio messages.
         // Prepending ">" forces the message to be spoken instead.
         // TODO chat refactor: remove this
         message = '>' + message;
-        _chat.TrySendInGameICMessage(ent.Owner, message, InGameICChatType.Speak, true);
-
-        if (useDelay != null)
-            _useDelay.TryResetDelay((ent.Owner, useDelay));
+        _chat.TrySendInGameICMessage(target.Value, message, InGameICChatType.Speak, true);
+        args.Handled = true;
     }
 }
index 81bbd48a0df057b8e544974254a0c3a67c898634..ade861af981821348e4f002b12b3cdf3ea93d688 100644 (file)
@@ -5,6 +5,7 @@ using Content.Shared.Database;
 using Content.Shared.Inventory;
 using Content.Shared.Popups;
 using Content.Shared.Storage;
+using Content.Shared.Trigger;
 using Robust.Server.Containers;
 
 namespace Content.Server.VoiceTrigger;
index b9e23f036b9879af4500032256d282df3a62baff..faef4d5078b2bf64178b9bbc52d091f953c6cf0d 100644 (file)
@@ -1,9 +1,9 @@
-using Content.Shared.Explosion.Components.OnTrigger;
+using Content.Shared.Explosion.Components;
 
 namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
 
 /// <summary>
-/// Activates 'trigger' for <see cref="ExplodeOnTriggerComponent"/>.
+/// Activates <see cref="ExplosiveComponent"/> to explode.
 /// </summary>
 [RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))]
 public sealed partial class XAETriggerExplosivesComponent : Component;
index 967980302d93a620a4d35b3c0120c475f06d7901..34e955a50e587b80f2d927ade150e2880595e58b 100644 (file)
@@ -24,6 +24,11 @@ public abstract class SharedChatSystem : EntitySystem
     public const char WhisperPrefix = ',';
     public const char DefaultChannelKey = 'h';
 
+    public const int VoiceRange = 10; // how far voice goes in world units
+    public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
+    public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
+    public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg";
+
     public static readonly ProtoId<RadioChannelPrototype> CommonChannel = "Common";
 
     public static readonly string DefaultChannelPrefix = $"{RadioChannelPrefix}{DefaultChannelKey}";
diff --git a/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs b/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs
deleted file mode 100644 (file)
index 87adc0c..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Shared.Damage.Components;
-
-[RegisterComponent]
-public sealed partial class DamageUserOnTriggerComponent : Component
-{
-    [DataField("ignoreResistances")] public bool IgnoreResistances;
-
-    [DataField("damage", required: true)]
-    public DamageSpecifier Damage = default!;
-}
index 6e4478bb6d504541b68387c8ccbb0d9b884357d3..72dc874935ee16a5ee55774a8564da9cea8a7be9 100644 (file)
@@ -1,3 +1,4 @@
+using Robust.Shared.Map;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Emp;
@@ -7,4 +8,15 @@ public abstract class SharedEmpSystem : EntitySystem
     [Dependency] protected readonly IGameTiming Timing = default!;
 
     protected const string EmpDisabledEffectPrototype = "EffectEmpDisabled";
+
+    /// <summary>
+    /// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
+    /// </summary>
+    /// <param name="coordinates">The location to trigger the EMP pulse at.</param>
+    /// <param name="range">The range of the EMP pulse.</param>
+    /// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
+    /// <param name="duration">The duration of the EMP effects.</param>
+    public virtual void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
+    {
+    }
 }
diff --git a/Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs b/Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs
deleted file mode 100644 (file)
index 6d43abc..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using Robust.Shared.Audio;
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components;
-
-/// <summary>
-///     Component for tracking active trigger timers. A timers can activated by some other component, e.g. <see cref="OnUseTimerTriggerComponent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveTimerTriggerComponent : Component
-{
-    [DataField] public float TimeRemaining;
-
-    [DataField] public EntityUid? User;
-
-    [DataField] public float BeepInterval;
-
-    [DataField] public float TimeUntilBeep;
-
-    [DataField] public SoundSpecifier? BeepSound;
-}
diff --git a/Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs b/Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs
deleted file mode 100644 (file)
index e14cd12..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components.OnTrigger;
-
-/// <summary>
-/// Explode using the entity's <see cref="ExplosiveComponent"/> if Triggered.
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-public sealed partial class ExplodeOnTriggerComponent : Component
-{
-}
diff --git a/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs b/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs
deleted file mode 100644 (file)
index 983b8a3..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-using System.Linq;
-using Content.Shared.Guidebook;
-using Robust.Shared.Audio;
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components
-{
-    [RegisterComponent, NetworkedComponent]
-    public sealed partial class OnUseTimerTriggerComponent : Component
-    {
-        [DataField] public float Delay = 1f;
-
-        /// <summary>
-        ///     If not null, a user can use verbs to configure the delay to one of these options.
-        /// </summary>
-        [DataField] public List<float>? DelayOptions = null;
-
-        /// <summary>
-        ///     If not null, this timer will periodically play this sound while active.
-        /// </summary>
-        [DataField] public SoundSpecifier? BeepSound;
-
-        /// <summary>
-        ///     Time before beeping starts. Defaults to a single beep interval. If set to zero, will emit a beep immediately after use.
-        /// </summary>
-        [DataField] public float? InitialBeepDelay;
-
-        [DataField] public float BeepInterval = 1;
-
-        /// <summary>
-        ///     Whether the timer should instead be activated through a verb in the right-click menu
-        /// </summary>
-        [DataField] public bool UseVerbInstead = false;
-
-        /// <summary>
-        ///     Should timer be started when it was stuck to another entity.
-        ///     Used for C4 charges and similar behaviour.
-        /// </summary>
-        [DataField] public bool StartOnStick;
-
-        /// <summary>
-        ///     Allows changing the start-on-stick quality.
-        /// </summary>
-        [DataField("canToggleStartOnStick")] public bool AllowToggleStartOnStick;
-
-        /// <summary>
-        ///     Whether you can examine the item to see its timer or not.
-        /// </summary>
-        [DataField] public bool Examinable = true;
-
-        /// <summary>
-        ///     Whether or not to show the user a popup when starting the timer.
-        /// </summary>
-        [DataField] public bool DoPopup = true;
-
-        #region GuidebookData
-
-        [GuidebookData]
-        public float? ShortestDelayOption => DelayOptions?.Min();
-
-        [GuidebookData]
-        public float? LongestDelayOption => DelayOptions?.Max();
-
-        #endregion GuidebookData
-    }
-}
index 059ad189d1113c0186d114b458c09b8b9eb09745..1cab5d1a77babba02593728761769084243d2bff 100644 (file)
@@ -112,4 +112,10 @@ public sealed partial class ScatteringGrenadeComponent : Component
     /// We need to store this because we are only allowed to spawn and trigger timed entities on the next available frame update
     /// </summary>
     public bool IsTriggered = false;
+
+    /// <summary>
+    /// The trigger key that will activate the grenade.
+    /// </summary>
+    [DataField]
+    public string TriggerKey = "timer";
 }
diff --git a/Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs b/Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs
deleted file mode 100644 (file)
index 02d1156..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components;
-
-[NetworkedComponent]
-public abstract partial class SharedTriggerOnProximityComponent : Component
-{
-
-}
diff --git a/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs
deleted file mode 100644 (file)
index 5027b04..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract partial class SharedReleaseGasOnTriggerSystem : EntitySystem;
-
-// I have dreams of Atmos in shared.
diff --git a/Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs
deleted file mode 100644 (file)
index 386024f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract class SharedRepulseAttractOnTriggerSystem : EntitySystem;
diff --git a/Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs
deleted file mode 100644 (file)
index b206dfa..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract class SharedSmokeOnTriggerSystem : EntitySystem
-{
-}
diff --git a/Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs b/Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs
deleted file mode 100644 (file)
index cc5b3f6..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract class SharedTriggerSystem : EntitySystem
-{
-       
-}
\ No newline at end of file
diff --git a/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs b/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs
deleted file mode 100644 (file)
index e735b37..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-using Robust.Shared.GameStates;
-namespace Content.Shared.Flash.Components;
-
-/// <summary>
-/// Upon being triggered will flash in an area around it.
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-public sealed partial class FlashOnTriggerComponent : Component
-{
-    [DataField]
-    public float Range = 1.0f;
-
-    [DataField]
-    public TimeSpan Duration = TimeSpan.FromSeconds(8);
-
-    [DataField]
-    public float Probability = 1.0f;
-}
index 9bcd218335cdfc0c2af7922e105d1c7cb10b4880..28dbb31324c19a7439ac341b4a6d0270bba0295c 100644 (file)
@@ -12,7 +12,7 @@ public sealed partial class ActiveHotPotatoComponent : Component
     /// <summary>
     /// Hot potato effect spawn cooldown in seconds
     /// </summary>
-    [DataField("effectCooldown"), ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float EffectCooldown = 0.3f;
 
     /// <summary>
index f5b2e16189008b4be4434d2c47d4a5792c38c070..b077e91e8f7112ce6102ab378af1015acf1536f5 100644 (file)
@@ -14,7 +14,6 @@ public sealed partial class HotPotatoComponent : Component
     /// <summary>
     /// If set to true entity can be removed by hitting entities if they have hands
     /// </summary>
-    [DataField("canTransfer"), ViewVariables(VVAccess.ReadWrite)]
-    [AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public bool CanTransfer = true;
 }
index cd7a5d6da5cdbfc2973b1cdded2287fae0be796b..6f2f498782da52513dee9fb951b4ba95cfd67dea 100644 (file)
@@ -1,18 +1,79 @@
+using Content.Shared.Audio;
+using Content.Shared.Damage.Systems;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Popups;
+using Content.Shared.Trigger;
+using Content.Shared.Weapons.Melee.Events;
 using Robust.Shared.Containers;
+using Robust.Shared.Timing;
 
 namespace Content.Shared.HotPotato;
 
 public abstract class SharedHotPotatoSystem : EntitySystem
 {
+    [Dependency] private readonly SharedHandsSystem _hands = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
+    [Dependency] private readonly DamageOnHoldingSystem _damageOnHolding = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+
     public override void Initialize()
     {
         base.Initialize();
+
         SubscribeLocalEvent<HotPotatoComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
+        SubscribeLocalEvent<HotPotatoComponent, ActiveTimerTriggerEvent>(OnActiveTimer);
+        SubscribeLocalEvent<HotPotatoComponent, MeleeHitEvent>(OnMeleeHit);
     }
 
-    private void OnRemoveAttempt(EntityUid uid, HotPotatoComponent comp, ContainerGettingRemovedAttemptEvent args)
+    private void OnRemoveAttempt(Entity<HotPotatoComponent> ent, ref ContainerGettingRemovedAttemptEvent args)
     {
-        if (!comp.CanTransfer)
+        if (!_timing.ApplyingState && !ent.Comp.CanTransfer)
             args.Cancel();
     }
+
+    private void OnActiveTimer(Entity<HotPotatoComponent> ent, ref ActiveTimerTriggerEvent args)
+    {
+        EnsureComp<ActiveHotPotatoComponent>(ent);
+        ent.Comp.CanTransfer = false;
+        _ambientSound.SetAmbience(ent.Owner, true);
+        _damageOnHolding.SetEnabled(ent.Owner, true);
+        Dirty(ent);
+    }
+
+    private void OnMeleeHit(Entity<HotPotatoComponent> ent, ref MeleeHitEvent args)
+    {
+        if (!HasComp<ActiveHotPotatoComponent>(ent))
+            return;
+
+        ent.Comp.CanTransfer = true;
+        foreach (var hitEntity in args.HitEntities)
+        {
+            if (!TryComp<HandsComponent>(hitEntity, out var hands))
+                continue;
+
+            if (!_hands.IsHolding((hitEntity, hands), ent.Owner, out _) && _hands.TryForcePickupAnyHand(hitEntity, ent.Owner, handsComp: hands))
+            {
+                _popup.PopupPredicted(
+                    Loc.GetString("hot-potato-passed", ("from", Identity.Entity(args.User, EntityManager)), ("to", Identity.Entity(hitEntity, EntityManager))),
+                    ent.Owner,
+                    args.User,
+                    PopupType.Medium);
+                break;
+            }
+
+            _popup.PopupClient(
+                Loc.GetString("hot-potato-failed", ("to", Identity.Entity(hitEntity, EntityManager))),
+                ent.Owner,
+                args.User,
+                PopupType.Medium);
+
+            break;
+        }
+
+        ent.Comp.CanTransfer = false;
+    }
 }
diff --git a/Content.Shared/Implants/Components/RattleComponent.cs b/Content.Shared/Implants/Components/RattleComponent.cs
deleted file mode 100644 (file)
index 3ec63e8..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Shared.Radio;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Implants.Components;
-
-[RegisterComponent, NetworkedComponent]
-public sealed partial class RattleComponent : Component
-{
-    // The radio channel the message will be sent to
-    [DataField]
-    public ProtoId<RadioChannelPrototype> RadioChannel = "Syndicate";
-
-    // The message that the implant will send when crit
-    [DataField]
-    public LocId CritMessage = "deathrattle-implant-critical-message";
-
-    // The message that the implant will send when dead
-    [DataField]
-    public LocId DeathMessage = "deathrattle-implant-dead-message";
-}
diff --git a/Content.Shared/Implants/Components/TriggerImplantActionComponent.cs b/Content.Shared/Implants/Components/TriggerImplantActionComponent.cs
deleted file mode 100644 (file)
index 0f9856f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Implants.Components;
-
-/// <summary>
-/// Triggers implants when the action is pressed
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-public sealed partial class TriggerImplantActionComponent : Component
-{
-
-}
index 95c3f8664fbf8cd63c25fab08a332fe6aad466da..177e24ff02cde0a0f08fc9ba145a4f2a122f1eab 100644 (file)
@@ -179,7 +179,7 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
         if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer))
             return;
 
-        var relayEv = new ImplantRelayEvent<T>(args);
+        var relayEv = new ImplantRelayEvent<T>(args, uid);
         foreach (var implant in implantContainer.ContainedEntities)
         {
             if (args is HandledEntityEventArgs { Handled : true })
@@ -194,9 +194,12 @@ public sealed class ImplantRelayEvent<T> where T : notnull
 {
     public readonly T Event;
 
-    public ImplantRelayEvent(T ev)
+    public readonly EntityUid ImplantedEntity;
+
+    public ImplantRelayEvent(T ev, EntityUid implantedEntity)
     {
         Event = ev;
+        ImplantedEntity = implantedEntity;
     }
 }
 
index 424bd12bb3f0a0aca733c11cddb074d1d21c5ae9..6f6e55b5ef2d11b6824fa2d98030740f85ada6bd 100644 (file)
@@ -50,25 +50,37 @@ public sealed partial class ItemToggleComponent : Component
     /// /// <remarks>
     /// If server-side systems affect the item's toggle, like charge/fuel systems, then the item is not predictable.
     /// </remarks>
-    [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public bool Predictable = true;
 
     /// <summary>
     ///     The noise this item makes when it is toggled on.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public SoundSpecifier? SoundActivate;
 
     /// <summary>
     ///     The noise this item makes when it is toggled off.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public SoundSpecifier? SoundDeactivate;
 
+    /// <summary>
+    ///     The popup to show to someone activating this item.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId? PopupActivate;
+
+    /// <summary>
+    ///     The popup to show to someone deactivating this item.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId? PopupDeactivate;
+
     /// <summary>
     ///     The noise this item makes when it is toggled on.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public SoundSpecifier? SoundFailToActivate;
 }
 
index 8008ecf9e5bc9cbad5c71c1f594440fabbac8f7b..ff31faaaa1a097fbe1e6298e709718542eccb2e5 100644 (file)
@@ -117,30 +117,30 @@ public sealed class ItemToggleSystem : EntitySystem
     /// Sets its state to the opposite of what it is.
     /// </summary>
     /// <returns>Same as <see cref="TrySetActive"/></returns>
-    public bool Toggle(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
+    public bool Toggle(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true, bool showPopup = true)
     {
         if (!_query.Resolve(ent, ref ent.Comp, false))
             return false;
 
-        return TrySetActive(ent, !ent.Comp.Activated, user, predicted);
+        return TrySetActive(ent, !ent.Comp.Activated, user, predicted, showPopup);
     }
 
     /// <summary>
     /// Tries to set the activated bool from a value.
     /// </summary>
     /// <returns>false if the attempt fails for any reason</returns>
-    public bool TrySetActive(Entity<ItemToggleComponent?> ent, bool active, EntityUid? user = null, bool predicted = true)
+    public bool TrySetActive(Entity<ItemToggleComponent?> ent, bool active, EntityUid? user = null, bool predicted = true, bool showPopup = true)
     {
         if (active)
-            return TryActivate(ent, user, predicted: predicted);
+            return TryActivate(ent, user, predicted: predicted, showPopup);
         else
-            return TryDeactivate(ent, user, predicted: predicted);
+            return TryDeactivate(ent, user, predicted: predicted, showPopup);
     }
 
     /// <summary>
     /// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
     /// </summary>
-    public bool TryActivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
+    public bool TryActivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true, bool showPopup = true)
     {
         if (!_query.Resolve(ent, ref ent.Comp, false))
             return false;
@@ -169,7 +169,7 @@ public sealed class ItemToggleSystem : EntitySystem
             else
                 _audio.PlayPvs(comp.SoundFailToActivate, uid);
 
-            if (attempt.Popup != null && user != null)
+            if (showPopup && attempt.Popup != null && user != null)
             {
                 if (predicted)
                     _popup.PopupClient(attempt.Popup, uid, user.Value);
@@ -180,14 +180,14 @@ public sealed class ItemToggleSystem : EntitySystem
             return false;
         }
 
-        Activate((uid, comp), predicted, user);
+        Activate((uid, comp), predicted, user, showPopup);
         return true;
     }
 
     /// <summary>
     /// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
     /// </summary>
-    public bool TryDeactivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
+    public bool TryDeactivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true, bool showPopup = true)
     {
         if (!_query.Resolve(ent, ref ent.Comp, false))
             return false;
@@ -211,7 +211,7 @@ public sealed class ItemToggleSystem : EntitySystem
             if (attempt.Silent)
                 return false;
 
-            if (attempt.Popup != null && user != null)
+            if (showPopup && attempt.Popup != null && user != null)
             {
                 if (predicted)
                     _popup.PopupClient(attempt.Popup, uid, user.Value);
@@ -222,18 +222,26 @@ public sealed class ItemToggleSystem : EntitySystem
             return false;
         }
 
-        Deactivate((uid, comp), predicted, user);
+        Deactivate((uid, comp), predicted, user, showPopup);
         return true;
     }
 
-    private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
+    private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null, bool showPopup = true)
     {
         var (uid, comp) = ent;
         var soundToPlay = comp.SoundActivate;
         if (predicted)
+        {
             _audio.PlayPredicted(soundToPlay, uid, user);
+            if (showPopup && ent.Comp.PopupActivate != null && user != null)
+                _popup.PopupClient(Loc.GetString(ent.Comp.PopupActivate), user.Value, user.Value);
+        }
         else
+        {
             _audio.PlayPvs(soundToPlay, uid);
+            if (showPopup && ent.Comp.PopupActivate != null && user != null)
+                _popup.PopupEntity(Loc.GetString(ent.Comp.PopupActivate), user.Value, user.Value);
+        }
 
         comp.Activated = true;
         UpdateVisuals((uid, comp));
@@ -246,14 +254,22 @@ public sealed class ItemToggleSystem : EntitySystem
     /// <summary>
     /// Used to make the actual changes to the item's components on deactivation.
     /// </summary>
-    private void Deactivate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
+    private void Deactivate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null, bool showPopup = true)
     {
         var (uid, comp) = ent;
         var soundToPlay = comp.SoundDeactivate;
         if (predicted)
+        {
             _audio.PlayPredicted(soundToPlay, uid, user);
+            if (showPopup && ent.Comp.PopupDeactivate != null && user != null)
+                _popup.PopupClient(Loc.GetString(ent.Comp.PopupDeactivate), user.Value, user.Value);
+        }
         else
+        {
             _audio.PlayPvs(soundToPlay, uid);
+            if (showPopup && ent.Comp.PopupDeactivate != null && user != null)
+                _popup.PopupEntity(Loc.GetString(ent.Comp.PopupDeactivate), user.Value, user.Value);
+        }
 
         comp.Activated = false;
         UpdateVisuals((uid, comp));
index 6d7483802bd5a1418432cb13fe1757e2f975e487..4f48f00b3a9306d33ad0e3fb27e2580a97d1b5d7 100644 (file)
@@ -2,20 +2,20 @@ using Robust.Shared.GameStates;
 
 namespace Content.Shared.Mousetrap;
 
-[RegisterComponent, NetworkedComponent]
+/// <summary>
+/// Component inteded to be used for mouse traps.
+/// Will stop step triggers from happening unless armed via <see cref="Item.ItemToggle.Components.ItemToggleComponent"/>
+/// and will scale damage taken from <see cref="Trigger.Components.Effects.DamageOnTriggerComponent"/>
+/// depending on mass.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class MousetrapComponent : Component
 {
-    [ViewVariables]
-    [DataField("isActive")]
-    public bool IsActive = false;
-
     /// <summary>
-    ///     Set this to change where the
-    ///     inflection point in the scaling
-    ///     equation will occur.
-    ///     The default is 10.
+    /// Set this to change where the
+    /// inflection point in the damage scaling
+    /// equation will occur.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("massBalance")]
+    [DataField, AutoNetworkedField]
     public int MassBalance = 10;
 }
diff --git a/Content.Shared/Mousetrap/MousetrapSystem.cs b/Content.Shared/Mousetrap/MousetrapSystem.cs
new file mode 100644 (file)
index 0000000..96f7411
--- /dev/null
@@ -0,0 +1,42 @@
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Trigger.Systems;
+using Content.Shared.StepTrigger.Systems;
+using Robust.Shared.Physics.Components;
+
+namespace Content.Shared.Mousetrap;
+
+public sealed class MousetrapSystem : EntitySystem
+{
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<MousetrapComponent, BeforeDamageOnTriggerEvent>(BeforeDamageOnTrigger);
+        SubscribeLocalEvent<MousetrapComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
+    }
+
+    // only allow step triggers to trigger if the trap is armed
+    // TODO: refactor Steptriggers to get rid of this
+    // they should just use the new trigger conditions
+    private void OnStepTriggerAttempt(Entity<MousetrapComponent> ent, ref StepTriggerAttemptEvent args)
+    {
+        if (!TryComp<ItemToggleComponent>(ent, out var toggle))
+            return;
+
+        args.Continue |= toggle.Activated;
+    }
+
+    // scale the damage according to mass
+    private void BeforeDamageOnTrigger(Entity<MousetrapComponent> ent, ref BeforeDamageOnTriggerEvent args)
+    {
+        if (TryComp(args.Tripper, out PhysicsComponent? physics) && physics.Mass != 0)
+        {
+            // The idea here is inverse,
+            // Small - big damage,
+            // Large - small damage
+            // yes i punched numbers into a calculator until the graph looked right
+            var scaledDamage = -50 * Math.Atan(physics.Mass - ent.Comp.MassBalance) + 25 * Math.PI;
+            args.Damage *= scaledDamage;
+        }
+    }
+}
diff --git a/Content.Shared/Mousetrap/MousetrapVisuals.cs b/Content.Shared/Mousetrap/MousetrapVisuals.cs
deleted file mode 100644 (file)
index 9685157..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Mousetrap;
-
-[Serializable, NetSerializable]
-public enum MousetrapVisuals : byte
-{
-    Visual,
-    Armed,
-    Unarmed
-}
index 3ba4494cca43b9c776a7f013b72be7f03b733dcf..75053011ffa40c0811e0aa3df9472816f1a62450 100644 (file)
@@ -10,11 +10,21 @@ namespace Content.Shared.Ninja.Components;
 [RegisterComponent, NetworkedComponent, Access(typeof(SharedSpiderChargeSystem))]
 public sealed partial class SpiderChargeComponent : Component
 {
-    /// Range for planting within the target area
+    /// <summary>
+    /// Range for planting within the target area.
+    /// </summary>
     [DataField]
     public float Range = 10f;
 
-    /// The ninja that planted this charge
+    /// <summary>
+    /// The ninja that planted this charge.
+    /// </summary>
     [DataField]
     public EntityUid? Planter;
+
+    /// <summary>
+    /// The trigger that will mark the objective as successful.
+    /// </summary>
+    [DataField]
+    public string TriggerKey = "timer";
 }
index b064e91198c01e8274114338daaa6138b66b8eb9..d07da4b24e0640b3726d79e33e3396da408f5782 100644 (file)
@@ -1,4 +1,5 @@
-using Content.Shared.Explosion.Components;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Components.Triggers;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 
@@ -10,7 +11,7 @@ namespace Content.Shared.Payload.Components;
 /// <remarks>
 ///     This component performs two functions. Firstly, it will add or remove other components to some entity when this
 ///     item is installed inside of it. This is intended for use with constructible grenades. For example, this allows
-///     you to add things like <see cref="OnUseTimerTriggerComponent"/>, or <see cref="TriggerOnProximityComponent"/>.
+///     you to add things like <see cref="TimerTriggerComponent"/>, or <see cref="TriggerOnProximityComponent"/>.
 ///     This is required because otherwise you would have to forward arbitrary interaction directed at the casing
 ///     through to the trigger, which would be quite complicated. Also proximity triggers don't really work inside of
 ///     containers.
@@ -29,7 +30,7 @@ public sealed partial class PayloadTriggerComponent : Component
     /// <summary>
     ///     List of components to add or remove from an entity when this trigger is (un)installed.
     /// </summary>
-    [DataField("components", serverOnly:true, readOnly: true)]
+    [DataField(serverOnly: true, readOnly: true)]
     public ComponentRegistry? Components = null;
 
     /// <summary>
@@ -41,6 +42,6 @@ public sealed partial class PayloadTriggerComponent : Component
     ///     when removing the component, to ensure that removal of this trigger only removes the components that it was
     ///     responsible for adding.
     /// </remarks>
-    [DataField("grantedComponents", serverOnly: true)]
+    [DataField(serverOnly: true)]
     public HashSet<Type> GrantedComponents = new();
 }
index 9a6697cf97abc9ffd026733035dccf89a985f719..c3deca07699bcc94b78fc417390ca9b6b21d12aa 100644 (file)
@@ -1,5 +1,4 @@
-using Content.Shared.Damage.Components;
-using Content.Shared.Actions;
+using Content.Shared.Actions;
 using Content.Shared.Actions.Components;
 using Content.Shared.Alert;
 using Content.Shared.Coordinates;
@@ -9,6 +8,7 @@ using Content.Shared.Mobs;
 using Content.Shared.Movement.Systems;
 using Content.Shared.Slippery;
 using Content.Shared.Toggleable;
+using Content.Shared.Trigger.Components.Effects;
 using Robust.Shared.Audio.Systems;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Events;
@@ -127,7 +127,7 @@ public abstract class SharedRootableSystem : EntitySystem
         if (!ent.Comp.Rooted)
             return;
 
-        if (args.SlipCausingEntity != null && HasComp<DamageUserOnTriggerComponent>(args.SlipCausingEntity))
+        if (args.SlipCausingEntity != null && HasComp<DamageOnTriggerComponent>(args.SlipCausingEntity))
             return;
 
         args.NoSlip = true;
diff --git a/Content.Shared/Speech/Components/ActiveListenerComponent.cs b/Content.Shared/Speech/Components/ActiveListenerComponent.cs
new file mode 100644 (file)
index 0000000..fde108a
--- /dev/null
@@ -0,0 +1,17 @@
+using Content.Shared.Chat;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+/// <summary>
+/// This component is used to relay speech events to other systems.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveListenerComponent : Component
+{
+    /// <summary>
+    /// The range in which to listen to speech.
+    /// </summary>
+    [DataField]
+    public float Range = SharedChatSystem.VoiceRange;
+}
similarity index 93%
rename from Content.Server/Speech/ListenEvent.cs
rename to Content.Shared/Speech/ListenEvent.cs
index b67aa92f65fea52e717d93b4dc48d986a112c1a3..8854bd99f4233d938bd4fa566e782bcd02df223f 100644 (file)
@@ -1,4 +1,4 @@
-namespace Content.Server.Speech;
+namespace Content.Shared.Speech;
 
 public sealed class ListenEvent : EntityEventArgs
 {
index 451309175481eb181014c97c40ca364fa7de9406..a4007b0780ca3ba0fba25285bc02a1575ba0687c 100644 (file)
@@ -1,5 +1,5 @@
 using Content.Shared.Sticky.Systems;
-using Content.Shared.Whitelist;
+using Content.Shared.Whitelist;
 using Robust.Shared.GameStates;
 using Robust.Shared.Utility;
 
index d02752e16b38e28f443e3b3693389bc020fcfd59..0950e8981de68f7b9b680aa0861447603469e21b 100644 (file)
@@ -9,7 +9,7 @@ public sealed class UseDelaySystem : EntitySystem
     [Dependency] private readonly IGameTiming _gameTiming = default!;
     [Dependency] private readonly MetaDataSystem _metadata = default!;
 
-    private const string DefaultId = "default";
+    public const string DefaultId = "default";
 
     public override void Initialize()
     {
diff --git a/Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs b/Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs
new file mode 100644 (file)
index 0000000..d7da5ce
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// Component used for tracking active timers triggers.
+/// Used internally for performance reasons.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveTimerTriggerComponent : Component;
diff --git a/Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs b/Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs
new file mode 100644 (file)
index 0000000..bddbbd4
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// Component used for tracking active two-stage triggers.
+/// Used internally for performance reasons.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveTwoStageTriggerComponent : Component;
diff --git a/Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs
new file mode 100644 (file)
index 0000000..7e77c99
--- /dev/null
@@ -0,0 +1,15 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Base class for components that add a condition to triggers.
+/// </summary>
+public abstract partial class BaseTriggerConditionComponent : Component
+{
+    /// <summary>
+    /// The keys that are checked for the condition.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public HashSet<string> Keys = new() { TriggerSystem.DefaultTriggerKey };
+}
diff --git a/Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs
new file mode 100644 (file)
index 0000000..478f066
--- /dev/null
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Adds an alt verb that can be used to toggle a trigger.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ToggleTriggerConditionComponent : BaseTriggerConditionComponent
+{
+    /// <summary>
+    /// Is the component currently enabled?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Enabled = true;
+
+    /// <summary>
+    /// The text of the toggle verb.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId ToggleVerb = "toggle-trigger-condition-default-verb";
+
+    /// <summary>
+    /// The popup to show when toggled on.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId ToggleOn = "toggle-trigger-condition-default-on";
+
+    /// <summary>
+    /// The popup to show when toggled off.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId ToggleOff = "toggle-trigger-condition-default-off";
+}
diff --git a/Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs
new file mode 100644 (file)
index 0000000..70a3312
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Shared.Timing;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Checks if the triggered entity has an active UseDelay.
+/// </summary>
+/// <remarks>
+/// TODO: Support specific UseDelay IDs for each trigger key.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class UseDelayTriggerConditionComponent : BaseTriggerConditionComponent
+{
+    /// <summary>
+    /// Checks if the triggered entity has an active UseDelay.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string UseDelayId = UseDelaySystem.DefaultId;
+}
diff --git a/Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs b/Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs
new file mode 100644 (file)
index 0000000..a2779f7
--- /dev/null
@@ -0,0 +1,24 @@
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Checks if the user of a trigger satisfies a whitelist and blacklist condition for the triggered entity or the one triggering it.
+/// Cancels the trigger otherwise.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class WhitelistTriggerConditionComponent : BaseTriggerConditionComponent
+{
+    /// <summary>
+    /// Whitelist for what entites can cause this trigger.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityWhitelist? UserWhitelist;
+
+    /// <summary>
+    /// Blacklist for what entites can cause this trigger.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityWhitelist? UserBlacklist;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..06f5258
--- /dev/null
@@ -0,0 +1,37 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Adds the specified components when triggered.
+/// If TargetUser is true they will be added to the user.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AddComponentsOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The list of components that will be added.
+    /// </summary>
+    [DataField(required: true)]
+    public ComponentRegistry Components = new();
+
+    /// <summary>
+    /// If this component has been triggered at least once already.
+    /// If this is true the components have been added.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Triggered = false;
+
+    /// <summary>
+    /// If this effect can only be triggered once.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool TriggerOnce = false;
+
+    /// <summary>
+    /// Should components that already exist on the entity be overwritten?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool RemoveExisting = false;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..0d16134
--- /dev/null
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Changes the alert level of the station when triggered.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AlertLevelChangeOnTriggerComponent : BaseXOnTriggerComponent
+{
+    ///<summary>
+    /// The alert level to change to when triggered.
+    ///</summary>
+    [DataField, AutoNetworkedField]
+    public string Level = "blue";
+
+    /// <summary>
+    /// Whether to play the sound when the alert level changes.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool PlaySound = true;
+
+    /// <summary>
+    /// Whether to say the announcement when the alert level changes.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Announce = true;
+
+    /// <summary>
+    /// Force the alert change. This applies if the alert level is not selectable or not.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Force = false;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..ed61876
--- /dev/null
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will (un)anchor the entity when triggered.
+/// If TargetUser is true they will be (un)anchored instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AnchorOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// Anchor the entity on trigger if it is currently unanchored?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool CanAnchor = true;
+
+    /// <summary>
+    /// Unanchor the entity on trigger if it is currently anchored?
+    /// If both this and CanAnchor are true then the trigger will toggle between states.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool CanUnanchor = false;
+
+    /// <summary>
+    /// Removes this component when triggered so it can only be activated once.
+    /// </summary>
+    /// <remarks>
+    /// TODO: Make this a generic thing for all triggers.
+    /// Or just add a RemoveComponentsOnTriggerComponent.
+    /// </remarks>
+    [DataField, AutoNetworkedField]
+    public bool RemoveOnTrigger = true;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..cf93ad6
--- /dev/null
@@ -0,0 +1,22 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Base class for components that do something when triggered.
+/// </summary>
+public abstract partial class BaseXOnTriggerComponent : Component
+{
+    /// <summary>
+    /// The keys that will activate the effect.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public HashSet<string> KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+    /// <summary>
+    /// Set to true to make the user of the trigger the effect target.
+    /// Set to false to make the owner of this component the target.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool TargetUser = false;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..8d35b01
--- /dev/null
@@ -0,0 +1,25 @@
+using Content.Shared.Damage;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will damage an entity when triggered.
+/// If TargetUser is true it the user will take damage instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class DamageOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// Should the damage ignore resistances?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool IgnoreResistances;
+
+    /// <summary>
+    /// The base damage amount that is dealt.
+    /// May be further modified by <see cref="Systems.BeforeDamageOnTriggerEvent"/> subscriptions.
+    /// </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public DamageSpecifier Damage = default!;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..9d0a5b7
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will delete the entity when triggered.
+/// If TargetUser is true it will delete them instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class DeleteOnTriggerComponent : BaseXOnTriggerComponent;
diff --git a/Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..f387022
--- /dev/null
@@ -0,0 +1,31 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will play a sound in PVS range when triggered.
+/// If TargetUser is true it will be played at their position.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EmitSoundOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The <see cref="SoundSpecifier"/> to play.
+    /// </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public SoundSpecifier? Sound;
+
+    /// <summary>
+    /// Play the sound at the position instead of parented to the source entity.
+    /// Useful if the entity is deleted after.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Positional;
+
+    /// <summary>
+    /// Should this sound be predicted for the User?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Predicted;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..327e9f5
--- /dev/null
@@ -0,0 +1,29 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will cause an EMP at the entity's location when triggered.
+/// If TargetUser is true then it will be spawned at their position.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EmpOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// EMP range.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Range = 1.0f;
+
+    /// <summary>
+    /// How much energy (in Joules) will be consumed per battery in range.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float EnergyConsumption;
+
+    /// <summary>
+    /// How long it disables targets.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan DisableDuration = TimeSpan.FromSeconds(60);
+}
diff --git a/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..2a1af40
--- /dev/null
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will explode using the entity's <see cref="ExplosiveComponent"/> when triggered.
+/// TargetUser will only work of the user has ExplosiveComponent as well.
+/// The User will be logged in the admin logs.
+/// </summary>
+/// <summary>
+/// TODO: Allow this to work without an ExplosiveComponent on the user via QueueExplosion.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ExplodeOnTriggerComponent : BaseXOnTriggerComponent;
diff --git a/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..8b75417
--- /dev/null
@@ -0,0 +1,29 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will cause a flash in an area around the entity when triggered.
+/// If TargetUser is true then their location will be used.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class FlashOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The range in which to flash entities in.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Range = 1.0f;
+
+    /// <summary>
+    /// The duration of the status effect.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan Duration = TimeSpan.FromSeconds(8);
+
+    /// <summary>
+    /// The probability to apply the status effect.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float Probability = 1.0f;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..16c1365
--- /dev/null
@@ -0,0 +1,19 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will kick a player from the server as if their connection dropped if triggered.
+/// Yes, really. Don't use this component.
+/// If TargetUser is true then the user of the trigger will be kicked, otherwise the entity itself.
+/// <see cref="Server.GhostKick.GhostKickManager"/>
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GhostKickOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The reason that will be displayed in the server log when a player is kicked.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId Reason = "ghost-kick-on-trigger-default";
+}
diff --git a/Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..b3475ac
--- /dev/null
@@ -0,0 +1,17 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will gib the entity when triggered.
+/// If TargetUser is true the user will be gibbed instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GibOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// Should gibbing also delete the owners items?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool DeleteItems = false;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..36273ef
--- /dev/null
@@ -0,0 +1,27 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will ignite for a certain length of time when triggered.
+/// Requires <see cref="IgnitionSourceComponent"/> along with triggering components.
+/// The if TargetUser is true they will be ignited instead (they need IgnitionSourceComponent as well).
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class IgniteOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// Once ignited, the time it will unignite at.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan IgnitedUntil = TimeSpan.Zero;
+
+    /// <summary>
+    /// How long the ignition source is active for after triggering.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan IgnitedTime = TimeSpan.FromSeconds(0.5);
+}
diff --git a/Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..4c5360f
--- /dev/null
@@ -0,0 +1,37 @@
+using Content.Shared.Item.ItemToggle.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will toggle an item when triggered. Requires <see cref="ItemToggleComponent"/>.
+/// If TargetUser is true and they have that component they will be toggled instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ItemToggleOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// Can the item be toggled on using the trigger?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool CanActivate = true;
+
+    /// <summary>
+    /// Can the item be toggled on using the trigger?
+    /// If both this and CanActivate are true then the trigger will toggle between states.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool CanDeactivate = true;
+
+    /// <summary>
+    /// Can the audio and popups be predicted?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Predicted = true;
+
+    /// <summary>
+    /// Show a popup to the user when toggling the item?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool ShowPopup = true;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..3d2021e
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Shared.Polymorph;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Polymorphs the enity when triggered.
+/// If TargetUser is true it will polymorph the user instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class PolymorphOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// Polymorph settings.
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<PolymorphPrototype> Polymorph;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..599a643
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Shared.Mobs;
+using Content.Shared.Radio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Sends an emergency message over coms when triggered giving information about the entity's mob status.
+/// If TargetUser is true then the user's mob state will be used instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class RattleOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The radio channel the message will be sent to.
+    /// </summary>
+    [DataField]
+    public ProtoId<RadioChannelPrototype> RadioChannel = "Syndicate";
+
+    /// <summary>
+    /// The message to be send depending on the target's current mob state.
+    /// </summary>
+    [DataField]
+    public Dictionary<MobState, LocId> Messages = new()
+    {
+        {MobState.Critical, "deathrattle-implant-critical-message"},
+        {MobState.Dead, "deathrattle-implant-dead-message"}
+    };
+}
similarity index 90%
rename from Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs
index 28a5c5cf813515d439377693c7f85289fd2a8a00..4edd5f83f4408d9c81b44da65186d62c372e3803 100644 (file)
@@ -1,18 +1,16 @@
 using Content.Shared.Atmos;
-using Content.Shared.Explosion.EntitySystems;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Shared.Explosion.Components.OnTrigger;
+namespace Content.Shared.Trigger.Components.Effects;
 
 /// <summary>
 /// Contains a GasMixture that will release its contents to the atmosphere when triggered.
 /// </summary>
 [RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentPause]
-[Access(typeof(SharedReleaseGasOnTriggerSystem))]
-public sealed partial class ReleaseGasOnTriggerComponent : Component
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class ReleaseGasOnTriggerComponent : BaseXOnTriggerComponent
 {
     /// <summary>
     /// Whether this grenade is active and releasing gas.
similarity index 57%
rename from Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs
index 43febff03bd70dc417e63b1c6a4f3af8b20946ec..68af0bb544f8e67a468c20a917f3dba618006d56 100644 (file)
@@ -1,37 +1,39 @@
 using Content.Shared.Physics;
 using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
 
-namespace Content.Shared.Explosion.Components.OnTrigger;
+namespace Content.Shared.Trigger.Components.Effects;
 
 /// <summary>
-/// Generates a gravity pulse/repulse using the RepulseAttractComponent when the entity is triggered
+/// Generates a gravity pulse/repulse using the RepulseAttractComponent around the entity when triggered.
+/// If TargetUser is true their location will be used instead.
 /// </summary>
-[RegisterComponent]
-public sealed partial class SharedRepulseAttractOnTriggerComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class RepulseAttractOnTriggerComponent : BaseXOnTriggerComponent
 {
     /// <summary>
     /// How fast should the Repulsion/Attraction be?
-    /// A positive value will repulse objects, a negative value will attract
+    /// A positive value will repulse objects, a negative value will attract.
     /// </summary>
-    [DataField]
-    public float Speed;
+    [DataField, AutoNetworkedField]
+    public float Speed = 5.0f;
 
     /// <summary>
     /// How close do the entities need to be?
     /// </summary>
-    [DataField]
-    public float Range;
+    [DataField, AutoNetworkedField]
+    public float Range = 5.0f;
 
     /// <summary>
     /// What kind of entities should this effect apply to?
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public EntityWhitelist? Whitelist;
 
     /// <summary>
     /// What collision layers should be excluded?
     /// The default excludes ghost mobs, revenants, the AI camera etc.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public CollisionGroup CollisionMask = CollisionGroup.GhostImpassable;
 }
diff --git a/Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..e7da7a3
--- /dev/null
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will electrocute the entity when triggered.
+/// If TargetUser is true it will electrocute the user instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ShockOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// Electrocute entity containing this entity instead (for example for wearable clothing).
+    /// Has priority over TargetUser.
+    /// </summary>
+    /// <remarks>
+    /// TODO: Make this more generic so it can be used for all triggers.
+    /// Maybe a BeforeTriggerEvent where we modify the target.
+    /// </remarks>
+    [DataField, AutoNetworkedField]
+    public bool TargetContainer;
+
+    /// <summary>
+    /// The force of an electric shock when the trigger is triggered.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int Damage = 5;
+
+    /// <summary>
+    /// Duration of electric shock when the trigger is triggered.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan Duration = TimeSpan.FromSeconds(2);
+}
diff --git a/Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..0698f2c
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.DeviceLinking;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Sends a device link signal when triggered.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SignalOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The port that gets signaled when the switch turns on.
+    /// </summary>
+    [DataField]
+    public ProtoId<SourcePortPrototype> Port = "Trigger";
+}
similarity index 59%
rename from Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs
index 1138e74af8f7659758a1c399d9056ae6109bfe01..07a5bad056c35907abe4b733507660325febfd08 100644 (file)
@@ -1,34 +1,34 @@
-using Content.Shared.Explosion.EntitySystems;
 using Content.Shared.Chemistry.Components;
 using Robust.Shared.GameStates;
 using Robust.Shared.Prototypes;
 
-namespace Content.Shared.Explosion.Components;
+namespace Content.Shared.Trigger.Components.Effects;
 
 /// <summary>
 /// Creates a smoke cloud when triggered, with an optional solution to include in it.
-/// No sound is played incase a grenade is stealthy, use <see cref="SoundOnTriggerComponent"/> if you want a sound.
+/// No sound is played incase a grenade is stealthy, use <see cref="EmitSoundOnTriggerComponent"/> if you want a sound.
+/// If TargetUser is true the smoke is spawned at their location.
 /// </summary>
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedSmokeOnTriggerSystem))]
-public sealed partial class SmokeOnTriggerComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SmokeOnTriggerComponent : BaseXOnTriggerComponent
 {
     /// <summary>
-    /// How long the smoke stays for, after it has spread.
+    /// How long the smoke stays for, after it has spread (in seconds).
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public float Duration = 10;
+    [DataField, AutoNetworkedField]
+    public TimeSpan Duration = TimeSpan.FromSeconds(10);
 
     /// <summary>
     /// How much the smoke will spread.
     /// </summary>
-    [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+    [DataField(required: true), AutoNetworkedField]
     public int SpreadAmount;
 
     /// <summary>
     /// Smoke entity to spawn.
     /// Defaults to smoke but you can use foam if you want.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField, AutoNetworkedField]
     public EntProtoId SmokePrototype = "Smoke";
 
     /// <summary>
@@ -37,6 +37,6 @@ public sealed partial class SmokeOnTriggerComponent : Component
     /// <remarks>
     /// When using repeating trigger this essentially gets multiplied so dont do anything crazy like omnizine or lexorin.
     /// </remarks>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField, AutoNetworkedField]
     public Solution Solution = new();
 }
diff --git a/Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..782626f
--- /dev/null
@@ -0,0 +1,31 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Spawns a protoype when triggered.
+/// If TargetUser is true it will be spawned at their location.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SpawnOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The prototype to spawn.
+    /// </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public EntProtoId Proto = string.Empty;
+
+    /// <summary>
+    /// Use MapCoordinates for spawning?
+    /// Set to true if you don't want the new entity parented to the spawner.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool UseMapCoords;
+
+    /// <summary>
+    /// Whether or not to use predicted spawning.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Predicted;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..8fe6927
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Shared.Dataset;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Makes the entity speak a message when triggered.
+/// If TargetUser is true then they will be forced to speak instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SpeakOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The text to speak. This has priority over Pack.
+    /// </summary>
+    [DataField]
+    public LocId? Text;
+
+    /// <summary>
+    /// The identifier for the dataset prototype containing messages to be spoken by this entity.
+    /// The spoken text will be picked randomly from it.
+    /// </summary>
+    [DataField]
+    public ProtoId<LocalizedDatasetPrototype>? Pack;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs
new file mode 100644 (file)
index 0000000..4d43c28
--- /dev/null
@@ -0,0 +1,26 @@
+using Content.Shared.Timing;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will activate an UseDelay on the target when triggered.
+/// </summary>
+/// <remarks>
+/// TODO: Support specific UseDelay IDs for each trigger key.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class UseDelayOnTriggerComponent : BaseXOnTriggerComponent
+{
+    /// <summary>
+    /// The UseDelay Id to delay.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string UseDelayId = UseDelaySystem.DefaultId;
+
+    /// <summary>
+    /// If true ongoing delays won't be reset.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool CheckDelayed;
+}
similarity index 50%
rename from Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs
rename to Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs
index 3863b9c313acf2c592f4985ba4927951b975f214..89b5535854f867f6299856471d91ca654827a97b 100644 (file)
@@ -1,22 +1,22 @@
-using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.GameStates;
 
-namespace Content.Server.Explosion.Components;
+namespace Content.Shared.Trigger.Components;
 
 /// <summary>
-/// This is used for randomizing a <see cref="RandomTimerTriggerComponent"/> on MapInit
+/// This is used for randomizing a <see cref="TimerTriggerComponent"/> on MapInit.
 /// </summary>
-[RegisterComponent, Access(typeof(TriggerSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class RandomTimerTriggerComponent : Component
 {
     /// <summary>
     /// The minimum random trigger time.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float Min;
 
     /// <summary>
     /// The maximum random trigger time.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public float Max;
 }
diff --git a/Content.Shared/Trigger/Components/TimerTriggerComponent.cs b/Content.Shared/Trigger/Components/TimerTriggerComponent.cs
new file mode 100644 (file)
index 0000000..9cc58d3
--- /dev/null
@@ -0,0 +1,109 @@
+using Content.Shared.Guidebook;
+using Content.Shared.Trigger.Systems;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+using System.Linq;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// Starts a timer when activated by a trigger.
+/// Will cause a different trigger once the time is over.
+/// Can play a sound while the timer is active.
+/// The time can be set by other components, for example <see cref="RandomTimerTriggerComponent"/>.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TimerTriggerComponent : Component
+{
+    /// <summary>
+    /// The keys that will activate the timer.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public List<string> KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+    /// <summary>
+    /// The key that will trigger once the timer is finished.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string? KeyOut = "timer";
+
+    /// <summary>
+    /// The time after which this timer will trigger after it is activated.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan Delay = TimeSpan.FromSeconds(1);
+
+    /// <summary>
+    /// If not empty, a user can use verbs to configure the delay to one of these options.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public List<TimeSpan> DelayOptions = new();
+
+    /// <summary>
+    /// The time at which this trigger will activate.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan NextTrigger = TimeSpan.Zero;
+
+    /// <summary>
+    /// Time of the next beeping sound.
+    /// </summary>
+    /// <remarks>
+    /// Not networked because it's only used server side.
+    /// </remarks>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoPausedField]
+    public TimeSpan NextBeep = TimeSpan.Zero;
+
+    /// <summary>
+    /// Initial beep delay.
+    /// Defaults to a single BeepInterval if null.
+    /// </summary>
+    /// <remarks>
+    /// Not networked because it's only used server side.
+    /// </remarks>
+    [DataField]
+    public TimeSpan? InitialBeepDelay;
+
+    /// <summary>
+    /// The time between beeps.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan BeepInterval = TimeSpan.FromSeconds(1);
+
+    /// <summary>
+    /// The entity that activated this trigger.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? User;
+
+    /// <summary>
+    /// The beeping sound, if any.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public SoundSpecifier? BeepSound;
+
+    /// <summary>
+    /// Whether you can examine the item to see its timer or not.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Examinable = true;
+
+    /// <summary>
+    /// The popup to show the user when starting the timer, if any.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId? Popup = "timer-trigger-activated";
+
+    #region GuidebookData
+
+    [GuidebookData]
+    public float? ShortestDelayOption => DelayOptions.Count == 0 ? null : (float)DelayOptions.Min().TotalSeconds;
+
+    [GuidebookData]
+    public float? LongestDelayOption => DelayOptions.Count == 0 ? null : (float)DelayOptions.Max().TotalSeconds;
+
+    #endregion GuidebookData
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs b/Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs
new file mode 100644 (file)
index 0000000..88b6891
--- /dev/null
@@ -0,0 +1,6 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveTriggerOnTimedCollideComponent : Component;
diff --git a/Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs b/Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs
new file mode 100644 (file)
index 0000000..1f4807f
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Base class for components that cause a trigger to be activated.
+/// </summary>
+public abstract partial class BaseTriggerOnXComponent : Component
+{
+    /// <summary>
+    /// The key that the trigger will activate.
+    /// null will activate all triggers.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string? KeyOut = TriggerSystem.DefaultTriggerKey;
+}
similarity index 56%
rename from Content.Server/Explosion/Components/RepeatingTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs
index cc08de53f9002cc0053791a441002d948a8966df..27527d7773bf31fc88d896041d17f9ef52eb1b02 100644 (file)
@@ -1,25 +1,26 @@
-using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.GameStates;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Server.Explosion.Components;
+namespace Content.Shared.Trigger.Components.Triggers;
 
 /// <summary>
 /// Constantly triggers after being added to an entity.
 /// </summary>
-[RegisterComponent, Access(typeof(TriggerSystem))]
-[AutoGenerateComponentPause]
-public sealed partial class RepeatingTriggerComponent : Component
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class RepeatingTriggerComponent : BaseTriggerOnXComponent
 {
     /// <summary>
     /// How long to wait between triggers.
     /// The first trigger starts this long after the component is added.
     /// </summary>
-    [DataField]
+    [DataField, AutoNetworkedField]
     public TimeSpan Delay = TimeSpan.FromSeconds(1);
 
     /// <summary>
     /// When the next trigger will be.
     /// </summary>
-    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
-    public TimeSpan NextTrigger;
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan NextTrigger = TimeSpan.Zero;
 }
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs
new file mode 100644 (file)
index 0000000..9dd145b
--- /dev/null
@@ -0,0 +1,17 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when activated in hand or by clicking on the entity.
+/// The user is the player activating it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnActivateComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// Is this interaction a complex interaction?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool RequireComplex = true;
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs
new file mode 100644 (file)
index 0000000..b26f3b6
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when activating an action granted by an implant.
+/// The user is the player activating it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnActivateImplantComponent : BaseTriggerOnXComponent;
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs
new file mode 100644 (file)
index 0000000..a1e234b
--- /dev/null
@@ -0,0 +1,23 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when colliding with another entity.
+/// The user is the entity collided with.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnCollideComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// The fixture with which to collide.
+    /// </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public string FixtureID = string.Empty;
+
+    /// <summary>
+    /// Doesn't trigger if the other colliding fixture is nonhard.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool IgnoreOtherNonHard = true;
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs
new file mode 100644 (file)
index 0000000..40d468e
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when attempting to shoot a gun while it's empty.
+/// The user is the player holding the gun.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnEmptyGunshotComponent : BaseTriggerOnXComponent;
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs
new file mode 100644 (file)
index 0000000..a8dab4e
--- /dev/null
@@ -0,0 +1,35 @@
+using Content.Shared.Mobs;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when this entity's mob state changes.
+/// The user is the entity that caused the state change or the owner depending on the settings.
+/// If added to an implant it will trigger when the implanted entity's mob state changes.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnMobstateChangeComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// What states should trigger this?
+    /// </summary>
+    [DataField(required: true), AutoNetworkedField]
+    public List<MobState> MobState = new();
+
+    /// <summary>
+    /// If true, prevents suicide attempts for the trigger to prevent cheese.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool PreventSuicide = false;
+
+    /// <summary>
+    /// If false, the trigger user will be the entity that caused the mobstate to change.
+    /// If true, the trigger user will the entity that changed its mob state.
+    /// </summary>
+    /// <summary>
+    /// Set this to true for implants that apply an effect on the implanted entity.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool TargetMobstateEntity = true;
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs
new file mode 100644 (file)
index 0000000..047d6f0
--- /dev/null
@@ -0,0 +1,91 @@
+using Content.Shared.Physics;
+using Robust.Shared.GameStates;
+using Robust.Shared.Physics.Collision.Shapes;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Dynamics;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers whenever an entity collides with a fixture attached to the owner of this component.
+/// The user is the entity that collided with the fixture.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TriggerOnProximityComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// The ID if the fixture that is observed for collisions.
+    /// </summary>
+    public const string FixtureID = "trigger-on-proximity-fixture";
+
+    /// <summary>
+    /// Currently colliding entities.
+    /// </summary>
+    [ViewVariables]
+    public readonly Dictionary<EntityUid, PhysicsComponent> Colliding = new();
+
+    /// <summary>
+    /// What is the shape of the proximity fixture?
+    /// </summary>
+    [ViewVariables]
+    [DataField]
+    public IPhysShape Shape = new PhysShapeCircle(2f);
+
+    /// <summary>
+    /// How long the the proximity trigger animation plays for.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
+
+    /// <summary>
+    /// Whether the entity needs to be anchored for the proximity to work.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool RequiresAnchored = true;
+
+    /// <summary>
+    /// Whether the proximity trigger is currently enabled.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Enabled = true;
+
+    /// <summary>
+    /// The minimum delay between repeating triggers.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
+
+    /// <summary>
+    /// When can the trigger run again?
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan NextTrigger = TimeSpan.Zero;
+
+    /// <summary>
+    /// When will the visual state be updated again after activation?
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan NextVisualUpdate = TimeSpan.Zero;
+
+    /// <summary>
+    /// What speed should the other object be moving at to trigger the proximity fixture?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float TriggerSpeed = 3.5f;
+
+    /// <summary>
+    /// If this proximity is triggered should we continually repeat it?
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Repeating = true;
+
+    /// <summary>
+    /// What layer is the trigger fixture on?
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(FlagSerializer<CollisionLayer>))]
+    public int Layer = (int)(CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable);
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs
new file mode 100644 (file)
index 0000000..6ed81c5
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Shared.DeviceLinking;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Sends a trigger when signal is received.
+/// The user is the sender of the signal.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnSignalComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// The sink port prototype we can connect devices to.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public ProtoId<SinkPortPrototype> Port = "Trigger";
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs
new file mode 100644 (file)
index 0000000..b0381ee
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers an entity when someone slipped on it.
+/// The user is the entity that was slipped.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnSlipComponent : BaseTriggerOnXComponent;
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs
new file mode 100644 (file)
index 0000000..c718a20
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when the entity is initialized.
+/// The user is null.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnSpawnComponent : BaseTriggerOnXComponent;
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs
new file mode 100644 (file)
index 0000000..70d33a1
--- /dev/null
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers if a StepTrigger is activated by someone stepping on this entity.
+/// The user is the mob who stepped on it.
+/// </summary>
+/// <remarks>
+/// This is used for entities that want the more generic 'trigger' behavior after a step trigger occurs.
+/// Not done by default, since it's not useful for everything and might cause weird behavior. But it is useful for a lot of stuff like mousetraps.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnStepTriggerComponent : BaseTriggerOnXComponent;
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs
new file mode 100644 (file)
index 0000000..073a64f
--- /dev/null
@@ -0,0 +1,11 @@
+using Content.Shared.Sticky.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when an entity with <see cref="StickyComponent"/> is stuck to something.
+/// The user is the player doing so.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnStuckComponent : BaseTriggerOnXComponent;
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs
new file mode 100644 (file)
index 0000000..185ea7d
--- /dev/null
@@ -0,0 +1,26 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when the entity is overlapped for the specified duration.
+/// The user is the entity that passes the time threshold while colliding.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnTimedCollideComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// The time an entity has to collide until the trigger is activated.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan Threshold = TimeSpan.FromSeconds(1);
+
+    /// <summary>
+    /// A collection of entities that are currently colliding with this, and their own unique accumulator.
+    /// </summary>
+    /// <remarks>
+    /// TODO: Add AutoPausedField and (de)serialize values as time offsets when https://github.com/space-wizards/RobustToolbox/issues/3768 is fixed.
+    /// </remarks>
+    [DataField, AutoNetworkedField]
+    public Dictionary<EntityUid, TimeSpan> Colliding = new();
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs
new file mode 100644 (file)
index 0000000..71d2897
--- /dev/null
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers on use in hand.
+/// The user is the player holding the item.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnUseComponent : BaseTriggerOnXComponent;
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs
new file mode 100644 (file)
index 0000000..463860f
--- /dev/null
@@ -0,0 +1,20 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Starts a trigger when a verb is selected.
+/// The user is the player selecting the verb.
+/// </summary>
+/// <remarks>
+/// TODO: Support multiple verbs and trigger keys.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnVerbComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// The text to display in the verb.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public LocId Text = "trigger-on-verb-default";
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs
new file mode 100644 (file)
index 0000000..a36992d
--- /dev/null
@@ -0,0 +1,47 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Sends a trigger when the keyphrase is heard.
+/// The User is the speaker.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent
+{
+    /// <summary>
+    /// Whether or not the component is actively listening at the moment.
+    /// </summary>
+    [ViewVariables]
+    public bool IsListening => IsRecording || !string.IsNullOrWhiteSpace(KeyPhrase);
+
+    /// <summary>
+    /// The keyphrase that has been set to trigger it.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string? KeyPhrase;
+
+    /// <summary>
+    /// Range in which we listen for the keyphrase.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int ListenRange = 4;
+
+    /// <summary>
+    /// Whether we are currently recording a new keyphrase.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool IsRecording;
+
+    /// <summary>
+    /// Minimum keyphrase length.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int MinLength = 3;
+
+    /// <summary>
+    /// Maximum keyphrase length.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public int MaxLength = 50;
+}
diff --git a/Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs b/Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs
new file mode 100644 (file)
index 0000000..f0161c7
--- /dev/null
@@ -0,0 +1,58 @@
+using Content.Shared.Trigger.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// After being triggered applies the specified components and runs triggers again.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TwoStageTriggerComponent : Component
+{
+    /// <summary>
+    /// The keys that will activate the timer and add the given components (first stage).
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public List<string> KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+    /// <summary>
+    /// The key that will trigger once the timer is finished (second stage).
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public string? KeyOut = "stageTwo";
+
+    /// <summary>
+    /// How long it takes for the second stage to be triggered.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10);
+
+    /// <summary>
+    /// This list of components that will be added on the first trigger.
+    /// </summary>
+    [DataField(required: true)]
+    public ComponentRegistry Components = new();
+
+    /// <summary>
+    /// The time at which the second stage will trigger.
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+    [AutoNetworkedField, AutoPausedField]
+    public TimeSpan? NextTriggerTime;
+
+    /// <summary>
+    /// Has this entity been triggered already?
+    /// Used to prevent the components from being added multiple times.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Triggered = false;
+
+    /// <summary>
+    /// The entity that activated this trigger.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public EntityUid? User;
+}
diff --git a/Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..908307d
--- /dev/null
@@ -0,0 +1,33 @@
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class AddComponentsOnTriggerSystem : EntitySystem
+{
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<AddComponentsOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<AddComponentsOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        if (ent.Comp.TriggerOnce && ent.Comp.Triggered)
+            return;
+
+        EntityManager.AddComponents(target.Value, ent.Comp.Components, ent.Comp.RemoveExisting);
+        ent.Comp.Triggered = true;
+        Dirty(ent);
+
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..8f30c85
--- /dev/null
@@ -0,0 +1,40 @@
+using Content.Shared.Damage;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class DamageOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DamageOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<DamageOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        var damage = new DamageSpecifier(ent.Comp.Damage);
+        var ev = new BeforeDamageOnTriggerEvent(damage, target.Value);
+        RaiseLocalEvent(ent.Owner, ref ev);
+
+        args.Handled |= _damageableSystem.TryChangeDamage(target, ev.Damage, ent.Comp.IgnoreResistances, origin: ent.Owner) is not null;
+    }
+}
+
+/// <summary>
+/// Raised on an entity before it deals damage using DamageOnTriggerComponent.
+/// Used to modify the damage that will be dealt.
+/// </summary>
+[ByRefEvent]
+public record struct BeforeDamageOnTriggerEvent(DamageSpecifier Damage, EntityUid Tripper);
diff --git a/Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..e296ccc
--- /dev/null
@@ -0,0 +1,55 @@
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class EmitSoundOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly INetManager _netMan = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<EmitSoundOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<EmitSoundOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        args.Handled |= TryEmitSound(ent, target.Value, args.User);
+    }
+
+    private bool TryEmitSound(Entity<EmitSoundOnTriggerComponent> ent, EntityUid target, EntityUid? user = null)
+    {
+        if (ent.Comp.Sound == null)
+            return false;
+
+        if (ent.Comp.Positional)
+        {
+            var coords = Transform(target).Coordinates;
+            if (ent.Comp.Predicted)
+                _audio.PlayPredicted(ent.Comp.Sound, coords, user);
+            else if (_netMan.IsServer)
+                _audio.PlayPvs(ent.Comp.Sound, coords);
+        }
+        else
+        {
+            if (ent.Comp.Predicted)
+                _audio.PlayPredicted(ent.Comp.Sound, target, user);
+            else if (_netMan.IsServer)
+                _audio.PlayPvs(ent.Comp.Sound, target);
+        }
+
+        return true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..136c447
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Shared.Emp;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class EmpOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedEmpSystem _emp = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<EmpOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<EmpOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        _emp.EmpPulse(_transform.GetMapCoordinates(target.Value), ent.Comp.Range, ent.Comp.EnergyConsumption, (float)ent.Comp.DisableDuration.TotalSeconds);
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..1c773b7
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Shared.Explosion.EntitySystems;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class ExplodeOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedExplosionSystem _explosion = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<ExplodeOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        _explosion.TriggerExplosive(target.Value, user: args.User);
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..6153e22
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Shared.Flash;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class FlashOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedFlashSystem _flash = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<FlashOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<FlashOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        _flash.FlashArea(target.Value, args.User, ent.Comp.Range, ent.Comp.Duration, probability: ent.Comp.Probability);
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..95ef5ec
--- /dev/null
@@ -0,0 +1,40 @@
+using Content.Shared.Body.Systems;
+using Content.Shared.Inventory;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class GibOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedBodySystem _body = default!;
+    [Dependency] private readonly InventorySystem _inventory = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<GibOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<GibOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        if (ent.Comp.DeleteItems)
+        {
+            var items = _inventory.GetHandOrInventoryEntities(target.Value);
+            foreach (var item in items)
+            {
+                PredictedQueueDel(item);
+            }
+        }
+        _body.GibBody(target.Value, true);
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..9bedc87
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.RepulseAttract;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class RepulseAttractOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly RepulseAttractSystem _repulse = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<RepulseAttractOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<RepulseAttractOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        var position = _transform.GetMapCoordinates(target.Value);
+        _repulse.TryRepulseAttract(position, args.User, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask);
+
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..e1871e0
--- /dev/null
@@ -0,0 +1,36 @@
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Trigger.Systems;
+
+/// <summary>
+/// Releases a gas mixture to the atmosphere when triggered.
+/// Can also release gas over a set timespan to prevent trolling people
+/// with the instant-wall-of-pressure-inator.
+/// </summary>
+public abstract class SharedReleaseGasOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ReleaseGasOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    /// <summary>
+    /// Shrimply sets the component to active when triggered, allowing it to release over time.
+    /// </summary>
+    private void OnTrigger(Entity<ReleaseGasOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        ent.Comp.Active = true;
+        ent.Comp.NextReleaseTime = _timing.CurTime;
+        ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles;
+        _appearance.SetData(ent, ReleaseGasOnTriggerVisuals.Key, true);
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs
new file mode 100644 (file)
index 0000000..c4d34af
--- /dev/null
@@ -0,0 +1,44 @@
+using Content.Shared.Electrocution;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class ShockOnTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly SharedElectrocutionSystem _electrocution = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<ShockOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        EntityUid? target;
+        if (ent.Comp.TargetContainer)
+        {
+            // shock whoever is wearing this clothing item
+            if (!_container.TryGetContainingContainer(ent.Owner, out var container))
+                return;
+            target = container.Owner;
+        }
+        else
+        {
+            target = ent.Comp.TargetUser ? args.User : ent.Owner;
+        }
+
+        if (target == null)
+            return;
+
+        _electrocution.TryDoElectrocution(target.Value, null, ent.Comp.Damage, ent.Comp.Duration, true, ignoreInsulation: true);
+        args.Handled = true;
+    }
+
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs
new file mode 100644 (file)
index 0000000..3825708
--- /dev/null
@@ -0,0 +1,22 @@
+using Content.Shared.Implants.Components;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerOnActivateImplantSystem : EntitySystem
+{
+    [Dependency] private readonly TriggerSystem _trigger = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TriggerOnActivateImplantComponent, ActivateImplantEvent>(OnActivateImplant);
+    }
+
+    private void OnActivateImplant(Entity<TriggerOnActivateImplantComponent> ent, ref ActivateImplantEvent args)
+    {
+        _trigger.Trigger(ent.Owner, args.Performer, ent.Comp.KeyOut);
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs
new file mode 100644 (file)
index 0000000..cc23fa2
--- /dev/null
@@ -0,0 +1,20 @@
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Weapons.Ranged.Events;
+
+namespace Content.Shared.Trigger.Systems;
+public sealed partial class TriggerOnEmptyGunshotSystem : EntitySystem
+{
+    [Dependency] private readonly TriggerSystem _trigger = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TriggerOnEmptyGunshotComponent, OnEmptyGunShotEvent>(OnEmptyGunShot);
+    }
+
+    private void OnEmptyGunShot(Entity<TriggerOnEmptyGunshotComponent> ent, ref OnEmptyGunShotEvent args)
+    {
+        _trigger.Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+    }
+}
similarity index 56%
rename from Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs
rename to Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs
index ccd2a6e3df06eb3052801b326cbf02d2b86d467a..68c109aef983a32d6f63fbc6e53bda363be50100 100644 (file)
@@ -1,20 +1,25 @@
-using Content.Server.Explosion.Components;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Implants;
+using Content.Shared.Implants;
 using Content.Shared.Interaction.Events;
 using Content.Shared.Mobs;
+using Content.Shared.Popups;
+using Content.Shared.Trigger.Components.Triggers;
 
-namespace Content.Server.Explosion.EntitySystems;
+namespace Content.Shared.Trigger.Systems;
 
-public sealed partial class TriggerSystem
+public sealed partial class TriggerOnMobstateChangeSystem : EntitySystem
 {
-    private void InitializeMobstate()
+    [Dependency] private readonly TriggerSystem _trigger = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+    public override void Initialize()
     {
+        base.Initialize();
+
         SubscribeLocalEvent<TriggerOnMobstateChangeComponent, MobStateChangedEvent>(OnMobStateChanged);
         SubscribeLocalEvent<TriggerOnMobstateChangeComponent, SuicideEvent>(OnSuicide);
 
-        SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<SuicideEvent>>(OnSuicideRelay);
         SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<MobStateChangedEvent>>(OnMobStateRelay);
+        SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<SuicideEvent>>(OnSuicideRelay);
     }
 
     private void OnMobStateChanged(EntityUid uid, TriggerOnMobstateChangeComponent component, MobStateChangedEvent args)
@@ -22,25 +27,21 @@ public sealed partial class TriggerSystem
         if (!component.MobState.Contains(args.NewMobState))
             return;
 
-        //This chains Mobstate Changed triggers with OnUseTimerTrigger if they have it
-        //Very useful for things that require a mobstate change and a timer
-        if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
-        {
-            HandleTimerTrigger(
-                uid,
-                args.Origin,
-                timerTrigger.Delay,
-                timerTrigger.BeepInterval,
-                timerTrigger.InitialBeepDelay,
-                timerTrigger.BeepSound);
-        }
-        else
-            Trigger(uid);
+        _trigger.Trigger(uid, component.TargetMobstateEntity ? uid : args.Origin, component.KeyOut);
+    }
+
+    private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<MobStateChangedEvent> args)
+    {
+        if (!component.MobState.Contains(args.Event.NewMobState))
+            return;
+
+        _trigger.Trigger(uid, component.TargetMobstateEntity ? args.ImplantedEntity : args.Event.Origin, component.KeyOut);
     }
 
     /// <summary>
     /// Checks if the user has any implants that prevent suicide to avoid some cheesy strategies
     /// Prevents suicide by handling the event without killing the user
+    /// TODO: This doesn't seem to work at the moment as the event is never checked for being handled.
     /// </summary>
     private void OnSuicide(EntityUid uid, TriggerOnMobstateChangeComponent component, SuicideEvent args)
     {
@@ -50,17 +51,19 @@ public sealed partial class TriggerSystem
         if (!component.PreventSuicide)
             return;
 
-        _popupSystem.PopupEntity(Loc.GetString("suicide-prevented"), args.Victim, args.Victim);
+        _popup.PopupClient(Loc.GetString("suicide-prevented"), args.Victim);
         args.Handled = true;
     }
 
     private void OnSuicideRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<SuicideEvent> args)
     {
-        OnSuicide(uid, component, args.Event);
-    }
+        if (args.Event.Handled)
+            return;
 
-    private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<MobStateChangedEvent> args)
-    {
-        OnMobStateChanged(uid, component, args.Event);
+        if (!component.PreventSuicide)
+            return;
+
+        _popup.PopupClient(Loc.GetString("suicide-prevented"), args.Event.Victim);
+        args.Event.Handled = true;
     }
 }
diff --git a/Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs
new file mode 100644 (file)
index 0000000..6940ea5
--- /dev/null
@@ -0,0 +1,21 @@
+using Content.Shared.Slippery;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerOnSlipSystem : EntitySystem
+{
+    [Dependency] private readonly TriggerSystem _trigger = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlip);
+    }
+
+    private void OnSlip(Entity<TriggerOnSlipComponent> ent, ref SlipEvent args)
+    {
+        _trigger.Trigger(ent.Owner, args.Slipped, ent.Comp.KeyOut);
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs
new file mode 100644 (file)
index 0000000..d364adc
--- /dev/null
@@ -0,0 +1,21 @@
+using Content.Shared.Sticky;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class TriggerOnStuckSystem : EntitySystem
+{
+    [Dependency] private readonly TriggerSystem _trigger = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TriggerOnStuckComponent, EntityStuckEvent>(OnStuck);
+    }
+
+    private void OnStuck(Entity<TriggerOnStuckComponent> ent, ref EntityStuckEvent args)
+    {
+        _trigger.Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs
new file mode 100644 (file)
index 0000000..d5830dd
--- /dev/null
@@ -0,0 +1,31 @@
+using Content.Shared.Verbs;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerOnVerbSystem : EntitySystem
+{
+    [Dependency] private readonly TriggerSystem _trigger = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TriggerOnVerbComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
+    }
+
+    private void OnGetAltVerbs(Entity<TriggerOnVerbComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess || args.Hands == null)
+            return;
+
+        var user = args.User;
+
+        args.Verbs.Add(new AlternativeVerb
+        {
+            Text = Loc.GetString(ent.Comp.Text),
+            Act = () => _trigger.Trigger(ent.Owner, user, ent.Comp.KeyOut),
+            Priority = 2 // should be above any timer settings
+        });
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs
new file mode 100644 (file)
index 0000000..5243b13
--- /dev/null
@@ -0,0 +1,73 @@
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.StepTrigger.Systems;
+using Robust.Shared.Physics.Events;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+    private void InitializeCollide()
+    {
+        SubscribeLocalEvent<TriggerOnCollideComponent, StartCollideEvent>(OnCollide);
+        SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered);
+
+        SubscribeLocalEvent<TriggerOnTimedCollideComponent, StartCollideEvent>(OnTimedCollide);
+        SubscribeLocalEvent<TriggerOnTimedCollideComponent, EndCollideEvent>(OnTimedEndCollide);
+        SubscribeLocalEvent<TriggerOnTimedCollideComponent, ComponentShutdown>(OnTimedShutdown);
+    }
+
+    private void OnCollide(Entity<TriggerOnCollideComponent> ent, ref StartCollideEvent args)
+    {
+        if (args.OurFixtureId == ent.Comp.FixtureID && (!ent.Comp.IgnoreOtherNonHard || args.OtherFixture.Hard))
+            Trigger(ent.Owner, args.OtherEntity, ent.Comp.KeyOut);
+    }
+
+    private void OnStepTriggered(Entity<TriggerOnStepTriggerComponent> ent, ref StepTriggeredOffEvent args)
+    {
+        Trigger(ent, args.Tripper, ent.Comp.KeyOut);
+    }
+
+    private void OnTimedCollide(Entity<TriggerOnTimedCollideComponent> ent, ref StartCollideEvent args)
+    {
+        //Ensures the trigger entity will have an active component
+        EnsureComp<ActiveTriggerOnTimedCollideComponent>(ent);
+        var otherUID = args.OtherEntity;
+        if (ent.Comp.Colliding.ContainsKey(otherUID))
+            return;
+        ent.Comp.Colliding.Add(otherUID, _timing.CurTime + ent.Comp.Threshold);
+        Dirty(ent);
+    }
+
+    private void OnTimedEndCollide(Entity<TriggerOnTimedCollideComponent> ent, ref EndCollideEvent args)
+    {
+        var otherUID = args.OtherEntity;
+        ent.Comp.Colliding.Remove(otherUID);
+        Dirty(ent);
+
+        if (ent.Comp.Colliding.Count == 0)
+            RemComp<ActiveTriggerOnTimedCollideComponent>(ent);
+    }
+
+    private void OnTimedShutdown(Entity<TriggerOnTimedCollideComponent> ent, ref ComponentShutdown args)
+    {
+        RemComp<ActiveTriggerOnTimedCollideComponent>(ent);
+    }
+
+    private void UpdateTimedCollide()
+    {
+        var curTime = _timing.CurTime;
+        var query = EntityQueryEnumerator<ActiveTriggerOnTimedCollideComponent, TriggerOnTimedCollideComponent>();
+        while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide))
+        {
+            foreach (var (collidingEntity, collidingTime) in triggerOnTimedCollide.Colliding)
+            {
+                if (curTime > collidingTime)
+                {
+                    triggerOnTimedCollide.Colliding[collidingEntity] += triggerOnTimedCollide.Threshold;
+                    Dirty(uid, triggerOnTimedCollide);
+                    Trigger(uid, collidingEntity, triggerOnTimedCollide.KeyOut);
+                }
+            }
+        }
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Condition.cs
new file mode 100644 (file)
index 0000000..a917f1a
--- /dev/null
@@ -0,0 +1,57 @@
+using Content.Shared.Trigger.Components.Conditions;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+    private void InitializeCondition()
+    {
+        SubscribeLocalEvent<WhitelistTriggerConditionComponent, AttemptTriggerEvent>(OnWhitelistTriggerAttempt);
+
+        SubscribeLocalEvent<UseDelayTriggerConditionComponent, AttemptTriggerEvent>(OnUseDelayTriggerAttempt);
+
+        SubscribeLocalEvent<ToggleTriggerConditionComponent, AttemptTriggerEvent>(OnToggleTriggerAttempt);
+        SubscribeLocalEvent<ToggleTriggerConditionComponent, GetVerbsEvent<AlternativeVerb>>(OnToggleGetAltVerbs);
+    }
+
+    private void OnWhitelistTriggerAttempt(Entity<WhitelistTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
+    {
+        if (args.Key == null || ent.Comp.Keys.Contains(args.Key))
+            args.Cancelled |= !_whitelist.CheckBoth(args.User, ent.Comp.UserBlacklist, ent.Comp.UserWhitelist);
+    }
+
+    private void OnUseDelayTriggerAttempt(Entity<UseDelayTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
+    {
+        if (args.Key == null || ent.Comp.Keys.Contains(args.Key))
+            args.Cancelled |= _useDelay.IsDelayed(ent.Owner, ent.Comp.UseDelayId);
+    }
+
+    private void OnToggleTriggerAttempt(Entity<ToggleTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
+    {
+        if (args.Key == null || ent.Comp.Keys.Contains(args.Key))
+            args.Cancelled |= !ent.Comp.Enabled;
+    }
+
+    private void OnToggleGetAltVerbs(Entity<ToggleTriggerConditionComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess || args.Hands == null)
+            return;
+
+        var user = args.User;
+
+        args.Verbs.Add(new AlternativeVerb()
+        {
+            Text = Loc.GetString(ent.Comp.ToggleVerb),
+            Act = () => Toggle(ent, user)
+        });
+    }
+
+    private void Toggle(Entity<ToggleTriggerConditionComponent> ent, EntityUid user)
+    {
+        var msg = ent.Comp.Enabled ? ent.Comp.ToggleOff : ent.Comp.ToggleOn;
+        _popup.PopupPredicted(Loc.GetString(msg), ent.Owner, user);
+        ent.Comp.Enabled = !ent.Comp.Enabled;
+        Dirty(ent);
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Interaction.cs
new file mode 100644 (file)
index 0000000..f506909
--- /dev/null
@@ -0,0 +1,96 @@
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+    private void InitializeInteraction()
+    {
+        SubscribeLocalEvent<TriggerOnActivateComponent, ActivateInWorldEvent>(OnActivate);
+        SubscribeLocalEvent<TriggerOnUseComponent, UseInHandEvent>(OnUse);
+
+        SubscribeLocalEvent<ItemToggleOnTriggerComponent, TriggerEvent>(HandleItemToggleOnTrigger);
+        SubscribeLocalEvent<AnchorOnTriggerComponent, TriggerEvent>(HandleAnchorOnTrigger);
+        SubscribeLocalEvent<UseDelayOnTriggerComponent, TriggerEvent>(HandleUseDelayOnTrigger);
+    }
+
+    private void OnActivate(Entity<TriggerOnActivateComponent> ent, ref ActivateInWorldEvent args)
+    {
+        if (args.Handled)
+            return;
+
+        if (ent.Comp.RequireComplex && !args.Complex)
+            return;
+
+        Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+        args.Handled = true;
+    }
+
+    private void OnUse(Entity<TriggerOnUseComponent> ent, ref UseInHandEvent args)
+    {
+        if (args.Handled)
+            return;
+
+        Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+        args.Handled = true;
+    }
+
+    private void HandleItemToggleOnTrigger(Entity<ItemToggleOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (!TryComp<ItemToggleComponent>(target, out var itemToggle))
+            return;
+
+        var handled = false;
+        if (itemToggle.Activated && ent.Comp.CanDeactivate)
+            handled = _itemToggle.TryDeactivate((target.Value, itemToggle), args.User, ent.Comp.Predicted, ent.Comp.ShowPopup);
+        else if (ent.Comp.CanActivate)
+            handled = _itemToggle.TryActivate((target.Value, itemToggle), args.User, ent.Comp.Predicted, ent.Comp.ShowPopup);
+
+        args.Handled |= handled;
+    }
+
+    private void HandleAnchorOnTrigger(Entity<AnchorOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        var xform = Transform(target.Value);
+
+        if (xform.Anchored && ent.Comp.CanUnanchor)
+            _transform.Unanchor(target.Value, xform);
+        else if (ent.Comp.CanAnchor)
+            _transform.AnchorEntity(target.Value, xform);
+
+        if (ent.Comp.RemoveOnTrigger)
+            RemCompDeferred<AnchorOnTriggerComponent>(target.Value);
+
+        args.Handled = true;
+    }
+
+    private void HandleUseDelayOnTrigger(Entity<UseDelayOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        args.Handled |= _useDelay.TryResetDelay(target.Value, ent.Comp.CheckDelayed);
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Proximity.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Proximity.cs
new file mode 100644 (file)
index 0000000..cf7c113
--- /dev/null
@@ -0,0 +1,138 @@
+using Content.Shared.Trigger.Components.Triggers;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Events;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+    private void InitializeProximity()
+    {
+        SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide);
+        SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide);
+        SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit);
+        // Shouldn't need re-anchoring.
+        SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor);
+    }
+
+    private void OnProximityAnchor(Entity<TriggerOnProximityComponent> ent, ref AnchorStateChangedEvent args)
+    {
+        ent.Comp.Enabled = !ent.Comp.RequiresAnchored || args.Anchored;
+
+        SetProximityAppearance(ent);
+
+        if (!ent.Comp.Enabled)
+        {
+            ent.Comp.Colliding.Clear();
+        }
+        // Re-check for contacts as we cleared them.
+        else if (TryComp<PhysicsComponent>(ent, out var body))
+        {
+            _physics.RegenerateContacts((ent.Owner, body));
+        }
+
+        Dirty(ent);
+    }
+
+    private void OnMapInit(Entity<TriggerOnProximityComponent> ent, ref MapInitEvent args)
+    {
+        ent.Comp.Enabled = !ent.Comp.RequiresAnchored || Transform(ent).Anchored;
+
+        SetProximityAppearance(ent);
+
+        if (!TryComp<PhysicsComponent>(ent, out var body))
+            return;
+
+        _fixture.TryCreateFixture(
+            ent.Owner,
+            ent.Comp.Shape,
+            TriggerOnProximityComponent.FixtureID,
+            hard: false,
+            body: body,
+            collisionLayer: ent.Comp.Layer);
+
+        Dirty(ent);
+    }
+
+    private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args)
+    {
+        if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
+            return;
+
+        component.Colliding[args.OtherEntity] = args.OtherBody;
+    }
+
+    private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args)
+    {
+        if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
+            return;
+
+        component.Colliding.Remove(args.OtherEntity);
+    }
+
+    private void SetProximityAppearance(Entity<TriggerOnProximityComponent> ent)
+    {
+        _appearance.SetData(ent.Owner, ProximityTriggerVisualState.State, ent.Comp.Enabled ? ProximityTriggerVisuals.Inactive : ProximityTriggerVisuals.Off);
+    }
+
+    private void Activate(Entity<TriggerOnProximityComponent> ent, EntityUid user)
+    {
+        var curTime = _timing.CurTime;
+
+        if (!ent.Comp.Repeating)
+        {
+            ent.Comp.Enabled = false;
+            ent.Comp.Colliding.Clear();
+        }
+        else
+        {
+            ent.Comp.NextTrigger = curTime + ent.Comp.Cooldown;
+        }
+
+        // Queue a visual update for when the animation is complete.
+        ent.Comp.NextVisualUpdate = curTime + ent.Comp.AnimationDuration;
+        Dirty(ent);
+
+        _appearance.SetData(ent.Owner, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active);
+
+        Trigger(ent.Owner, user, ent.Comp.KeyOut);
+    }
+
+    private void UpdateProximity()
+    {
+        var curTime = _timing.CurTime;
+
+        var query = EntityQueryEnumerator<TriggerOnProximityComponent>();
+        while (query.MoveNext(out var uid, out var trigger))
+        {
+            if (curTime >= trigger.NextVisualUpdate)
+            {
+                // Update the visual state once the animation is done.
+                trigger.NextVisualUpdate = TimeSpan.MaxValue;
+                Dirty(uid, trigger);
+                SetProximityAppearance((uid, trigger));
+            }
+
+            if (!trigger.Enabled)
+                continue;
+
+            if (curTime < trigger.NextTrigger)
+                // The trigger's on cooldown.
+                continue;
+
+            // Check for anything colliding and moving fast enough.
+            foreach (var (collidingUid, colliding) in trigger.Colliding)
+            {
+                if (TerminatingOrDeleted(collidingUid))
+                    continue;
+
+                if (colliding.LinearVelocity.Length() < trigger.TriggerSpeed)
+                    continue;
+
+                // Trigger!
+                Activate((uid, trigger), collidingUid);
+                break;
+            }
+        }
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Signal.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Signal.cs
new file mode 100644 (file)
index 0000000..fa5aa7e
--- /dev/null
@@ -0,0 +1,44 @@
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.DeviceLinking.Events;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+    private void InitializeSignal()
+    {
+        SubscribeLocalEvent<SignalOnTriggerComponent, ComponentInit>(SignalOnTriggerInit);
+        SubscribeLocalEvent<TriggerOnSignalComponent, ComponentInit>(TriggerOnSignalInit);
+
+        SubscribeLocalEvent<SignalOnTriggerComponent, TriggerEvent>(HandleSignalOnTrigger);
+        SubscribeLocalEvent<TriggerOnSignalComponent, SignalReceivedEvent>(OnSignalReceived);
+    }
+
+    private void SignalOnTriggerInit(Entity<SignalOnTriggerComponent> ent, ref ComponentInit args)
+    {
+        _deviceLink.EnsureSourcePorts(ent.Owner, ent.Comp.Port);
+    }
+
+    private void TriggerOnSignalInit(Entity<TriggerOnSignalComponent> ent, ref ComponentInit args)
+    {
+        _deviceLink.EnsureSinkPorts(ent.Owner, ent.Comp.Port);
+    }
+
+    private void HandleSignalOnTrigger(Entity<SignalOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        _deviceLink.InvokePort(ent.Owner, ent.Comp.Port);
+        args.Handled = true;
+    }
+
+    private void OnSignalReceived(Entity<TriggerOnSignalComponent> ent, ref SignalReceivedEvent args)
+    {
+        if (args.Port != ent.Comp.Port)
+            return;
+
+        Trigger(ent.Owner, args.Trigger, ent.Comp.KeyOut);
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Spawn.cs
new file mode 100644 (file)
index 0000000..edcdd03
--- /dev/null
@@ -0,0 +1,70 @@
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+
+    private void InitializeSpawn()
+    {
+        SubscribeLocalEvent<TriggerOnSpawnComponent, MapInitEvent>(OnSpawnInit);
+
+        SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(HandleSpawnOnTrigger);
+        SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteOnTrigger);
+    }
+
+    private void OnSpawnInit(Entity<TriggerOnSpawnComponent> ent, ref MapInitEvent args)
+    {
+        Trigger(ent.Owner, null, ent.Comp.KeyOut);
+    }
+
+    private void HandleSpawnOnTrigger(Entity<SpawnOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        var xform = Transform(target.Value);
+
+        if (ent.Comp.UseMapCoords)
+        {
+            var mapCoords = _transform.GetMapCoordinates(target.Value, xform);
+            if (ent.Comp.Predicted)
+                EntityManager.PredictedSpawn(ent.Comp.Proto, mapCoords);
+            else if (_net.IsServer)
+                Spawn(ent.Comp.Proto, mapCoords);
+
+        }
+        else
+        {
+            var coords = xform.Coordinates;
+            if (!coords.IsValid(EntityManager))
+                return;
+
+            if (ent.Comp.Predicted)
+                PredictedSpawnAttachedTo(ent.Comp.Proto, coords);
+            else if (_net.IsServer)
+                SpawnAttachedTo(ent.Comp.Proto, coords);
+
+        }
+    }
+
+    private void HandleDeleteOnTrigger(Entity<DeleteOnTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+        if (target == null)
+            return;
+
+        PredictedQueueDel(target);
+        args.Handled = true;
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Timer.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Timer.cs
new file mode 100644 (file)
index 0000000..776b17b
--- /dev/null
@@ -0,0 +1,179 @@
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Examine;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+    private void InitializeTimer()
+    {
+        SubscribeLocalEvent<RepeatingTriggerComponent, MapInitEvent>(OnRepeatInit);
+        SubscribeLocalEvent<RandomTimerTriggerComponent, MapInitEvent>(OnRandomInit);
+        SubscribeLocalEvent<TimerTriggerComponent, ComponentShutdown>(OnTimerShutdown);
+        SubscribeLocalEvent<TimerTriggerComponent, ExaminedEvent>(OnTimerExamined);
+        SubscribeLocalEvent<TimerTriggerComponent, TriggerEvent>(OnTimerTriggered);
+        SubscribeLocalEvent<TimerTriggerComponent, GetVerbsEvent<AlternativeVerb>>(OnTimerGetAltVerbs);
+    }
+
+    // set the time of the first trigger after being spawned
+    private void OnRepeatInit(Entity<RepeatingTriggerComponent> ent, ref MapInitEvent args)
+    {
+        ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay;
+        Dirty(ent);
+    }
+
+    private void OnRandomInit(Entity<RandomTimerTriggerComponent> ent, ref MapInitEvent args)
+    {
+        if (_net.IsClient) // Nextfloat will mispredict, so we set it on the server and dirty it
+            return;
+
+        if (!TryComp<TimerTriggerComponent>(ent, out var timerTriggerComp))
+            return;
+
+        timerTriggerComp.Delay = TimeSpan.FromSeconds(_random.NextFloat(ent.Comp.Min, ent.Comp.Max));
+        Dirty(ent.Owner, timerTriggerComp);
+    }
+
+    private void OnTimerShutdown(Entity<TimerTriggerComponent> ent, ref ComponentShutdown args)
+    {
+        RemComp<ActiveTimerTriggerComponent>(ent);
+    }
+
+    private void OnTimerExamined(Entity<TimerTriggerComponent> ent, ref ExaminedEvent args)
+    {
+        if (args.IsInDetailsRange && ent.Comp.Examinable)
+            args.PushText(Loc.GetString("timer-trigger-examine", ("time", ent.Comp.Delay.TotalSeconds)));
+    }
+
+    private void OnTimerTriggered(Entity<TimerTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        args.Handled |= ActivateTimerTrigger(ent.AsNullable(), args.User);
+    }
+
+    /// <summary>
+    /// Add an alt-click interaction that cycles through delays.
+    /// </summary>
+    private void OnTimerGetAltVerbs(Entity<TimerTriggerComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess || args.Hands == null)
+            return;
+
+        if (ent.Comp.DelayOptions == null || ent.Comp.DelayOptions.Count == 1)
+            return;
+
+        var user = args.User;
+
+        args.Verbs.Add(new AlternativeVerb
+        {
+            Category = TimerOptions,
+            Text = Loc.GetString("timer-trigger-verb-cycle"),
+            Act = () => CycleDelay(ent, user),
+            Priority = 1
+        });
+
+        foreach (var option in ent.Comp.DelayOptions)
+        {
+            if (MathHelper.CloseTo(option.TotalSeconds, ent.Comp.Delay.TotalSeconds))
+            {
+                args.Verbs.Add(new AlternativeVerb
+                {
+                    Category = TimerOptions,
+                    Text = Loc.GetString("timer-trigger-verb-set-current", ("time", option.TotalSeconds)),
+                    Disabled = true,
+                    Priority = -100 * (int)option.TotalSeconds
+                });
+            }
+            else
+            {
+                args.Verbs.Add(new AlternativeVerb
+                {
+                    Category = TimerOptions,
+                    Text = Loc.GetString("timer-trigger-verb-set", ("time", option.TotalSeconds)),
+                    Priority = -100 * (int)option.TotalSeconds,
+                    Act = () =>
+                    {
+                        ent.Comp.Delay = option;
+                        Dirty(ent);
+                        _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option.TotalSeconds)), user, user);
+                    }
+                });
+            }
+        }
+    }
+
+    public static readonly VerbCategory TimerOptions = new("verb-categories-timer", "/Textures/Interface/VerbIcons/clock.svg.192dpi.png");
+
+    /// <summary>
+    /// Select the next entry from the DelayOptions.
+    /// </summary>
+    private void CycleDelay(Entity<TimerTriggerComponent> ent, EntityUid? user)
+    {
+        if (ent.Comp.DelayOptions.Count <= 1)
+            return;
+
+        // This is somewhat inefficient, but its good enough. This is run rarely, and the lists should be short.
+
+        ent.Comp.DelayOptions.Sort();
+        Dirty(ent);
+
+        if (ent.Comp.DelayOptions[^1] <= ent.Comp.Delay)
+        {
+            ent.Comp.Delay = ent.Comp.DelayOptions[0];
+            _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", ent.Comp.Delay)), ent.Owner, user);
+            return;
+        }
+
+        foreach (var option in ent.Comp.DelayOptions)
+        {
+            if (option > ent.Comp.Delay)
+            {
+                ent.Comp.Delay = option;
+                _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option)), ent.Owner, user);
+                return;
+            }
+        }
+    }
+
+    private void UpdateRepeat()
+    {
+        var curTime = _timing.CurTime;
+        var query = EntityQueryEnumerator<RepeatingTriggerComponent>();
+        while (query.MoveNext(out var uid, out var comp))
+        {
+            if (comp.NextTrigger > curTime)
+                continue;
+
+            comp.NextTrigger += comp.Delay;
+            Dirty(uid, comp);
+            Trigger(uid, null, comp.KeyOut);
+        }
+    }
+
+    private void UpdateTimer()
+    {
+        var curTime = _timing.CurTime;
+        var query = EntityQueryEnumerator<ActiveTimerTriggerComponent, TimerTriggerComponent>();
+        while (query.MoveNext(out var uid, out _, out var timer))
+        {
+            if (_net.IsServer && timer.BeepSound != null && timer.NextBeep <= curTime)
+            {
+                _audio.PlayPvs(timer.BeepSound, uid);
+                timer.NextBeep += timer.BeepInterval;
+            }
+
+            if (timer.NextTrigger <= curTime)
+            {
+                Trigger(uid, timer.User, timer.KeyOut);
+                // Remove after triggering to prevent it from starting the timer again
+                RemComp<ActiveTimerTriggerComponent>(uid);
+                if (TryComp<AppearanceComponent>(uid, out var appearance))
+                    _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
+            }
+        }
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs b/Content.Shared/Trigger/Systems/TriggerSystem.Voice.cs
new file mode 100644 (file)
index 0000000..ac67cb7
--- /dev/null
@@ -0,0 +1,160 @@
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
+using Content.Shared.Database;
+using Content.Shared.Examine;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+    private void InitializeVoice()
+    {
+        SubscribeLocalEvent<TriggerOnVoiceComponent, ComponentInit>(OnVoiceInit);
+        SubscribeLocalEvent<TriggerOnVoiceComponent, ExaminedEvent>(OnVoiceExamine);
+        SubscribeLocalEvent<TriggerOnVoiceComponent, ListenEvent>(OnListen);
+        SubscribeLocalEvent<TriggerOnVoiceComponent, GetVerbsEvent<AlternativeVerb>>(OnVoiceGetAltVerbs);
+    }
+
+    private void OnVoiceInit(Entity<TriggerOnVoiceComponent> ent, ref ComponentInit args)
+    {
+        if (ent.Comp.IsListening)
+            EnsureComp<ActiveListenerComponent>(ent).Range = ent.Comp.ListenRange;
+        else
+            RemCompDeferred<ActiveListenerComponent>(ent);
+    }
+
+    private void OnVoiceExamine(Entity<TriggerOnVoiceComponent> ent, ref ExaminedEvent args)
+    {
+        if (args.IsInDetailsRange)
+        {
+            args.PushText(string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase)
+                ? Loc.GetString("trigger-on-voice-uninitialized")
+                : Loc.GetString("trigger-on-voice-examine", ("keyphrase", ent.Comp.KeyPhrase)));
+        }
+    }
+    private void OnListen(Entity<TriggerOnVoiceComponent> ent, ref ListenEvent args)
+    {
+        var component = ent.Comp;
+        var message = args.Message.Trim();
+
+        if (component.IsRecording)
+        {
+            var ev = new ListenAttemptEvent(args.Source);
+            RaiseLocalEvent(ent, ev);
+
+            if (ev.Cancelled)
+                return;
+
+            if (message.Length >= component.MinLength && message.Length <= component.MaxLength)
+                FinishRecording(ent, args.Source, args.Message);
+            else if (message.Length > component.MaxLength)
+                _popup.PopupEntity(Loc.GetString("trigger-on-voice-record-failed-too-long"), ent);
+            else if (message.Length < component.MinLength)
+                _popup.PopupEntity(Loc.GetString("trigger-on-voice-record-failed-too-short"), ent);
+
+            return;
+        }
+
+        if (!string.IsNullOrWhiteSpace(component.KeyPhrase) && message.IndexOf(component.KeyPhrase, StringComparison.InvariantCultureIgnoreCase) is var index and >= 0)
+        {
+            _adminLogger.Add(LogType.Trigger, LogImpact.Medium,
+                    $"A voice-trigger on {ToPrettyString(ent):entity} was triggered by {ToPrettyString(args.Source):speaker} speaking the key-phrase {component.KeyPhrase}.");
+            Trigger(ent, args.Source, ent.Comp.KeyOut);
+
+            var messageWithoutPhrase = message.Remove(index, component.KeyPhrase.Length).Trim();
+            var voice = new VoiceTriggeredEvent(args.Source, message, messageWithoutPhrase);
+            RaiseLocalEvent(ent, ref voice);
+        }
+    }
+
+    private void OnVoiceGetAltVerbs(Entity<TriggerOnVoiceComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess)
+            return;
+
+        var user = args.User;
+        args.Verbs.Add(new AlternativeVerb
+        {
+            Text = Loc.GetString(ent.Comp.IsRecording ? "trigger-on-voice-stop" : "trigger-on-voice-record"),
+            Act = () =>
+            {
+                if (ent.Comp.IsRecording)
+                    StopRecording(ent, user);
+                else
+                    StartRecording(ent, user);
+            },
+            Priority = 1
+        });
+
+        if (string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase))
+            return;
+
+        args.Verbs.Add(new AlternativeVerb
+        {
+            Text = Loc.GetString("trigger-on-voice-clear"),
+            Act = () =>
+            {
+                ClearRecording(ent);
+            }
+        });
+    }
+
+    /// <summary>
+    /// Start recording a new keyphrase.
+    /// </summary>
+    public void StartRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid? user)
+    {
+        ent.Comp.IsRecording = true;
+        Dirty(ent);
+        EnsureComp<ActiveListenerComponent>(ent).Range = ent.Comp.ListenRange;
+
+        if (user == null)
+            _adminLogger.Add(LogType.Trigger, LogImpact.Low, $"A voice-trigger on {ToPrettyString(ent):entity} has started recording.");
+        else
+            _adminLogger.Add(LogType.Trigger, LogImpact.Low, $"A voice-trigger on {ToPrettyString(ent):entity} has started recording. User: {ToPrettyString(user.Value):user}");
+
+        _popup.PopupPredicted(Loc.GetString("trigger-on-voice-start-recording"), ent, user);
+    }
+
+    /// <summary>
+    /// Stop recording without setting a keyphrase.
+    /// </summary>
+    public void StopRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid? user)
+    {
+        ent.Comp.IsRecording = false;
+        Dirty(ent);
+        if (string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase))
+            RemComp<ActiveListenerComponent>(ent);
+
+        _popup.PopupPredicted(Loc.GetString("trigger-on-voice-stop-recording"), ent, user);
+    }
+
+
+    /// <summary>
+    /// Stop recording and set the current keyphrase message.
+    /// </summary>
+    public void FinishRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid source, string message)
+    {
+        ent.Comp.KeyPhrase = message;
+        ent.Comp.IsRecording = false;
+        Dirty(ent);
+
+        _adminLogger.Add(LogType.Trigger, LogImpact.Low,
+                $"A voice-trigger on {ToPrettyString(ent):entity} has recorded a new keyphrase: '{ent.Comp.KeyPhrase}'. Recorded from {ToPrettyString(source):speaker}");
+
+        _popup.PopupEntity(Loc.GetString("trigger-on-voice-recorded", ("keyphrase", ent.Comp.KeyPhrase)), ent);
+    }
+
+    /// <summary>
+    /// Resets the key phrase and stops recording.
+    /// </summary>
+    public void ClearRecording(Entity<TriggerOnVoiceComponent> ent)
+    {
+        ent.Comp.KeyPhrase = null;
+        ent.Comp.IsRecording = false;
+        Dirty(ent);
+        RemComp<ActiveListenerComponent>(ent);
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerSystem.cs b/Content.Shared/Trigger/Systems/TriggerSystem.cs
new file mode 100644 (file)
index 0000000..6a749b8
--- /dev/null
@@ -0,0 +1,186 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Popups;
+using Content.Shared.Timing;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Timing;
+using Robust.Shared.Random;
+using Robust.Shared.Audio.Systems;
+
+
+namespace Content.Shared.Trigger.Systems;
+
+/// <summary>
+/// System containing the basic trigger API.
+/// </summary>
+/// <remarks>
+/// If you add a custom trigger subscription or effect then don't put them here.
+/// Put them into a separate system so we don't end up with a giant list of imports.
+/// </remarks>
+public sealed partial class TriggerSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly FixtureSystem _fixture = default!;
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly UseDelaySystem _useDelay = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+    [Dependency] private readonly ItemToggleSystem _itemToggle = default!;
+    [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
+
+    public const string DefaultTriggerKey = "trigger";
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        InitializeCollide();
+        InitializeCondition();
+        InitializeInteraction();
+        InitializeProximity();
+        InitializeSignal();
+        InitializeTimer();
+        InitializeSpawn();
+        InitializeVoice();
+    }
+
+    /// <summary>
+    /// Trigger the given entity.
+    /// </summary>
+    /// <param name="trigger">The entity that has the components that should be triggered.</param>
+    /// <param name="user">The user of the trigger. Some effects may target the user instead of the trigger entity.</param>
+    /// <param name="key">A key string to allow multiple, independent triggers on the same entity. If null then all triggers will activate.</param>
+    /// <returns>Whether or not the trigger has sucessfully activated an effect.</returns>
+    public bool Trigger(EntityUid trigger, EntityUid? user = null, string? key = null)
+    {
+        var attemptTriggerEvent = new AttemptTriggerEvent(user, key);
+        RaiseLocalEvent(trigger, ref attemptTriggerEvent);
+        if (attemptTriggerEvent.Cancelled)
+            return false;
+
+        var triggerEvent = new TriggerEvent(user, key);
+        RaiseLocalEvent(trigger, ref triggerEvent, true);
+        return triggerEvent.Handled;
+    }
+
+    /// <summary>
+    /// Activate a timer trigger on an entity with <see cref="TimerTriggerComponent"/>.
+    /// </summary>
+    /// <returns>Whether or not a timer was activated.</returns>
+    public bool ActivateTimerTrigger(Entity<TimerTriggerComponent?> ent, EntityUid? user = null)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        if (HasComp<ActiveTimerTriggerComponent>(ent))
+            return false; // already activated
+
+        if (user != null)
+        {
+            _adminLogger.Add(LogType.Trigger,
+                $"{ToPrettyString(user.Value):user} started a {ent.Comp.Delay} second timer trigger on entity {ToPrettyString(ent.Owner):timer}");
+        }
+        else
+        {
+            _adminLogger.Add(LogType.Trigger,
+                $"{ent.Comp.Delay} second timer trigger started on entity {ToPrettyString(ent.Owner):timer}");
+        }
+
+        if (ent.Comp.Popup != null)
+            _popup.PopupPredicted(Loc.GetString(ent.Comp.Popup.Value, ("device", ent.Owner)), ent.Owner, user);
+
+        AddComp<ActiveTimerTriggerComponent>(ent);
+        var curTime = _timing.CurTime;
+        ent.Comp.NextTrigger = curTime + ent.Comp.Delay;
+        var delay = ent.Comp.InitialBeepDelay ?? ent.Comp.BeepInterval;
+        ent.Comp.NextBeep = curTime + delay;
+        Dirty(ent);
+
+        var ev = new ActiveTimerTriggerEvent(user);
+        RaiseLocalEvent(ent.Owner, ref ev);
+
+        if (TryComp<AppearanceComponent>(ent, out var appearance))
+            _appearance.SetData(ent.Owner, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance);
+
+        return true;
+    }
+
+    /// <summary>
+    /// Stop a timer trigger on an entity with <see cref="TimerTriggerComponent"/>.
+    /// </summary>
+    /// <returns>Whether or not a timer was stopped.</returns>
+    public bool StopTimerTrigger(Entity<TimerTriggerComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return false;
+
+        if (!HasComp<ActiveTimerTriggerComponent>(ent))
+            return false; // the timer is not active
+
+        RemComp<ActiveTimerTriggerComponent>(ent);
+        if (TryComp<AppearanceComponent>(ent.Owner, out var appearance))
+            _appearance.SetData(ent.Owner, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
+
+        _adminLogger.Add(LogType.Trigger, $"A timer trigger was stopped before triggering on entity {ToPrettyString(ent.Owner):timer}");
+        return true;
+    }
+
+    /// <summary>
+    /// Delay an active timer trigger.
+    /// Returns false if not active.
+    /// </summary>
+    /// <param name="amount">The time to add.</param>
+    public bool TryDelay(Entity<TimerTriggerComponent?> ent, TimeSpan amount)
+    {
+        if (!Resolve(ent, ref ent.Comp, false) || !HasComp<ActiveTimerTriggerComponent>(ent))
+            return false;
+
+        ent.Comp.NextTrigger += amount;
+        Dirty(ent);
+        return true;
+    }
+
+    /// <summary>
+    /// Setter for the Delay datafield.
+    /// </summary>
+    public void SetDelay(Entity<TimerTriggerComponent?> ent, TimeSpan delay)
+    {
+        if (!Resolve(ent, ref ent.Comp))
+            return;
+
+        ent.Comp.Delay = delay;
+        Dirty(ent);
+    }
+
+    /// <summary>
+    /// Gets the remaining time until the trigger will activate.
+    /// Returns null if the trigger is not currently active.
+    /// </summary>
+    public TimeSpan? GetRemainingTime(Entity<TimerTriggerComponent?> ent)
+    {
+        if (!Resolve(ent, ref ent.Comp, false) || !HasComp<ActiveTimerTriggerComponent>(ent))
+            return null; // not a timer or not currently active
+
+        return ent.Comp.NextTrigger - _timing.CurTime;
+    }
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        UpdateTimer();
+        UpdateRepeat();
+        UpdateProximity();
+        UpdateTimedCollide();
+    }
+}
diff --git a/Content.Shared/Trigger/Systems/TwoStageTriggerSystem.cs b/Content.Shared/Trigger/Systems/TwoStageTriggerSystem.cs
new file mode 100644 (file)
index 0000000..2461e79
--- /dev/null
@@ -0,0 +1,51 @@
+using Robust.Shared.Timing;
+using Content.Shared.Trigger.Components;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class TwoStageTriggerSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly TriggerSystem _triggerSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<TwoStageTriggerComponent, TriggerEvent>(OnTrigger);
+    }
+
+    private void OnTrigger(Entity<TwoStageTriggerComponent> ent, ref TriggerEvent args)
+    {
+        if (ent.Comp.Triggered)
+            return; // already triggered
+
+        if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+            return;
+
+        EntityManager.AddComponents(ent, ent.Comp.Components);
+        EnsureComp<ActiveTwoStageTriggerComponent>(ent);
+        ent.Comp.Triggered = true;
+        ent.Comp.NextTriggerTime = _timing.CurTime + ent.Comp.TriggerDelay;
+        ent.Comp.User = args.User;
+        Dirty(ent);
+
+        args.Handled = true;
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var curTime = _timing.CurTime;
+        var enumerator = EntityQueryEnumerator<ActiveTwoStageTriggerComponent, TwoStageTriggerComponent>();
+        while (enumerator.MoveNext(out var uid, out _, out var component))
+        {
+            if (curTime < component.NextTriggerTime)
+                continue;
+
+            RemComp<ActiveTwoStageTriggerComponent>(uid);
+            _triggerSystem.Trigger(uid, component.User, component.KeyOut);
+        }
+    }
+}
diff --git a/Content.Shared/Trigger/TriggerEvent.cs b/Content.Shared/Trigger/TriggerEvent.cs
new file mode 100644 (file)
index 0000000..e65e3b4
--- /dev/null
@@ -0,0 +1,33 @@
+namespace Content.Shared.Trigger;
+
+/// <summary>
+/// Raised whenever something is Triggered on the entity.
+/// </summary>
+/// <param name="User">The entity that activated the trigger.</param>
+/// <param name="Key">
+/// Allows to have multiple independent triggers on the same entity.
+/// Setting this to null will activate all triggers.
+/// </param>
+/// <param name="Handled">Marks the event as handled if at least one trigger effect was activated.</param>
+[ByRefEvent]
+public record struct TriggerEvent(EntityUid? User = null, string? Key = null, bool Handled = false);
+
+/// <summary>
+/// Raised before a trigger is activated.
+/// Cancelling prevents it from triggering.
+/// </summary>
+/// <param name="User">The entity that activated the trigger.</param>
+/// <param name="Key">
+/// Allows to have multiple independent triggers on the same entity.
+/// Setting this to null will activate all triggers.
+/// </param>
+/// <param name="Handled">Marks the event as handled if at least one trigger effect was activated.</param>
+[ByRefEvent]
+public record struct AttemptTriggerEvent(EntityUid? User, string? Key = null, bool Cancelled = false);
+
+/// <summary>
+/// Raised when a timer trigger becomes active.
+/// </summary>
+/// <param name="User">The entity that activated the trigger.</param>
+[ByRefEvent]
+public readonly record struct ActiveTimerTriggerEvent(EntityUid? User);
index ed544f359049d7379c3660e02ae164105c654d66..f1d4a55fb1d907a7c98877b20f1f159225ce05d1 100644 (file)
@@ -1,31 +1,30 @@
 using Robust.Shared.Serialization;
 
-namespace Content.Shared.Trigger
+namespace Content.Shared.Trigger;
+
+[Serializable, NetSerializable]
+public enum ProximityTriggerVisuals : byte
 {
-    [Serializable, NetSerializable]
-    public enum ProximityTriggerVisuals : byte
-    {
-        Off,
-        Inactive,
-        Active,
-    }
+    Off,
+    Inactive,
+    Active,
+}
 
-    [Serializable, NetSerializable]
-    public enum ProximityTriggerVisualState : byte
-    {
-        State,
-    }
+[Serializable, NetSerializable]
+public enum ProximityTriggerVisualState : byte
+{
+    State,
+}
 
-    [Serializable, NetSerializable]
-    public enum TriggerVisuals : byte
-    {
-        VisualState,
-    }
+[Serializable, NetSerializable]
+public enum TriggerVisuals : byte
+{
+    VisualState,
+}
 
-    [Serializable, NetSerializable]
-    public enum TriggerVisualState : byte
-    {
-        Primed,
-        Unprimed,
-    }
+[Serializable, NetSerializable]
+public enum TriggerVisualState : byte
+{
+    Primed,
+    Unprimed,
 }
diff --git a/Content.Shared/Trigger/VoiceTriggeredEvent.cs b/Content.Shared/Trigger/VoiceTriggeredEvent.cs
new file mode 100644 (file)
index 0000000..af138b5
--- /dev/null
@@ -0,0 +1,10 @@
+namespace Content.Shared.Trigger;
+
+/// <summary>
+/// Raised when a voice trigger is activated, containing the message that triggered it.
+/// </summary>
+/// <param name="Source"> The EntityUid of the entity sending the message</param>
+/// <param name="Message"> The contents of the message</param>
+/// <param name="MessageWithoutPhrase"> The message without the phrase that triggered it.</param>
+[ByRefEvent]
+public readonly record struct VoiceTriggeredEvent(EntityUid Source, string? Message, string MessageWithoutPhrase);
index efae42952b4510a25a39dab200c050e5fd2b426b..998fa8f0eb9c6d9c73983b25eac5434f4ce9bd80 100644 (file)
@@ -4,4 +4,4 @@ namespace Content.Shared.Weapons.Ranged.Events;
 /// Raised directed on the gun when trying to fire it while it's out of ammo
 /// </summary>
 [ByRefEvent]
-public record struct OnEmptyGunShotEvent(EntityUid EmptyGun);
+public record struct OnEmptyGunShotEvent(EntityUid User);
index ba8254f68a3514e226a276602d4735471d979588..61ee8cdada0559a9d42ce396127b26efbfa8cd01 100644 (file)
@@ -341,7 +341,7 @@ public abstract partial class SharedGunSystem : EntitySystem
         if (ev.Ammo.Count <= 0)
         {
             // triggers effects on the gun if it's empty
-            var emptyGunShotEvent = new OnEmptyGunShotEvent();
+            var emptyGunShotEvent = new OnEmptyGunShotEvent(user);
             RaiseLocalEvent(gunUid, ref emptyGunShotEvent);
 
             gun.BurstActivated = false;
index d7a2636e11b67cd1c9fc24f5a7eb46577e836c78..40dc68140574e906cad436607e2e43bb04857761 100644 (file)
@@ -25,11 +25,8 @@ signal-port-description-close = Closes a device.
 signal-port-name-doorbolt = Door bolt
 signal-port-description-doorbolt = Bolts door when HIGH.
 
-signal-port-name-trigger = Trigger
-signal-port-description-trigger = Triggers some mechanism on the device.
-
-signal-port-name-timer = Timer
-signal-port-description-timer = Starts the timer countdown of the device.
+signal-port-name-trigger-receiver = Trigger
+signal-port-description-trigger-receiver = Triggers some mechanism on the device.
 
 signal-port-name-order-sender = Order sender
 signal-port-description-order-sender = Cargo console order sender
index 89a978479eb532523dcbd44e089ccea895efa8a5..d016a7e2064d8f14bb582b3288614a8683e57bd3 100644 (file)
@@ -25,8 +25,11 @@ signal-port-description-dockstatus = This port is invoked with HIGH when docked
 signal-port-name-middle = Middle
 signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position.
 
-signal-port-name-timer-trigger = Timer Trigger
-signal-port-description-timer-trigger = This port is invoked whenever the timer triggers.
+signal-port-name-trigger-sender = Trigger
+signal-port-description-trigger-sender = This port is invoked whenever the device triggers.
+
+signal-port-name-timer-trigger = Timer
+signal-port-description-timer-trigger = This port is invoked whenever the timer is up.
 
 signal-port-name-timer-start = Timer Start
 signal-port-description-timer-start = This port is invoked whenever the timer starts.
diff --git a/Resources/Locale/en-US/triggers/ghost-kick-on-trigger.ftl b/Resources/Locale/en-US/triggers/ghost-kick-on-trigger.ftl
new file mode 100644 (file)
index 0000000..acb06bd
--- /dev/null
@@ -0,0 +1 @@
+ghost-kick-on-trigger-default = Tripped over a kick mine, crashed through the fourth wall.
diff --git a/Resources/Locale/en-US/triggers/timer-trigger.ftl b/Resources/Locale/en-US/triggers/timer-trigger.ftl
new file mode 100644 (file)
index 0000000..5d6d553
--- /dev/null
@@ -0,0 +1,10 @@
+
+timer-trigger-verb-set = {$time} Seconds
+timer-trigger-verb-set-current = {$time} Seconds (current)
+timer-trigger-verb-cycle = Cycle Time Delay
+
+timer-trigger-examine = The timer is set to {$time} seconds.
+
+timer-trigger-popup-set = Timer set to {$time} seconds.
+
+timer-trigger-activated = You activate {THE($device)}.
diff --git a/Resources/Locale/en-US/triggers/toggle-trigger-condition.ftl b/Resources/Locale/en-US/triggers/toggle-trigger-condition.ftl
new file mode 100644 (file)
index 0000000..8744906
--- /dev/null
@@ -0,0 +1,7 @@
+toggle-trigger-condition-default-verb = Toggle device
+toggle-trigger-condition-default-on = Device enabled.
+toggle-trigger-condition-default-off = Device disabled.
+
+toggle-trigger-condition-stick-verb = Toggle auto-activation
+toggle-trigger-condition-stick-on = The device will now activate automatically when planted.
+toggle-trigger-condition-stick-off = The device will no longer activate automatically when planted.
diff --git a/Resources/Locale/en-US/triggers/trigger-on-verb.ftl b/Resources/Locale/en-US/triggers/trigger-on-verb.ftl
new file mode 100644 (file)
index 0000000..d9342e7
--- /dev/null
@@ -0,0 +1,2 @@
+trigger-on-verb-default = Trigger
+trigger-on-verb-detonation = Start detonation
\ No newline at end of file
diff --git a/Resources/Locale/en-US/triggers/trigger-on-voice.ftl b/Resources/Locale/en-US/triggers/trigger-on-voice.ftl
new file mode 100644 (file)
index 0000000..7ace486
--- /dev/null
@@ -0,0 +1,12 @@
+trigger-on-voice-examine = The display reads: "{$keyphrase}"
+trigger-on-voice-uninitialized = The display reads: Uninitialized...
+
+trigger-on-voice-record = Record
+trigger-on-voice-stop = Stop
+trigger-on-voice-clear = Clear recording
+
+trigger-on-voice-start-recording = Started recording.
+trigger-on-voice-stop-recording = Stopped recording.
+trigger-on-voice-record-failed-too-long = Message too long, try again.
+trigger-on-voice-record-failed-too-short = Message too short, try again.
+trigger-on-voice-recorded = Recorded successfully!
diff --git a/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl b/Resources/Locale/en-US/weapons/grenades/timer-trigger.ftl
deleted file mode 100644 (file)
index 9a10e9e..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-
-verb-trigger-timer-set = {$time} Seconds
-verb-trigger-timer-set-current = {$time} Seconds (current)
-verb-trigger-timer-cycle = Cycle Time Delay
-
-examine-trigger-timer = The timer is set to {$time} seconds.
-
-popup-trigger-timer-set = Timer set to {$time} seconds.
-
-verb-start-detonation = Start detonation
-
-verb-toggle-start-on-stick = Toggle auto-activation
-popup-start-on-stick-off = The device will no longer activate automatically when planted
-popup-start-on-stick-on = The device will now activate automatically when planted
-
-trigger-activated = You activate {THE($device)}.
diff --git a/Resources/Locale/en-US/weapons/grenades/voice-trigger.ftl b/Resources/Locale/en-US/weapons/grenades/voice-trigger.ftl
deleted file mode 100644 (file)
index 07ee594..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-examine-trigger-voice = The display reads: "{$keyphrase}"
-trigger-voice-uninitialized = The display reads: Uninitialized...
-
-verb-trigger-voice-record = Record
-verb-trigger-voice-stop = Stop
-verb-trigger-voice-clear = Clear recording
-
-popup-trigger-voice-start-recording = Started recording
-popup-trigger-voice-stop-recording = Stopped recording
-popup-trigger-voice-record-failed-too-long = Message too long, try again
-popup-trigger-voice-record-failed-too-short = Message too short, try again
-popup-trigger-voice-recorded = Recorded successfully
index 49e27e3fa934efbac0a5faf3b4dd875b5625d1c1..d8deb511410158500d03e6f09bfbb83bf096f2dd 100644 (file)
@@ -1351,6 +1351,8 @@ entities:
         intensity: 50
         type: SingularityDistortion
       - type: ExplodeOnTrigger
+        keysIn:
+        - stageTwo
       triggerDelay: 500
 - proto: GravityGeneratorMini
   entities:
@@ -1479,8 +1481,8 @@ entities:
 
                                            [bold]Crew of the NT-Quark[/bold]
 
-             [bold]Nuclear containment breach detected on long range 
-                 halcyon scanners. Seek immediate shelter. Avoid 
+             [bold]Nuclear containment breach detected on long range
+                 halcyon scanners. Seek immediate shelter. Avoid
                          contaminated materials. Do not panic.[/bold]
 
                               [color=red][bold]Alpha decay rates indicate estimated
index a5313fcc4e476640393d102361e795bc4d74c72a..66e2bee27dbed874f2a4d5edf3acf50f3610e08a 100644 (file)
@@ -45,8 +45,8 @@
 
 - type: sinkPort
   id: Trigger
-  name: signal-port-name-trigger
-  description: signal-port-description-trigger
+  name: signal-port-name-trigger-receiver
+  description: signal-port-description-trigger-receiver
 
 - type: sinkPort
   id: Timer
index 4a460e722d1f163bda68614975517431fc17ea83..302679c4d99bffbd33776963ac4d4ec3b6781e0f 100644 (file)
   name: signal-port-name-power-discharging
   description: signal-port-description-power-discharging
 
+- type: sourcePort
+  id: Trigger
+  name: signal-port-name-trigger-sender
+  description: signal-port-description-trigger-sender
+  defaultLinks: [ AutoClose, On, Open, Forward, Trigger, Timer ]
+
 - type: sourcePort
   id: ItemDetected
   name: signal-port-name-item-detected
index 4d406e9508ea72c4d95cdc1573d334e36f8c84d5..fc3e557bea9646493b6beb4ac7c71e355f80fb60 100644 (file)
     equipDelay: 5 # to avoid accidentally falling into the trap associated with SelfUnremovableClothing
   - type: SelfUnremovableClothing
   - type: ShockOnTrigger
+    targetContainer: true
     damage: 5
     duration: 3
-    cooldown: 4
   - type: TriggerOnSignal
   - type: DeviceLinkSink
     ports:
index 52c5a6c110d9564f3075372f490dc995416ea3e4..df9e11bcff7332b515356c4ae0645ad7446667d1 100644 (file)
@@ -7,7 +7,7 @@
     sprite: /Textures/Objects/Fun/goldbikehorn.rsi
     visible: false
     state: icon
-  - type: TriggerOnSpawn   
+  - type: TriggerOnSpawn
   - type: TimedDespawn
     lifetime: 5
 
@@ -25,7 +25,7 @@
   suffix: BluespaceFlash
   parent: AdminInstantEffectBase
   components:
-  - type: SpawnOnTrigger 
+  - type: SpawnOnTrigger
     proto: EffectFlashBluespace
 
 - type: entity
@@ -35,9 +35,9 @@
   components:
   - type: FlashOnTrigger
     range: 7
-  - type: SpawnOnTrigger 
+  - type: SpawnOnTrigger
     proto: GrenadeFlashEffect
-  
+
 - type: entity
   id: AdminInstantEffectSmoke3
   suffix: Smoke (03 sec)
   - type: SmokeOnTrigger
     duration: 3
     spreadAmount: 1
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
     sound: /Audio/Effects/smoke.ogg
   - type: TimerTriggerVisuals
     primingSound:
       path: /Audio/Effects/Smoke-grenade.ogg
-  
+
 - type: entity
   id: AdminInstantEffectSmoke10
   suffix: Smoke (10 sec)
   - type: SmokeOnTrigger
     duration: 10
     spreadAmount: 30
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
     sound: /Audio/Effects/smoke.ogg
   - type: TimerTriggerVisuals
     primingSound:
       path: /Audio/Effects/Smoke-grenade.ogg
-  
+
 - type: entity
   id: AdminInstantEffectSmoke30
   suffix: Smoke (30 sec)
@@ -74,7 +74,7 @@
   - type: SmokeOnTrigger
     duration: 30
     spreadAmount: 50
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
     sound: /Audio/Effects/smoke.ogg
   - type: TimerTriggerVisuals
     primingSound:
   id: AdminInstantEffectGravityWell
   suffix: Gravity Well
   parent: AdminInstantEffectBase
-  components: 
-  - type: SoundOnTrigger
-    removeOnTrigger: true
+  components:
+  - type: EmitSoundOnTrigger
     sound:
       path: /Audio/Effects/Grenades/Supermatter/supermatter_start.ogg
-      volume: 5
+      params:
+        volume: 5
   - type: AmbientSound
     enabled: true
     volume: -5
   - type: SingularityDistortion
     intensity: 10
     falloffPower: 1.5
-      
+
index 714c740cf8c2224266b7e87dbb76e076b2d04dfc..f8a12920cdf0a58b69f8c358331bc0dd481888bb 100644 (file)
     receiveFrequencyId: CyborgControl
     transmitFrequencyId: RoboticsConsole
     savableAddress: false
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 10
     examinable: false
     beepSound:
       params:
         volume: -4
   # prevent any funnies if someone makes a cyborg item...
-  - type: AutomatedTimer
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   # explosion does most of its damage in the center and less at the edges
   - type: Explosive
     explosionType: Minibomb
index dcf40dfcde50547f8be429153631d6d6ea61232d..41a66d2ef08b9244ac6f5fcd13467ba9e3cb44b1 100644 (file)
   - type: Item
     size: Normal
     sprite: Mobs/Animals/grenadepenguin.rsi
-  - type: OnUseTimerTrigger
+  - type: TriggerOnUse
+  - type: TimerTrigger
     delay: 10
     beepSound:
       path: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg #funny sfx use
     intensitySlope: 20
     totalIntensity: 225
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Destructible
     thresholds:
     - trigger:
index 77bb8e1a7fbd00a6b94b18b6d3c278861084c99b..ba4c5e3698c8009320d6d4280beb4bdb7243115f 100644 (file)
     critThreshold: 120
   - type: Destructible
     thresholds:
-    - trigger:
-        !type:DamageTrigger
-        damage: 100
-      behaviors:
-      - !type:TriggerBehavior
     - trigger:
         !type:DamageTrigger
         damage: 120
index 2748462abcee88f69133f6750028bc3cbded84f1..c031559b58efaff19ef2cb20881401e122ff8814 100644 (file)
       payloadSlot:
         whitelist:
           components:
-          - OnUseTimerTrigger
+          - TimerTrigger
         insertSound:
           path: /Audio/Weapons/Guns/Empty/empty.ogg
         ejectSound:
index 24843f608a74c8e53bd2b79e0e01b893a431c208..87bef747d8eb054254363ae29556ae25f60705cb 100644 (file)
   - type: IgnitionSource
     temperature: 800
   - type: IgniteOnTrigger
+  - type: EmitSoundOnTrigger
+    sound:
+      collection: WelderOn
+  - type: UseDelayOnTrigger
+  - type: UseDelayTriggerCondition
   - type: TriggerOnSignal
   - type: DeviceNetwork
     deviceNetId: Wireless
index 0a975702aaa6d1579a55e7de8c795a5b307f9db4..4b3d98f198a834fbb9a724f59678b18da354271d 100644 (file)
     sprite: Objects/Devices/signaller.rsi
     state: signaller
   - type: Signaller
+  - type: SignalOnTrigger
+    port: Pressed
+  - type: UseDelayOnTrigger
+  - type: UseDelayTriggerCondition
   - type: UseDelay
   - type: StaticPrice
     price: 40
@@ -43,4 +47,4 @@
   - type: WirelessNetworkConnection
     range: 50
   - type: StaticPrice
-    price: 30
\ No newline at end of file
+    price: 30
index 67374a81f1b68b8863c401a192a0c97bdc5e8d1a..c6943a63e3d81add9a55703effce9372810fcc5e 100644 (file)
     price: 40
   - type: PayloadTrigger
     components:
-    - type: OnUseTimerTrigger
+    - type: TriggerOnUse
+      keyOut: startTimer
+    - type: TimerTrigger
+      keysIn:
+      - startTimer
+      # use the default trigger key so it can activate any inserted payload
+      keyOut: trigger
       delay: 5
       delayOptions: [3, 5, 10, 15, 30]
       initialBeepDelay: 0
     - SignalTrigger
   - type: PayloadTrigger
     components:
-    - type: TimerStartOnSignal
     - type: DeviceNetwork
       deviceNetId: Wireless
       receiveFrequencyId: BasicDevice
     - type: WirelessNetworkConnection
       range: 200
     - type: DeviceLinkSink
-    - type: OnUseTimerTrigger
+    - type: TriggerOnSignal
+      keyOut: startTimer
+    - type: TimerTrigger
+      keysIn:
+      - startTimer
+      # use the default trigger key so it can activate any inserted payload
+      keyOut: trigger
       delay: 3
       initialBeepDelay: 0
       beepSound:
index d5f8352baa94304e3d635d63f8815f73e510e08f..526d297f608a5e7fce287194f1ea3544703d533e 100644 (file)
@@ -9,6 +9,7 @@
     state: icon
   - type: TriggerOnUse
   - type: PolymorphOnTrigger
+    targetUser: true
     polymorph: VoidPocket
   - type: UseDelay
     delay: 220 # long delay to ensure it can't be spammed, use it wisely
index 04349c3a1c16babb137eaffa140cf521e21687ca..f7be1943709ff6f99d266fe48b21919cc373f30e 100644 (file)
@@ -8,32 +8,37 @@
       sprite: Objects/Devices/mousetrap.rsi
       drawdepth: SmallMobs # if mice can hide under tables, so can mousetraps
       layers:
-        - state: mousetrap
-          map: ["base"]
+      - state: mousetrap
+        map: ["enum.ToggleableVisuals.Layer"]
     - type: StepTrigger
       intersectRatio: 0.2
       requiredTriggeredSpeed: 2
     - type: Mousetrap
+    - type: ItemToggle
+      soundActivate: "/Audio/Items/Handcuffs/cuff_end.ogg"
+      soundDeactivate: "/Audio/Items/snap.ogg"
+      popupActivate: mousetrap-on-activate
+      popupDeactivate: mousetrap-on-deactivate
+    - type: UseDelay
+    - type: ItemToggleOnTrigger
+      canActivate: false
+      showPopup: false # only show the popup when arming/disarming the trap in your hand
     - type: TriggerOnStepTrigger
     - type: PreventableStepTrigger
-    - type: DamageUserOnTrigger
+    - type: DamageOnTrigger
+      targetUser: true
       damage:
         types:
           Blunt: 2 # base damage, scales based on mass
-    - type: EmitSoundOnUse
-      sound: "/Audio/Items/Handcuffs/cuff_end.ogg"
-      handle: false
-    - type: EmitSoundOnTrigger
-      sound: "/Audio/Items/snap.ogg"
     - type: Item
       sprite: Objects/Devices/mousetrap.rsi
     - type: Appearance
     - type: GenericVisualizer
       visuals:
-        enum.MousetrapVisuals.Visual:
-          base:
-            Armed: { state: mousetraparmed }
-            Unarmed: { state: mousetrap }
+        enum.ToggleableVisuals.Enabled:
+          enum.ToggleableVisuals.Layer:
+            True: {state: mousetraparmed}
+            False: {state: mousetrap}
     - type: Physics
       bodyType: Dynamic
     - type: CollisionWake
@@ -66,6 +71,6 @@
   - type: Sprite
     layers:
     - state: mousetraparmed
-      map: ["base"]
-  - type: Mousetrap
-    isActive: true
+      map: ["enum.ToggleableVisuals.Layer"]
+  - type: ItemToggle
+    activated: true
index c161b2a0289836a6cb5bfe28747fa660d9e87fe7..e5dc07c7c1f284b6bec43418b6b84920deacf48b 100644 (file)
@@ -94,3 +94,4 @@
     - type: EmitSoundOnTrigger
       sound:
         path: "/Audio/Effects/flash_bang.ogg"
+      positional: true
index c4be0aa4531449207c0b59c210e877203f1bfb7d..f124be8a426beb245ea67a2f02a186de67cc5745 100644 (file)
       - MobMover
   - type: SpawnOnTrigger
     proto: EffectGravityPulse
+    predicted: true
index 32df1f402df325c9b01a1469752b826946e1303a..132f92212efa18e6f06327215ffdf64d997ad188 100644 (file)
       path: /Audio/Effects/glass_step.ogg
     slipData:
       launchForwardsMultiplier: 0
-  - type: DamageUserOnTrigger
+  - type: DamageOnTrigger
+    targetUser: true
     damage:
       types:
         Piercing: 5
index c99df854d231bbf6e7413e48a9114ac78fcd78e3..7575457f4d6d5488f69afab590a1da5cd3f8e1a3 100644 (file)
@@ -19,6 +19,8 @@
     - Figurine
   - type: UseDelay
     delay: 5
+  - type: UseDelayOnTrigger
+  - type: UseDelayTriggerCondition # prevent spam
   - type: TriggerOnActivate
   - type: TriggerOnSignal
   - type: Speech
index d0be38d5045ee8cafc2c011ec0d33ae98bb70e44..9ff94db3b1ce1af771481d3bc274993b57d126ca 100644 (file)
   - type: StepTrigger
     intersectRatio: 0.2
     requiredTriggeredSpeed: 2
-  - type: TriggerOnStepTrigger
+  - type: TriggerOnStepTrigger # for payloads
   - type: Appearance
   - type: CollisionWake
     enabled: false
index 2457404b4860fe161c4744dd0f87c86c17bea097..c649ef921cb7571799170a8c63c660e9de774982 100644 (file)
@@ -72,7 +72,8 @@
     slipData:
       launchForwardsMultiplier: 0
   - type: TriggerOnStepTrigger
-  - type: DamageUserOnTrigger
+  - type: DamageOnTrigger
+    targetUser: true
     damage:
       types:
         Piercing: 5
@@ -91,7 +92,7 @@
   - type: ToolRefinable
     refineResult:
     - id: SheetGlass1
-  - type: DamageUserOnTrigger
+  - type: DamageOnTrigger
     damage:
       types:
         Piercing: 4.5
     refineResult:
     - id: SheetGlass1
     - id: PartRodMetal1
-  - type: DamageUserOnTrigger
+  - type: DamageOnTrigger
     damage:
       types:
         Piercing: 5.5
     refineResult:
     - id: SheetGlass1
     - id: SheetPlasma1
-  - type: DamageUserOnTrigger
+  - type: DamageOnTrigger
     damage:
       types:
         Piercing: 6.5
     refineResult:
     - id: SheetGlass1
     - id: SheetUranium1
-  - type: DamageUserOnTrigger
+  - type: DamageOnTrigger
     damage:
       types:
         Piercing: 5
     refineResult:
     - id: SheetGlass1
     - id: SheetBrass1
-  - type: DamageUserOnTrigger
+  - type: DamageOnTrigger
     damage:
       types:
         Piercing: 5
index 7ed1b657ea2701998ac247f10c79ab721fab9161..a3a2b60401e50d4441f41643517fe6bc9f99f51f 100644 (file)
@@ -64,7 +64,8 @@
   name: kick mine
   parent: BaseLandMine
   components:
-  - type: GhostKickUserOnTrigger
+  - type: GhostKickOnTrigger
+    targetUser: true
   - type: DeleteOnTrigger
 
 - type: entity
index 7ddf4945d06a355230e26360c8ea52d6ef8f6656..a369a730cfc8af74eb1309e5337407c43027d0e0 100644 (file)
   description: This implant lets the user honk anywhere at any time.
   categories: [ HideSpawnMenu ]
   components:
-    - type: SubdermalImplant
-      implantAction: ActionActivateHonkImplant
-    - type: TriggerImplantAction
-    - type: EmitSoundOnTrigger
-      sound:
-        collection: BikeHorn
-        params:
-          variation: 0.125
-    - type: Tag
-      tags:
-      - BikeHorn
+  - type: SubdermalImplant
+    implantAction: ActionActivateHonkImplant
+  - type: TriggerOnActivateImplant
+  - type: EmitSoundOnTrigger
+    predicted: true
+    sound:
+      collection: BikeHorn
+      params:
+        variation: 0.125
+  - type: Tag
+    tags:
+    - BikeHorn
 
 #Security implants
 
   description: This implant creates an electromagnetic pulse when activated.
   categories: [ HideSpawnMenu ]
   components:
-    - type: SubdermalImplant
-      implantAction: ActionActivateEmpImplant
-    - type: TriggerImplantAction
-    - type: EmpOnTrigger
-      range: 2.75
-      energyConsumption: 50000
-      disableDuration: 10
+  - type: SubdermalImplant
+    implantAction: ActionActivateEmpImplant
+  - type: TriggerOnActivateImplant
+  - type: EmpOnTrigger
+    range: 2.75
+    energyConsumption: 50000
+    disableDuration: 10
 
 - type: entity
   parent: BaseSubdermalImplant
   description: This implant randomly teleports the user within a large radius when activated.
   categories: [ HideSpawnMenu ]
   components:
-    - type: SubdermalImplant
-      implantAction: ActionActivateScramImplant
-    - type: TriggerImplantAction
-    - type: ScramImplant
+  - type: SubdermalImplant
+    implantAction: ActionActivateScramImplant
+  - type: TriggerOnActivateImplant
+  - type: ScramImplant
 
 - type: entity
   parent: BaseSubdermalImplant
   description: This implant detonates the user upon activation or upon death.
   categories: [ HideSpawnMenu ]
   components:
-    - type: SubdermalImplant
-      permanent: true
-      implantAction: ActionActivateMicroBomb
-    - type: TriggerOnMobstateChange
-      mobState:
-      - Dead
-    - type: TriggerImplantAction
-    - type: ExplodeOnTrigger
-    - type: GibOnTrigger
-      deleteItems: true
-    - type: Explosive
-      explosionType: MicroBomb
-      totalIntensity: 120
-      intensitySlope: 5
-      maxIntensity: 30
-      canCreateVacuum: false
-    - type: Tag
-      tags:
-        - SubdermalImplant
-        - HideContextMenu
-        - MicroBomb
+  - type: SubdermalImplant
+    permanent: true
+    implantAction: ActionActivateMicroBomb
+  - type: TriggerOnMobstateChange
+    mobState:
+    - Dead
+  - type: TriggerOnActivateImplant
+  - type: ExplodeOnTrigger
+  - type: GibOnTrigger
+    targetUser: true
+    deleteItems: true
+  - type: Explosive
+    explosionType: MicroBomb
+    totalIntensity: 120
+    intensitySlope: 5
+    maxIntensity: 30
+    canCreateVacuum: false
+  - type: Tag
+    tags:
+    - SubdermalImplant
+    - HideContextMenu
+    - MicroBomb
 
 
 - type: entity
   components:
     - type: SubdermalImplant
       permanent: true
-    - type: TriggerOnMobstateChange #Chains with OnUseTimerTrigger
+    - type: TriggerOnMobstateChange #activates the timer
       mobState:
       - Dead
       preventSuicide: true
-    - type: OnUseTimerTrigger
+    - type: TimerTrigger
       delay: 7
-      initialBeepDelay: 0
       beepSound:
         path: /Audio/Machines/Nuke/general_beep.ogg
         params:
           volume: -2
     - type: ExplodeOnTrigger
+      keysIn:
+      - timer
     - type: GibOnTrigger
+      targetUser: true
       deleteItems: true
+      keysIn:
+      - timer
     - type: Explosive
       explosionType: Default
       totalIntensity: 3500
   - type: TriggerOnMobstateChange
     mobState:
     - Dead
-  - type: TriggerImplantAction
+  - type: TriggerOnActivateImplant
   - type: GibOnTrigger
+    targetUser: true
     deleteItems: true
   - type: SpawnOnTrigger
     proto: Acidifier
+    predicted: true
   - type: Tag
     tags:
     - SubdermalImplant
       mobState:
       - Critical
       - Dead
-    - type: Rattle
+    - type: RattleOnTrigger
+      targetUser: true
 
 - type: entity
   parent: BaseSubdermalImplant
   description: This implant will inform the Centcomm radio channel should the user fall into critical condition or die.
   categories: [ HideSpawnMenu ]
   components:
-  - type: Rattle
+  - type: RattleOnTrigger
     radioChannel: CentCom
index d9d4851158ec3dcfdbcbab3adf500687fdbe7217..fb740f1cd6aed0a6ff61ca9bb53691c146ead77e 100644 (file)
       path: /Audio/Effects/beep_landmine.ogg
       params:
         maxDistance: 10
-  - type: ExplodeOnTrigger
   - type: Explosive
     explosionType: HardBomb  # normally Default and max 5 total 60
     maxIntensity: 10 # about a ~67.5 total damage
     totalIntensity: 30 # about a ~3 tile radius
     canCreateVacuum: false
+  - type: ExplodeOnTrigger
+    keysIn:
+    - timer
+    - trigger # directly explode from the landmine
   - type: DeleteOnTrigger
-  - type: OnUseTimerTrigger
-    useVerbInstead: true
+    keysIn:
+    - timer
+    - trigger
+  - type: TimerTrigger
     beepInterval: .25
     beepSound:
       path: /Audio/Items/Janitor/floor_sign_beep.ogg
       params:
         volume: 1
     examinable: false
+  - type: TriggerOnVerb
+    text: trigger-on-verb-detonation
   - type: Tag
     tags: # ignore "WhitelistChameleon" tag
-      - WetFloorSign
+    - WetFloorSign
 
 - type: entity
   name: plunger
index 01283128e6d626422eb9b684061b3ea66528af88..9f33e3c2398885697e93c066eaafee22eb0084b2 100644 (file)
   - type: Slippery
   - type: StepTrigger
     intersectRatio: 0.04
+  - type: TriggerOnStepTrigger
   - type: Item
     heldPrefix: syndie
   - type: Fixtures
         - ItemMask
   - type: DeleteOnTrigger
   - type: EmitSoundOnTrigger
+    predicted: true
+    positional: true
     sound:
       path: "/Audio/Effects/Fluids/splat.ogg"
       params:
-        volume: -20
+        volume: -8
 
 - type: entity
   name: soap
index 23f5cc436260a84cf6d52886701e20927adfedd5..fec2cca3abdcae69d33af0c4065f53d28e27a22a 100644 (file)
@@ -16,7 +16,8 @@
     - state: wires
   - type: Item
     sprite: Objects/Weapons/Bombs/ied.rsi
-  - type: OnUseTimerTrigger
+  - type: TriggerOnUse
+  - type: TimerTrigger
     delay: 5
     examinable: false
     initialBeepDelay: 0
@@ -34,6 +35,8 @@
     maxIntensity: 3
     canCreateVacuum: false
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Appearance
   - type: AnimationPlayer
   - type: TimerTriggerVisuals
index 73c021748c1e358afd7fb1f4d44fc1eced44bcf9..298b9d7c123a8fe58594cd1cb5c07f99d454df40 100644 (file)
       damage:
         types:
           Blunt: 5
-    - type: OnUseTimerTrigger
+    - type: TriggerOnUse
+    - type: TimerTrigger
       delay: 120
       beepSound:
         path: /Audio/Machines/Nuke/general_beep.ogg
         params:
           volume: -2
     - type: ExplodeOnTrigger
+      keysIn:
+      - timer
     - type: Explosive
       explosionType: Default
       maxIntensity: 50
@@ -37,6 +40,8 @@
       totalIntensity: 100
       canCreateVacuum: false
     - type: DeleteOnTrigger
+      keysIn:
+      - timer
     - type: HotPotato
     - type: DamageOnHolding
       enabled: false
@@ -49,7 +54,7 @@
         enum.Trigger.TriggerVisuals.VisualState:
           base:
             Primed: { state: activated }
-            Unprimed: { state: complete }
+            Unprimed: { state: icon }
 
 - type: entity
   id: HotPotatoEffect
index d553b2b2a41cd0462680ae4aa29f74e8139d92bd..8de013afff280228a9a12aa496f4002db657b57d 100644 (file)
@@ -5,7 +5,7 @@
   description: A dark ink pen.
   id: PenExploding
   components:
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 4
     examinable: false
   - type: Explosive
     canCreateVacuum: false
   - type: ActivateOnPaperOpened
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
+  - type: TriggerOnUse
   - type: TriggerOnSignal
-  - type: DeviceLinkSink # This should be changed into separate behavior where triggering via signal makes beep, while triggering manually is quiet, when that functionality is supported.
+  - type: DeviceLinkSink # This should be changed into separate behavior where triggering via signal makes beep, while triggering manually is quiet.
     ports:
     - Trigger
   - type: EmitSoundOnUse
index a39a1de6719ceb9ba3e5b3670539cb9402991816..a661224f318b7c87946547ed1948a7edf57972cc 100644 (file)
@@ -10,7 +10,8 @@
     - state: base
       map: ["enum.TriggerVisualLayers.Base"]
     - state: wires
-  - type: OnUseTimerTrigger # todo: make it activate through welder/lighter/fire instead
+  - type: TriggerOnActivate # todo: make it activate through welder/lighter/fire instead
+  - type: TimerTrigger
     delay: 5
     examinable: false
     initialBeepDelay: 0
@@ -19,6 +20,8 @@
     min: 1
     max: 10
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Explosive # Weak explosion in a very small radius. Doesn't break underplating.
     explosionType: Default
     totalIntensity: 50
index 7e3b3b9e83e08772e3ff4a8203ee5925dc44651b..b1bfdeef9a3a41fc21e03843c814d96ad5ffea4c 100644 (file)
@@ -33,7 +33,7 @@
       enum.Trigger.TriggerVisuals.VisualState:
         base:
           Primed: { state: primed }
-          Unprimed: { state: complete }
+          Unprimed: { state: icon }
 
 - type: entity
   name: composition C-4
     quickEquip: false
     equipDelay: 3
     unequipDelay: 6
-  - type: OnUseTimerTrigger
+  - type: TriggerOnActivate
+  - type: TriggerOnSignal
+  - type: TriggerOnStuck
+    keyOut: stuck
+  - type: ToggleTriggerCondition # for toggling the start on stuck ability
+    keys:
+    - stuck
+    toggleVerb: toggle-trigger-condition-stick-verb
+    toggleOn: toggle-trigger-condition-stick-on
+    toggleOff: toggle-trigger-condition-stick-off
+  - type: TimerTrigger
+    keysIn:
+    - trigger
+    - stuck
     delay: 10
     delayOptions: [10, 30, 60, 120, 300]
     initialBeepDelay: 0
     beepSound: /Audio/Machines/Nuke/general_beep.ogg
-    startOnStick: true
-    canToggleStartOnStick: true
-  - type: TimerStartOnSignal
   - type: DeviceLinkSink
     ports:
-    - Timer
+    - Trigger
   - type: Explosive # Powerful explosion in a very small radius. Doesn't break underplating.
     explosionType: DemolitionCharge
     totalIntensity: 60
@@ -72,6 +82,8 @@
     maxIntensity: 30
     canCreateVacuum: false
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: HolidayVisuals
     holidays:
       festive:
     layers:
     - state: icon
       map: ["base"]
-  - type: OnUseTimerTrigger
+  - type: TriggerOnActivate
+  - type: TriggerOnSignal
+  - type: TriggerOnStuck
+    keyOut: stuck
+  - type: ToggleTriggerCondition # for toggling the start on stuck ability
+    keys:
+    - stuck
+    toggleVerb: toggle-trigger-condition-stick-verb
+    toggleOn: toggle-trigger-condition-stick-on
+    toggleOff: toggle-trigger-condition-stick-off
+  - type: TimerTrigger
+    keysIn:
+    - trigger
+    - stuck
     delay: 5
     delayOptions: [5, 10, 15, 20]
     initialBeepDelay: 0
       path: /Audio/Effects/Cargo/buzz_two.ogg
       params:
         volume: -6
-    startOnStick: false
-    canToggleStartOnStick: true
-  - type: TimerStartOnSignal
   - type: DeviceLinkSink
     ports:
-    - Timer
+    - Trigger
   - type: Explosive
     explosionType: Cryo
     totalIntensity: 120
     maxIntensity: 30
     canCreateVacuum: false
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
index 2ac23f8c3f78c6c4429c837b7ccc022a1f0d8df5..d00297a7236796efd1381f1b338f1382acde02e1 100644 (file)
     sprite: Objects/Weapons/Bombs/spidercharge.rsi
     size: Small
   - type: SpiderCharge
-  - type: OnUseTimerTrigger
+  - type: TriggerOnStuck
+  - type: TimerTrigger
     delay: 10
     delayOptions: [5, 10, 30, 60]
     initialBeepDelay: 0
     beepSound: /Audio/Machines/Nuke/general_beep.ogg
-    startOnStick: true
-  - type: AutomatedTimer
   - type: Sticky
     stickDelay: 5
     stickPopupStart: comp-sticky-start-stick-bomb
@@ -37,6 +36,8 @@
     maxIntensity: 120
     canCreateVacuum: true
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: StickyVisualizer
   - type: Appearance
   - type: GenericVisualizer
@@ -44,4 +45,4 @@
       enum.Trigger.TriggerVisuals.VisualState:
         base:
           Primed: { state: primed }
-          Unprimed: { state: complete }
+          Unprimed: { state: icon }
index c587787d4807d0d8e3d3c12e7b8e8e4c6cdcc9aa..9e2b6d76c189a8890bc6a449c0965732d91aabee 100644 (file)
 
 - type: entity
   id: ProjectilePolyboltBase
+  abstract: true
   parent: BaseBullet
   categories: [ HideSpawnMenu ]
   components:
         Poison: 5
   - type: TriggerOnCollide
     fixtureID: projectile
+  - type: PolymorphOnTrigger
+    targetUser: true
 
 - type: entity
   id: ProjectilePolyboltCarp
   components:
   - type: PolymorphOnTrigger
     polymorph: WizardForcedCarp
-  - type: TriggerWhitelist
-    whitelist:
+  - type: WhitelistTriggerCondition
+    userWhitelist:
       components:
       - Body
 
   components:
   - type: PolymorphOnTrigger
     polymorph: WizardForcedMonkey
-  - type: TriggerWhitelist
-    whitelist:
+  - type: WhitelistTriggerCondition
+    userWhitelist:
       components:
       - Body
 
       color: brown
   - type: PolymorphOnTrigger
     polymorph: WizardWallDoor
-  - type: TriggerWhitelist
-    whitelist:
+  - type: WhitelistTriggerCondition
+    userWhitelist:
       components:
       - Airlock
       - Firelock
   components:
   - type: PolymorphOnTrigger
     polymorph: WizardForcedCluwne
-  - type: TriggerWhitelist
-    whitelist:
+  - type: WhitelistTriggerCondition
+    userWhitelist:
       components:
       - Body
 
   components:
   - type: PolymorphOnTrigger
     polymorph: BreadMorph
-  - type: TriggerWhitelist
-    whitelist:
+  - type: WhitelistTriggerCondition
+    userWhitelist:
       components:
       - Body
index e89792694d5dc2ed1f75783680cd624d83438084..1cf6ab9a94b808fe37f29c039267e4a9e5fe8a88 100644 (file)
     range: 7
   - type: SpawnOnTrigger
     proto: GrenadeFlashEffect
-  - type: ActiveTimerTrigger
-    timeRemaining: 0.3
   - type: DeleteOnTrigger
 
 - type: entity
     sprite: Objects/Weapons/Guns/Projectiles/projectiles2.rsi
     layers:
     - state: cleanade
-  - type: ActiveTimerTrigger
-    timeRemaining: 0.3
   - type: SmokeOnTrigger
     duration: 3.5
     spreadAmount: 30
       reagents:
       - ReagentId: SpaceCleaner
         Quantity: 30
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
     sound: /Audio/Items/smoke_grenade_smoke.ogg
   - type: ExplodeOnTrigger
   - type: Explosive
index c5dbb1b216c89751bc9007a2bed853431cef4d65..5259f71f586eb7822849281c65da68a654ddcf5b 100644 (file)
@@ -47,7 +47,7 @@
   - type: Repairable
     qualityNeeded: "Anchoring"
     doAfterDelay: 3
-  - type: TriggerWhenEmpty
+  - type: TriggerOnEmptyGunshot
   - type: ExplodeOnTrigger
   - type: Explosive
     explosionType: Default
     interactSuccessString: petting-success-generic
     interactFailureString: petting-failure-generic
     interactSuccessSound:
-      path: /Audio/Animals/snake_hiss.ogg
\ No newline at end of file
+      path: /Audio/Animals/snake_hiss.ogg
index 4d65cc0806a76a12cd0d028ab55625a5844cc137..d843463886428e83de44883c951ba5648bfc522a 100644 (file)
@@ -14,7 +14,8 @@
     quickEquip: false
     slots:
     - Belt
-  - type: OnUseTimerTrigger
+  - type: TriggerOnUse
+  - type: TimerTrigger
     delay: 3
   - type: Damageable
     damageContainer: Inorganic
@@ -25,6 +26,7 @@
         damage: 10
       behaviors:
       - !type:TriggerBehavior
+        keyOut: timer # explode immediately
       - !type:DoActsBehavior
         acts: ["Destruction"]
   - type: Appearance
   id: ExGrenade
   components:
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Explosive
     explosionType: Default
     maxIntensity: 10
     intensitySlope: 3
     totalIntensity: 120 # about a ~4 tile radius
     canCreateVacuum: false
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     beepSound:
       path: "/Audio/Effects/beep1.ogg"
       params:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/flashbang.rsi
   - type: FlashOnTrigger
+    keysIn:
+    - timer
     range: 7
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Effects/flash_bang.ogg"
   - type: DeleteOnTrigger
+    keysIn:
+    - timer
   - type: SpawnOnTrigger
+    keysIn:
+    - timer
     proto: GrenadeFlashEffect
+    predicted: true
   - type: Appearance
   - type: TimerTriggerVisuals
     primingSound:
         damage: 45
       behaviors:
       - !type:TriggerBehavior
+        keyOut: timer # immediately explode
       - !type:DoActsBehavior
         acts: ["Destruction"]
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 5
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Explosive
     explosionType: Minibomb
     totalIntensity: 200
   categories: [ HideSpawnMenu ]
   components:
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Explosive
     explosionType: Minibomb
     totalIntensity: 400
     intensitySlope: 30
     maxIntensity: 125
     canCreateVacuum: true
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 4.5
     beepSound:
       path: /Audio/Effects/Grenades/SelfDestruct/SDS_Charge2.ogg
   components:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/singularitygrenade.rsi
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 3
     beepInterval: 0.46
     beepSound:
       path: /Audio/Effects/Grenades/Supermatter/smbeep.ogg
       params:
         volume: -5
-  - type: SoundOnTrigger
-    removeOnTrigger: true
+  - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: /Audio/Effects/Grenades/Supermatter/supermatter_start.ogg
-      volume: 5
+      params:
+        volume: 5
   - type: AnchorOnTrigger
+    keysIn:
+    - timer
     removeOnTrigger: true
   - type: TwoStageTrigger
+    keysIn:
+    - timer
     triggerDelay: 10.45
     components:
       - type: AmbientSound
         radius: 6
         softness: 1
         offset: "0, 0"
-      - type: SoundOnTrigger
+      - type: EmitSoundOnTrigger
         sound:
           path: /Audio/Effects/Grenades/Supermatter/supermatter_end.ogg
           params:
             volume: 5
+        keysIn:
+        - stageTwo
       - type: DeleteOnTrigger
+        keysIn:
+        - stageTwo
   - type: StaticPrice
     price: 1000
 
   components:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/whiteholegrenade.rsi
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 3
     beepInterval: 0.69
-  - type: SoundOnTrigger
-    removeOnTrigger: true
+  - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: /Audio/Effects/Grenades/Supermatter/whitehole_start.ogg
-      volume: 5
+      params:
+        volume: 5
+  - type: AnchorOnTrigger
+    keysIn:
+    - timer
+    removeOnTrigger: true
   - type: TwoStageTrigger
     triggerDelay: 11.14
     components:
         radius: 6
         softness: 1
         offset: "0, 0"
-      - type: SoundOnTrigger
+      - type: EmitSoundOnTrigger
+        keysIn:
+        - stageTwo
         sound:
           path: /Audio/Effects/Grenades/Supermatter/supermatter_end.ogg
           params:
             volume: 15
+        positional: true
       - type: DeleteOnTrigger
+        keysIn:
+        - stageTwo
   - type: StaticPrice
     price: 1000
 
   components:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/nukenade.rsi
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 5
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Explosive
     explosionType: Default
     totalIntensity: 20000 # ~15 tile radius.
         damage: 50
       behaviors:
       - !type:TriggerBehavior
+        keyOut: timer # immediately explode
       - !type:DoActsBehavior
         acts: ["Destruction"]
   - type: Appearance
   - type: Sprite
     sprite: Objects/Weapons/Grenades/empgrenade.rsi
   - type: EmpOnTrigger
+    keysIn:
+    - timer
     range: 5.5
     energyConsumption: 50000
   - type: DeleteOnTrigger
+    keysIn:
+    - timer
   - type: Appearance
   - type: TimerTriggerVisuals
     primingSound:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/holyhandgrenade.rsi
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Explosive
     explosionType: Default # same as macrobomb
     totalIntensity: 3500
     intensitySlope: 15
     maxIntensity: 70
     canCreateVacuum: true
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 3 # by canon
   - type: PointLight
     radius: 7
   - type: Sprite
     sprite: Objects/Weapons/Grenades/smoke.rsi
   - type: SmokeOnTrigger
+    keysIn:
+    - timer
     duration: 30
     spreadAmount: 50
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound: /Audio/Items/smoke_grenade_smoke.ogg
   - type: DeleteOnTrigger
+    keysIn:
+    - timer
   - type: TimerTriggerVisuals
     primingSound:
       path: /Audio/Items/smoke_grenade_prime.ogg
   - type: Sprite
     sprite: Objects/Weapons/Grenades/janitor.rsi
   - type: SmokeOnTrigger
+    keysIn:
+    - timer
     duration: 15
     spreadAmount: 50
     smokePrototype: Foam
   - type: Sprite
     sprite: Objects/Weapons/Grenades/tear_gas.rsi
   - type: SmokeOnTrigger
+    keysIn:
+    - timer
     duration: 10
     spreadAmount: 30
     smokePrototype: TearGasSmokeWhite
   components:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/metalfoam.rsi
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 5
   - type: SmokeOnTrigger
+    keysIn:
+    - timer
     duration: 10
     spreadAmount: 20
     smokePrototype: AluminiumMetalFoam
   components:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/airboom.rsi
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound: /Audio/Items/smoke_grenade_smoke.ogg
   - type: TimerTriggerVisuals
     primingSound:
       path: /Audio/Items/smoke_grenade_prime.ogg
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 3
   - type: ReleaseGasOnTrigger
+    keysIn:
+    - timer
     removeFraction: 0.25
     air:
       volume: 1000
   - type: Sprite
     sprite: Objects/Weapons/Grenades/grenade.rsi
   - type: DeleteOnTrigger
+    keysIn:
+    - timer
   - type: SpawnOnTrigger
+    keysIn:
+    - timer
     proto: GrenadeFlashEffect
-  - type: SoundOnTrigger
+    predicted: true
+  - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: /Audio/Effects/Emotes/parp1.ogg
   - type: Appearance
   components:
   - type: Sprite
     sprite: Objects/Weapons/Grenades/syndgrenade.rsi
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     delay: 5
-  - type: SoundOnTrigger
+  - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: /Audio/Effects/Emotes/parp1.ogg
   - type: Appearance
index bdd8370b104b000cc26e5258b24bbba486854500..f9ce11a3b988043814ea0f5a8e6e0cba58dc0a0b 100644 (file)
@@ -7,6 +7,8 @@
   - type: Damageable
     damageContainer: Inorganic
   - type: DeleteOnTrigger
+    keysIn:
+    - timer
   - type: Destructible
     thresholds:
     - trigger:
@@ -14,6 +16,7 @@
         damage: 10
       behaviors:
       - !type:TriggerBehavior
+        keyOut: timer # explode immediately
   - type: ContainerContainer
     containers:
       cluster-payload: !type:Container
     fillPrototype: PelletClusterRubber
     capacity: 30
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Effects/flash_bang.ogg"
   - type: SpawnOnTrigger
+    keysIn:
+    - timer
     proto: GrenadeFlashEffect
-  - type: OnUseTimerTrigger
+    predicted: true
+  - type: TriggerOnUse
+  - type: TimerTrigger
     initialBeepDelay: 0
     beepInterval: 2
     delay: 3.5
@@ -80,7 +89,8 @@
   - type: ProjectileGrenade
     fillPrototype: PelletClusterIncendiary
     capacity: 30
-  - type: OnUseTimerTrigger
+  - type: TriggerOnUse
+  - type: TimerTrigger
     beepSound:
       path: "/Audio/Effects/beep1.ogg"
       params:
@@ -89,6 +99,8 @@
     beepInterval: 2
     delay: 3.5
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg"
   - type: StaticPrice
   - type: ProjectileGrenade
     fillPrototype: PelletClusterLethal
     capacity: 30
-  - type: OnUseTimerTrigger
+  - type: TriggerOnUse
+  - type: TimerTrigger
     beepSound:
       path: "/Audio/Effects/beep1.ogg"
       params:
     beepInterval: 2
     delay: 3.5
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg"
   - type: StaticPrice
index 9038df13e01b271c8de4a780a7b8a6ce50175ed2..ce031ba6ff9d5f2c21df5e5959b10c4119752a6e 100644 (file)
         damage: 10
       behaviors:
       - !type:TriggerBehavior
+        keyOut: timer # explode immediately
   - type: ScatteringGrenade
-  - type: OnUseTimerTrigger
+  - type: TriggerOnUse
+  - type: TimerTrigger
     delay: 3
   - type: Tag
     tags:
@@ -81,6 +83,8 @@
           Primed: { state: primed }
           Unprimed: { state: icon }
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Machines/door_lock_off.ogg"
 
   - type: ScatteringGrenade
     fillPrototype: ExGrenade
     distance: 4
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     beepSound:
       path: "/Audio/Effects/beep1.ogg"
       params:
     initialBeepDelay: 0
     beepInterval: 0.5
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Machines/door_lock_off.ogg"
   - type: StaticPrice
       types:
         Blunt: 10
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Items/bikehorn.ogg"
 
       types:
         Blunt: 10
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Effects/flash_bang.ogg"
   - type: StaticPrice
     fillPrototype: BulletFoam
     capacity: 30
     velocity: 30
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     beepSound:
       path: "/Audio/Effects/beep1.ogg"
       params:
     initialBeepDelay: 0
     beepInterval: 2
   - type: EmitSoundOnTrigger
+    keysIn:
+    - timer
     sound:
       path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg"
index 7f69d77f93e2efd7d7e759034a7d4a783e216996..2f6ac834ba46f3506d2eb02f5be337cc8559a4b9 100644 (file)
   description: An ultrabright flashbulb with a proximity trigger, useful for making an area security-only.
   components:
     - type: EmitSoundOnTrigger
+      predicted: true
       sound:
         path: /Audio/Weapons/flash.ogg
     - type: FlashOnTrigger
index 65eb07f064b16dcd78979ca54d30e02195d3a353..dc3aa359f44ca5034aa3c8087604bb3201e08bf5 100644 (file)
       intensitySlope: 5
       maxIntensity: 4
     - type: ExplodeOnTrigger
+      keysIn:
+      - timer
     # If you nerf the syndicate bomb in any major way, this should probably drop down to at least 100s (not 90s to compensate for slower movement speed & less lag in SS14)
     # Unless, of course, you want the 90 seconds regardless. I can't stop you.
-    - type: OnUseTimerTrigger
+    - type: TimerTrigger
       delay: 180
       delayOptions: [180, 240, 300, 600, 900]
       initialBeepDelay: 0
   components:
     - type: Defusable
       disposable: true
-    - type: OnUseTimerTrigger
+    - type: TimerTrigger
       delay: 10
       delayOptions: [10, 20, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300]
index 20706356767d84fb3dc379f4257b1429dde81c0e..d3f0d0e2cbac2fe0757542bd0a2ef9e31608549b 100644 (file)
@@ -77,7 +77,7 @@
           Unprimed:
             state: gibtonite_inactive
             visible: false
-  - type: OnUseTimerTrigger
+  - type: TimerTrigger
     examinable: false
     beepInterval: 0.4
     beepSound:
@@ -86,6 +86,8 @@
     min: 8
     max: 10
   - type: ExplodeOnTrigger
+    keysIn:
+    - timer
   - type: Explosive
     explosionType: DemolitionCharge
     totalIntensity: 450
       - !type:DoActsBehavior
         acts: ["Destruction"]
       - !type:TriggerBehavior
+        keyOut: timer # explode immediately
 
 # Ore veins
 - type: entity
index 8ebd71a346838063d99bc813e896f3c69b467b4a..4d6880d0b837328563e6d643702250fd0e24a4d5 100644 (file)
@@ -29,7 +29,7 @@
   To arm a bomb, you can either [color=yellow]right click[/color] and click [color=yellow]Begin countdown[/click], or [color=yellow]alt-click[/color] the bomb. It will begin beeping.
 
   ## Time
-  A bomb has a limited time, at a minimum of [protodata="SyndicateBomb" comp="OnUseTimerTrigger" member="ShortestDelayOption"/] seconds and a maximum of [protodata="SyndicateBomb" comp="OnUseTimerTrigger" member="LongestDelayOption"/] seconds. You can view the timer by examining it, unless the Proceed wire is cut. Once the timer hits zero, the bomb will detonate.
+  A bomb has a limited time, at a minimum of [protodata="SyndicateBomb" comp="TimerTrigger" member="ShortestDelayOption"/] seconds and a maximum of [protodata="SyndicateBomb" comp="TimerTrigger" member="LongestDelayOption"/] seconds. You can view the timer by examining it, unless the Proceed wire is cut. Once the timer hits zero, the bomb will detonate.
 
   ## Bolts
   By default, once armed, a bomb will bolt itself to the ground. You must find the BOLT wire and cut it to disable the bolts, after which you can unwrench it and throw it into space.