diff --git a/Content.Client/Explosion/SmokeOnTriggerSystem.cs b/Content.Client/Explosion/SmokeOnTriggerSystem.cs
deleted file mode 100644
index cac255e1ba..0000000000
--- a/Content.Client/Explosion/SmokeOnTriggerSystem.cs
+++ /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
index 5fa9bbfd23..0000000000
--- a/Content.Client/Explosion/TriggerOnProximityComponent.cs
+++ /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
index e18569a18e..0000000000
--- a/Content.Client/Explosion/TriggerSystem.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Client.Explosion;
-
-public sealed partial class TriggerSystem : EntitySystem
-{
- public override void Initialize()
- {
- base.Initialize();
- InitializeProximity();
- }
-}
diff --git a/Content.Client/HotPotato/HotPotatoSystem.cs b/Content.Client/HotPotato/HotPotatoSystem.cs
index 028a3b70d9..a1495ab994 100644
--- a/Content.Client/HotPotato/HotPotatoSystem.cs
+++ b/Content.Client/HotPotato/HotPotatoSystem.cs
@@ -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)));
}
}
}
diff --git a/Content.Client/Trigger/TimerTriggerVisualizerComponent.cs b/Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs
similarity index 84%
rename from Content.Client/Trigger/TimerTriggerVisualizerComponent.cs
rename to Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs
index 0e5a74b83b..0cb89edd89 100644
--- a/Content.Client/Trigger/TimerTriggerVisualizerComponent.cs
+++ b/Content.Client/Trigger/Components/TimerTriggerVisualizerComponent.cs
@@ -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
///
/// The RSI state used while the device has not been primed.
///
- [DataField("unprimedSprite")]
- [ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public string UnprimedSprite = "icon";
///
/// 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.
///
- [DataField("primingSprite")]
+ [DataField]
public string PrimingSprite = "primed";
///
/// The sound played when the device is primed.
/// Not VVWrite-able because it's only used at component init to construct the priming animation.
///
- [DataField("primingSound")]
+ [DataField, ViewVariables]
public SoundSpecifier? PrimingSound;
///
/// The actual priming animation.
/// Constructed at component init from the sprite and sound.
///
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
public Animation PrimingAnimation = default!;
}
diff --git a/Content.Client/Explosion/TriggerSystem.Proximity.cs b/Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs
similarity index 91%
rename from Content.Client/Explosion/TriggerSystem.Proximity.cs
rename to Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs
index 03e7436971..7954399505 100644
--- a/Content.Client/Explosion/TriggerSystem.Proximity.cs
+++ b/Content.Client/Trigger/Systems/ProximityTriggerAnimationSystem.cs
@@ -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(OnProximityInit);
SubscribeLocalEvent(OnProxAppChange);
SubscribeLocalEvent(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
index 0000000000..a183282dde
--- /dev/null
+++ b/Content.Client/Trigger/Systems/ReleaseGasOnTriggerSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Client.Trigger.Systems;
+
+public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem;
diff --git a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs b/Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs
similarity index 80%
rename from Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
rename to Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs
index b3d85f2017..7c977c7589 100644
--- a/Content.Client/Trigger/TimerTriggerVisualizerSystem.cs
+++ b/Content.Client/Trigger/Systems/TimerTriggerVisualizerSystem.cs
@@ -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
{
@@ -17,25 +16,26 @@ public sealed class TimerTriggerVisualizerSystem : VisualizerSystem(OnComponentInit);
}
- private void OnComponentInit(EntityUid uid, TimerTriggerVisualsComponent comp, ComponentInit args)
+ private void OnComponentInit(Entity 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) }
}
);
}
diff --git a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs
index 4db79373d3..f5a15295fd 100644
--- a/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs
+++ b/Content.IntegrationTests/Tests/Payload/ModularGrenadeTests.cs
@@ -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(false);
+ AssertComp(false);
await InteractUsing(Trigger);
- AssertComp();
+ AssertComp();
await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false);
await InteractUsing(Pry);
- AssertComp(false);
+ AssertComp(false);
// Trigger was dropped to floor, not deleted.
await FindEntity(Trigger, LookupFlags.Uncontained);
// Re-insert
await InteractUsing(Trigger);
- AssertComp();
+ AssertComp();
// Insert & remove payload.
await InteractUsing(Payload);
@@ -56,13 +56,14 @@ public sealed class ModularGrenadeTests : InteractionTest
await Pickup();
AssertComp(false);
await UseInHand();
+ AssertComp(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();
- while (timer.TimeRemaining >= 0)
+ var triggerSys = SEntMan.System();
+ 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
index aa6c5ba2bd..0000000000
--- a/Content.Server/AlertLevel/AlertLevelChangeOnTriggerComponent.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Content.Server.AlertLevel.Systems;
-
-namespace Content.Server.AlertLevel;
-///
-/// This component is for changing the alert level of the station when triggered.
-///
-[RegisterComponent, Access(typeof(AlertLevelChangeOnTriggerSystem))]
-public sealed partial class AlertLevelChangeOnTriggerComponent : Component
-{
- ///
- ///The alert level to change to when triggered.
- ///
- [DataField]
- public string Level = "blue";
-
- ///
- ///Whether to play the sound when the alert level changes.
- ///
- [DataField]
- public bool PlaySound = true;
-
- ///
- ///Whether to say the announcement when the alert level changes.
- ///
- [DataField]
- public bool Announce = true;
-
- ///
- ///Force the alert change. This applies if the alert level is not selectable or not.
- ///
- [DataField]
- public bool Force = false;
-}
diff --git a/Content.Server/Animals/Systems/ParrotMemorySystem.cs b/Content.Server/Animals/Systems/ParrotMemorySystem.cs
index 56843094a1..a88957913f 100644
--- a/Content.Server/Animals/Systems/ParrotMemorySystem.cs
+++ b/Content.Server/Animals/Systems/ParrotMemorySystem.cs
@@ -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
index d879cbf1bf..0000000000
--- a/Content.Server/Chat/SpeakOnTriggerComponent.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Shared.Dataset;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Chat;
-
-///
-/// Makes the entity speak when triggered. If the item has UseDelay component, the system will respect that cooldown.
-///
-[RegisterComponent]
-public sealed partial class SpeakOnTriggerComponent : Component
-{
- ///
- /// The identifier for the dataset prototype containing messages to be spoken by this entity.
- ///
- [DataField(required: true)]
- public ProtoId Pack = string.Empty;
-}
diff --git a/Content.Server/Chat/SuicideSystem.cs b/Content.Server/Chat/SuicideSystem.cs
index 9ae50a8c97..dca2959f98 100644
--- a/Content.Server/Chat/SuicideSystem.cs
+++ b/Content.Server/Chat/SuicideSystem.cs
@@ -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);
diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs
index 0b3a9c0a66..d49da57801 100644
--- a/Content.Server/Chat/Systems/ChatSystem.cs
+++ b/Content.Server/Chat/Systems/ChatSystem.cs
@@ -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
index 8a0ee51076..0000000000
--- a/Content.Server/Damage/Systems/DamageUserOnTriggerSystem.cs
+++ /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(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;
- }
-}
diff --git a/Content.Server/Defusable/Systems/DefusableSystem.cs b/Content.Server/Defusable/Systems/DefusableSystem.cs
index 1e9caece94..5c589d2131 100644
--- a/Content.Server/Defusable/Systems/DefusableSystem.cs
+++ b/Content.Server/Defusable/Systems/DefusableSystem.cs
@@ -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(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(uid, out var timerTrigger))
+ if (TryComp(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(uid);
- RemComp(uid);
+ RemComp(uid);
}
RemComp(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;
}
diff --git a/Content.Server/Destructible/DestructibleSystem.cs b/Content.Server/Destructible/DestructibleSystem.cs
index 82d5ffcb9a..b4a79c6830 100644
--- a/Content.Server/Destructible/DestructibleSystem.cs
+++ b/Content.Server/Destructible/DestructibleSystem.cs
@@ -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
{
diff --git a/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs
index 97a5f8b7ef..cf337ac76a 100644
--- a/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs
+++ b/Content.Server/Destructible/Thresholds/Behaviors/TimerStartBehavior.cs
@@ -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);
}
}
diff --git a/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs b/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs
index 03bdb8ff69..66da79063c 100644
--- a/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs
+++ b/Content.Server/Destructible/Thresholds/Behaviors/TriggerBehavior.cs
@@ -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
{
+ ///
+ /// The trigger key to use when triggering.
+ ///
+ [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);
}
}
diff --git a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs
index d957f0171e..ff20ac4d8d 100644
--- a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs
+++ b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs
@@ -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;
diff --git a/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs b/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs
index 45e4d21750..7743a97d72 100644
--- a/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs
+++ b/Content.Server/DeviceLinking/Systems/MemoryCellSystem.cs
@@ -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;
diff --git a/Content.Server/DeviceLinking/Systems/SignallerSystem.cs b/Content.Server/DeviceLinking/Systems/SignallerSystem.cs
index a5091508ed..7d684d1cd5 100644
--- a/Content.Server/DeviceLinking/Systems/SignallerSystem.cs
+++ b/Content.Server/DeviceLinking/Systems/SignallerSystem.cs
@@ -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(OnInit);
SubscribeLocalEvent(OnUseInHand);
- SubscribeLocalEvent(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;
- }
}
diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs
index bb9e71cfc3..05dfffa4a9 100644
--- a/Content.Server/Electrocution/ElectrocutionSystem.cs
+++ b/Content.Server/Electrocution/ElectrocutionSystem.cs
@@ -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 StatusEffectKey = "Electrocution";
+ private static readonly ProtoId StatusKeyIn = "Electrocution";
private static readonly ProtoId DamageType = "Shock";
private static readonly ProtoId 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(uid, StatusEffectKey, time, refresh, statusEffects))
+ if (!_statusEffects.TryAddStatusEffect(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
index fac509ea9f..0000000000
--- a/Content.Server/Emp/EmpOnTriggerComponent.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Content.Server.Emp;
-
-///
-/// Upon being triggered will EMP area around it.
-///
-[RegisterComponent]
-[Access(typeof(EmpSystem))]
-public sealed partial class EmpOnTriggerComponent : Component
-{
- [DataField("range"), ViewVariables(VVAccess.ReadWrite)]
- public float Range = 1.0f;
-
- ///
- /// How much energy will be consumed per battery in range
- ///
- [DataField("energyConsumption"), ViewVariables(VVAccess.ReadWrite)]
- public float EnergyConsumption;
-
- ///
- /// How long it disables targets in seconds
- ///
- [DataField("disableDuration"), ViewVariables(VVAccess.ReadWrite)]
- public float DisableDuration = 60f;
-}
diff --git a/Content.Server/Emp/EmpSystem.cs b/Content.Server/Emp/EmpSystem.cs
index 4b5143aa40..b8bfc63afe 100644
--- a/Content.Server/Emp/EmpSystem.cs
+++ b/Content.Server/Emp/EmpSystem.cs
@@ -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(OnExamine);
- SubscribeLocalEvent(HandleEmpTrigger);
SubscribeLocalEvent(OnRadioSendAttempt);
SubscribeLocalEvent(OnRadioReceiveAttempt);
@@ -28,14 +26,7 @@ public sealed class EmpSystem : SharedEmpSystem
SubscribeLocalEvent(OnCameraSetActive);
}
- ///
- /// Triggers an EMP pulse at the given location, by first raising an , then a raising on all entities in range.
- ///
- /// The location to trigger the EMP pulse at.
- /// The range of the EMP pulse.
- /// The amount of energy consumed by the EMP pulse.
- /// The duration of the EMP effects.
- 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
index 9d8073413c..0000000000
--- a/Content.Server/Explosion/Components/ActiveTriggerOnTimedCollideComponent.cs
+++ /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
index c01aeb91e5..0000000000
--- a/Content.Server/Explosion/Components/AutomatedTimerComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// Disallows starting the timer by hand, must be stuck or triggered by a system using StartTimer.
-///
-[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
index d83b57c188..0000000000
--- a/Content.Server/Explosion/Components/OnTrigger/AnchorOnTriggerComponent.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-///
-/// Will anchor the attached entity upon a .
-///
-[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
index 9846cad84b..0000000000
--- a/Content.Server/Explosion/Components/OnTrigger/DeleteOnTriggerComponent.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-///
-/// Will delete the attached entity upon a .
-///
-[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
index da58467659..0000000000
--- a/Content.Server/Explosion/Components/OnTrigger/GibOnTriggerComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// Gibs on trigger, self explanatory.
-/// Also in case of an implant using this, gibs the implant user instead.
-///
-[RegisterComponent]
-public sealed partial class GibOnTriggerComponent : Component
-{
- ///
- /// Should gibbing also delete the owners items?
- ///
- [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
index 7f9f16a227..0000000000
--- a/Content.Server/Explosion/Components/OnTrigger/SoundOnTriggerComponent.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Audio;
-
-namespace Content.Server.Explosion.Components;
-
-///
-/// Will play sound from the attached entity upon a .
-///
-[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
index a63d6fcf66..0000000000
--- a/Content.Server/Explosion/Components/OnTrigger/TwoStageTriggerComponent.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Explosion.Components.OnTrigger;
-
-///
-/// After being triggered applies the specified components and runs triggers again.
-///
-[RegisterComponent, AutoGenerateComponentPause]
-public sealed partial class TwoStageTriggerComponent : Component
-{
- ///
- /// How long it takes for the second stage to be triggered.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("triggerDelay")]
- public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10);
-
- ///
- /// This list of components that will be added for the second trigger.
- ///
- [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;
-}
diff --git a/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs b/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs
index 58d687e025..20a73b46b5 100644
--- a/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs
+++ b/Content.Server/Explosion/Components/ProjectileGrenadeComponent.cs
@@ -45,4 +45,10 @@ public sealed partial class ProjectileGrenadeComponent : Component
///
[DataField]
public float MaxVelocity = 6f;
+
+ ///
+ /// The trigger key that will activate the grenade.
+ ///
+ [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
index a553cc047a..0000000000
--- a/Content.Server/Explosion/Components/ShockOnTriggerComponent.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-///
-/// A component that electrocutes an entity having this component when a trigger is triggered.
-///
-[RegisterComponent, AutoGenerateComponentPause]
-[Access(typeof(TriggerSystem))]
-public sealed partial class ShockOnTriggerComponent : Component
-{
- ///
- /// The force of an electric shock when the trigger is triggered.
- ///
- [DataField]
- public int Damage = 5;
-
- ///
- /// Duration of electric shock when the trigger is triggered.
- ///
- [DataField]
- public TimeSpan Duration = TimeSpan.FromSeconds(2);
-
- ///
- /// The minimum delay between repeating triggers.
- ///
- [DataField]
- public TimeSpan Cooldown = TimeSpan.FromSeconds(4);
-
- ///
- /// When can the trigger run again?
- ///
- [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
index c28ec7faeb..0000000000
--- a/Content.Server/Explosion/Components/SpawnOnTriggerComponent.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Explosion.Components;
-
-///
-/// Spawns a protoype when triggered.
-///
-[RegisterComponent, Access(typeof(TriggerSystem))]
-public sealed partial class SpawnOnTriggerComponent : Component
-{
- ///
- /// The prototype to spawn.
- ///
- [DataField(required: true)]
- public EntProtoId Proto = string.Empty;
-
- ///
- /// Use MapCoordinates for spawning?
- /// Set to true if you don't want the new entity parented to the spawner.
- ///
- [DataField]
- public bool mapCoords;
-}
diff --git a/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs b/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs
deleted file mode 100644
index 9adc6dab87..0000000000
--- a/Content.Server/Explosion/Components/TimerStartOnSignalComponent.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Explosion.Components
-{
- ///
- /// Sends a trigger when signal is received.
- ///
- [RegisterComponent]
- public sealed partial class TimerStartOnSignalComponent : Component
- {
- [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string Port = "Timer";
- }
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs b/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs
deleted file mode 100644
index 1531c7425e..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnActivateComponent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// Triggers on click.
-///
-[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
index 28c2ed8c34..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnCollideComponent.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// Triggers when colliding with another entity.
-///
-[RegisterComponent]
-public sealed partial class TriggerOnCollideComponent : Component
-{
- ///
- /// The fixture with which to collide.
- ///
- [DataField(required: true)]
- public string FixtureID = string.Empty;
-
- ///
- /// Doesn't trigger if the other colliding fixture is nonhard.
- ///
- [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
index a6cdda247c..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnMobstateChangeComponent.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Content.Shared.Mobs;
-
-namespace Content.Server.Explosion.Components;
-
-///
-/// Use where you want something to trigger on mobstate change
-///
-[RegisterComponent]
-public sealed partial class TriggerOnMobstateChangeComponent : Component
-{
- ///
- /// What state should trigger this?
- ///
- [ViewVariables]
- [DataField("mobState", required: true)]
- public List MobState = new();
-
- ///
- /// If true, prevents suicide attempts for the trigger to prevent cheese.
- ///
- [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
index 4f3fb4754e..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnProximityComponent.cs
+++ /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
-{
-
- ///
- /// Raises a whenever an entity collides with a fixture attached to the owner of this component.
- ///
- [RegisterComponent, AutoGenerateComponentPause]
- public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent
- {
- public const string FixtureID = "trigger-on-proximity-fixture";
-
- [ViewVariables]
- public readonly Dictionary Colliding = new();
-
- ///
- /// What is the shape of the proximity fixture?
- ///
- [ViewVariables]
- [DataField("shape")]
- public IPhysShape Shape = new PhysShapeCircle(2f);
-
- ///
- /// How long the the proximity trigger animation plays for.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("animationDuration")]
- public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
-
- ///
- /// Whether the entity needs to be anchored for the proximity to work.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("requiresAnchored")]
- public bool RequiresAnchored = true;
-
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("enabled")]
- public bool Enabled = true;
-
- ///
- /// The minimum delay between repeating triggers.
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("cooldown")]
- public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
-
- ///
- /// When can the trigger run again?
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("nextTrigger", customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan NextTrigger = TimeSpan.Zero;
-
- ///
- /// When will the visual state be updated again after activation?
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("nextVisualUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan NextVisualUpdate = TimeSpan.Zero;
-
- ///
- /// What speed should the other object be moving at to trigger the proximity fixture?
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("triggerSpeed")]
- public float TriggerSpeed = 3.5f;
-
- ///
- /// If this proximity is triggered should we continually repeat it?
- ///
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("repeating")]
- public bool Repeating = true;
-
- ///
- /// What layer is the trigger fixture on?
- ///
- [ViewVariables]
- [DataField("layer", customTypeSerializer: typeof(FlagSerializer))]
- 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
index fb7495612c..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnSignalComponent.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Explosion.Components
-{
- ///
- /// Sends a trigger when signal is received.
- ///
- [RegisterComponent]
- public sealed partial class TriggerOnSignalComponent : Component
- {
- [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer))]
- public string Port = "Trigger";
- }
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs b/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs
deleted file mode 100644
index 16632b9b72..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnSlipComponent.cs
+++ /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
index 704ff410cd..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnSpawnComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// calls the trigger when the object is initialized
-///
-[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
index 49e99ff785..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnStepTriggerComponent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// 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.
-///
-[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
index d53bcbcc5d..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnTimedCollideComponent.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// Triggers when the entity is overlapped for the specified duration.
-///
-[RegisterComponent]
-public sealed partial class TriggerOnTimedCollideComponent : Component
-{
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("threshold")]
- public float Threshold;
-
- ///
- /// A collection of entities that are colliding with this, and their own unique accumulator.
- ///
- [ViewVariables]
- public readonly Dictionary Colliding = new();
-}
diff --git a/Content.Server/Explosion/Components/TriggerOnUseComponent.cs b/Content.Server/Explosion/Components/TriggerOnUseComponent.cs
deleted file mode 100644
index 2b44d2fbac..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnUseComponent.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// Triggers on use in hand.
-///
-[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
index 7d07f496d0..0000000000
--- a/Content.Server/Explosion/Components/TriggerOnVoiceComponent.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Content.Server.Explosion.Components
-{
- ///
- /// Sends a trigger when the keyphrase is heard
- ///
- [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
index ad39b9c515..0000000000
--- a/Content.Server/Explosion/Components/TriggerWhenEmptyComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Content.Server.Explosion.Components;
-
-///
-/// Triggers a gun when attempting to shoot while it's empty
-///
-[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
index 80becf17cc..0000000000
--- a/Content.Server/Explosion/Components/TriggerWhitelistComponent.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Content.Shared.Whitelist;
-
-namespace Content.Server.Explosion.Components;
-
-///
-/// Checks if the user of a Trigger satisfies a whitelist and blacklist condition.
-/// Cancels the trigger otherwise.
-///
-[RegisterComponent]
-public sealed partial class TriggerWhitelistComponent : Component
-{
- ///
- /// Whitelist for what entites can cause this trigger.
- ///
- [DataField]
- public EntityWhitelist? Whitelist;
-
- ///
- /// Blacklist for what entites can cause this trigger.
- ///
- [DataField]
- public EntityWhitelist? Blacklist;
-}
diff --git a/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs
index 6953010e46..4b93f22cd1 100644
--- a/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs
+++ b/Content.Server/Explosion/EntitySystems/ProjectileGrenadeSystem.cs
@@ -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
///
private void OnFragTrigger(Entity 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
index ae3f84f843..0000000000
--- a/Content.Server/Explosion/EntitySystems/ReleaseGasOnTriggerSystem.cs
+++ /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;
-
-///
-/// 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.
-///
-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(OnTrigger);
- }
-
- ///
- /// Shrimply sets the component to active when triggered, allowing it to release over time.
- ///
- private void OnTrigger(Entity 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();
-
- 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(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(uid);
- continue;
- }
- }
- }
-
- private void UpdateAppearance(Entity 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
index 9e595c5d9e..0000000000
--- a/Content.Server/Explosion/EntitySystems/RepulseAttractOnTriggerSystem.cs
+++ /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(OnTrigger);
- }
-
- private void OnTrigger(Entity 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);
- }
-}
diff --git a/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs b/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs
index 2657ba3449..5f7df28beb 100644
--- a/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs
+++ b/Content.Server/Explosion/EntitySystems/ScatteringGrenadeSystem.cs
@@ -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
///
private void OnScatteringTrigger(Entity 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(contentUid, out var contentTimer))
{
additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax);
- var contentTimer = EnsureComp(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
index 19335d3446..0000000000
--- a/Content.Server/Explosion/EntitySystems/SmokeOnTriggerSystem.cs
+++ /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;
-
-///
-/// Handles creating smoke when is triggered.
-///
-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(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(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
index d06e9fa1c2..0000000000
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.OnUse.cs
+++ /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(OnTimerUse);
- SubscribeLocalEvent(OnExamined);
- SubscribeLocalEvent>(OnGetAltVerbs);
- SubscribeLocalEvent(OnStuck);
- SubscribeLocalEvent(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)));
- }
-
- ///
- /// Add an alt-click interaction that cycles through delays.
- ///
- private void OnGetAltVerbs(EntityUid uid, OnUseTimerTriggerComponent component, GetVerbsEvent 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 ent, ref MapInitEvent args)
- {
- var (_, comp) = ent;
-
- if (!TryComp(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(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
index 426bade10e..0000000000
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Proximity.cs
+++ /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(OnProximityStartCollide);
- SubscribeLocalEvent(OnProximityEndCollide);
- SubscribeLocalEvent(OnMapInit);
- SubscribeLocalEvent(OnProximityShutdown);
- // Shouldn't need re-anchoring.
- SubscribeLocalEvent(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(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(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();
- 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
index 99e8c97d53..0000000000
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Signal.cs
+++ /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(OnSignalReceived);
- SubscribeLocalEvent(OnInit);
-
- SubscribeLocalEvent(OnTimerSignalReceived);
- SubscribeLocalEvent(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
index ea10b7c69b..0000000000
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs
+++ /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(OnTimerCollide);
- SubscribeLocalEvent(OnTimerEndCollide);
- SubscribeLocalEvent(OnComponentRemove);
- }
-
- private void OnTimerCollide(EntityUid uid, TriggerOnTimedCollideComponent component, ref StartCollideEvent args)
- {
- //Ensures the entity trigger will have an active component
- EnsureComp(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(uid))
- RemComp(uid);
- }
-
- private void OnComponentRemove(EntityUid uid, TriggerOnTimedCollideComponent component, ComponentRemove args)
- {
- if (HasComp(uid))
- RemComp(uid);
- }
-
- private void UpdateTimedCollide(float frameTime)
- {
- var query = EntityQueryEnumerator();
- 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
index a96508e37c..0000000000
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Voice.cs
+++ /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(OnVoiceInit);
- SubscribeLocalEvent(OnVoiceExamine);
- SubscribeLocalEvent>(OnVoiceGetAltVerbs);
- SubscribeLocalEvent(OnListen);
- }
-
- private void OnVoiceInit(EntityUid uid, TriggerOnVoiceComponent component, ComponentInit args)
- {
- if (component.IsListening)
- EnsureComp(uid).Range = component.ListenRange;
- else
- RemCompDeferred(uid);
- }
-
- private void OnListen(Entity 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 ent, ref GetVerbsEvent 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(ent);
- }
- });
- }
-
- public void StartRecording(Entity ent, EntityUid user)
- {
- var component = ent.Comp;
- component.IsRecording = true;
- EnsureComp(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 ent)
- {
- var component = ent.Comp;
- component.IsRecording = false;
- if (string.IsNullOrWhiteSpace(component.KeyPhrase))
- RemComp(ent);
-
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-stop-recording"), ent);
- }
-
- public void FinishRecording(Entity 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)));
- }
- }
- }
-}
-
-
-///
-/// Raised when a voice trigger is activated, containing the message that triggered it.
-///
-/// The EntityUid of the entity sending the message
-/// The contents of the message
-/// The message without the phrase that triggered it.
-[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
index f3a3f0c87c..0000000000
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.cs
+++ /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
-{
- ///
- /// Raised whenever something is Triggered on the entity.
- ///
- public sealed class TriggerEvent : HandledEntityEventArgs
- {
- public EntityUid Triggered { get; }
- public EntityUid? User { get; }
-
- public TriggerEvent(EntityUid triggered, EntityUid? user = null)
- {
- Triggered = triggered;
- User = user;
- }
- }
-
- ///
- /// Raised before a trigger is activated.
- ///
- [ByRefEvent]
- public record struct BeforeTriggerEvent(EntityUid Triggered, EntityUid? User, bool Cancelled = false);
-
- ///
- /// Raised when timer trigger becomes active.
- ///
- [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(OnSpawnTriggered);
- SubscribeLocalEvent(OnTriggerCollide);
- SubscribeLocalEvent(OnActivate);
- SubscribeLocalEvent(OnUse);
- SubscribeLocalEvent(OnImplantTrigger);
- SubscribeLocalEvent(OnStepTriggered);
- SubscribeLocalEvent(OnSlipTriggered);
- SubscribeLocalEvent(OnEmptyTriggered);
- SubscribeLocalEvent(OnRepeatInit);
-
- SubscribeLocalEvent(OnSpawnTrigger);
- SubscribeLocalEvent(HandleDeleteTrigger);
- SubscribeLocalEvent(HandleExplodeTrigger);
- SubscribeLocalEvent(HandleFlashTrigger);
- SubscribeLocalEvent(HandleGibTrigger);
-
- SubscribeLocalEvent(OnAnchorTrigger);
- SubscribeLocalEvent(OnSoundTrigger);
- SubscribeLocalEvent(HandleShockTrigger);
- SubscribeLocalEvent(HandleRattleTrigger);
-
- SubscribeLocalEvent(HandleWhitelist);
- }
-
- private void HandleWhitelist(Entity 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 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(uid);
- }
-
- private void OnSpawnTrigger(Entity 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(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(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(component.RadioChannel), uid);
- if (mobstate.CurrentState == MobState.Dead)
- _radioSystem.SendRadioMessage(uid, deathMessage, _prototypeManager.Index(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 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 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;
- }
-
- ///
- /// Start the timer for triggering the device.
- ///
- public void StartTimer(Entity 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(uid);
- Trigger(uid, user);
- return;
- }
-
- if (HasComp(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(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(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 toRemove = new();
- var query = EntityQueryEnumerator();
- 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(uid);
-
- // In case this is a re-usable grenade, un-prime it.
- if (TryComp(uid, out var appearance))
- _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
- }
- }
-
- private void UpdateRepeat()
- {
- var now = _timing.CurTime;
- var query = EntityQueryEnumerator();
- 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
index 8488fd14bb..0000000000
--- a/Content.Server/Explosion/EntitySystems/TwoStageTriggerSystem.cs
+++ /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(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();
- 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
index 11fb0156a4..0000000000
--- a/Content.Server/GhostKick/GhostKickUserOnTriggerComponent.cs
+++ /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
index 2a5d9065fa..0000000000
--- a/Content.Server/GhostKick/GhostKickUserOnTriggerSystem.cs
+++ /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(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;
- }
-}
diff --git a/Content.Server/Holopad/HolopadSystem.cs b/Content.Server/Holopad/HolopadSystem.cs
index f2bd0e05ad..884fb3ae71 100644
--- a/Content.Server/Holopad/HolopadSystem.cs
+++ b/Content.Server/Holopad/HolopadSystem.cs
@@ -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);
diff --git a/Content.Server/HotPotato/HotPotatoSystem.cs b/Content.Server/HotPotato/HotPotatoSystem.cs
index 8ca33fb8cd..554fe098b0 100644
--- a/Content.Server/HotPotato/HotPotatoSystem.cs
+++ b/Content.Server/HotPotato/HotPotatoSystem.cs
@@ -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(OnActiveTimer);
- SubscribeLocalEvent(OnMeleeHit);
- }
-
- private void OnActiveTimer(EntityUid uid, HotPotatoComponent comp, ref ActiveTimerTriggerEvent args)
- {
- EnsureComp(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(uid))
- return;
-
- comp.CanTransfer = true;
- foreach (var hitEntity in args.HitEntities)
- {
- if (!TryComp(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
index 939198c45e..0000000000
--- a/Content.Server/IgnitionSource/IgniteOnTriggerComponent.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.IgnitionSource;
-
-///
-/// Ignites for a certain length of time when triggered.
-/// Requires along with triggering components.
-///
-[RegisterComponent, Access(typeof(IgniteOnTriggerSystem))]
-public sealed partial class IgniteOnTriggerComponent : Component
-{
- ///
- /// Once ignited, the time it will unignite at.
- ///
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan IgnitedUntil = TimeSpan.Zero;
-
- ///
- /// How long the ignition source is active for after triggering.
- ///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan IgnitedTime = TimeSpan.FromSeconds(0.5);
-
- ///
- /// Sound to play when igniting.
- ///
- [DataField]
- public SoundSpecifier IgniteSound = new SoundCollectionSpecifier("WelderOn");
-}
diff --git a/Content.Server/LandMines/LandMineSystem.cs b/Content.Server/LandMines/LandMineSystem.cs
index 57d25ef8ab..fdea8e9c65 100644
--- a/Content.Server/LandMines/LandMineSystem.cs
+++ b/Content.Server/LandMines/LandMineSystem.cs
@@ -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
///
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);
}
///
@@ -44,7 +44,8 @@ public sealed class LandMineSystem : EntitySystem
///
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);
}
///
diff --git a/Content.Server/Mousetrap/MousetrapSystem.cs b/Content.Server/Mousetrap/MousetrapSystem.cs
deleted file mode 100644
index 3afe858ce0..0000000000
--- a/Content.Server/Mousetrap/MousetrapSystem.cs
+++ /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(OnUseInHand);
- SubscribeLocalEvent(BeforeDamageOnTrigger);
- SubscribeLocalEvent(OnStepTriggerAttempt);
- SubscribeLocalEvent(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);
- }
-}
diff --git a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
index 6594d7883b..c08576a5ce 100644
--- a/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
+++ b/Content.Server/Ninja/Systems/SpiderChargeSystem.cs
@@ -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
///
private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args)
{
+ if (args.Key != comp.TriggerKey)
+ return;
+
if (!TryComp(comp.Planter, out var ninja))
return;
diff --git a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs
index 5c0aca1578..e85393e6f7 100644
--- a/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs
+++ b/Content.Server/Nutrition/EntitySystems/CreamPieSystem.cs
@@ -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(item.Value, out var timerTrigger))
+ if (TryComp(item.Value, out var timerTrigger))
{
- _trigger.HandleTimerTrigger(
- item.Value,
- null,
- timerTrigger.Delay,
- timerTrigger.BeepInterval,
- timerTrigger.InitialBeepDelay,
- timerTrigger.BeepSound);
+ _trigger.ActivateTimerTrigger((item.Value, timerTrigger));
}
}
}
diff --git a/Content.Server/Payload/EntitySystems/PayloadSystem.cs b/Content.Server/Payload/EntitySystems/PayloadSystem.cs
index 18444bc590..11e97c5b93 100644
--- a/Content.Server/Payload/EntitySystems/PayloadSystem.cs
+++ b/Content.Server/Payload/EntitySystems/PayloadSystem.cs
@@ -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 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
index a11b4f4d4c..0000000000
--- a/Content.Server/Polymorph/Components/PolymorphOnTriggerComponent.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Content.Shared.Polymorph;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Polymorph.Components;
-
-///
-/// Intended for use with the trigger system.
-/// Polymorphs the user of the trigger.
-///
-[RegisterComponent]
-public sealed partial class PolymorphOnTriggerComponent : Component
-{
- ///
- /// Polymorph settings.
- ///
- [DataField(required: true)]
- public ProtoId Polymorph;
-}
diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs
deleted file mode 100644
index 452b060315..0000000000
--- a/Content.Server/Polymorph/Systems/PolymorphSystem.Trigger.cs
+++ /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
-{
- ///
- /// 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.
- ///
- private Queue<(EntityUid Ent, ProtoId Polymorph)> _queuedPolymorphUpdates = new();
-
- private void InitializeTrigger()
- {
- SubscribeLocalEvent(OnTrigger);
- }
-
- private void OnTrigger(Entity 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);
- }
- }
-}
diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs
index 696b19199e..d411eb7722 100644
--- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs
+++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs
@@ -58,7 +58,6 @@ public sealed partial class PolymorphSystem : EntitySystem
SubscribeLocalEvent(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 ent, ref ComponentStartup args)
diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
index 3829fc34d2..f052c460f5 100644
--- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
+++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
@@ -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;
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs
index e950d3f288..debed525f6 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.Transponder.cs
@@ -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(ent);
diff --git a/Content.Server/Silicons/Borgs/BorgSystem.cs b/Content.Server/Silicons/Borgs/BorgSystem.cs
index 0cd407000f..f33b71c54e 100644
--- a/Content.Server/Silicons/Borgs/BorgSystem.cs
+++ b/Content.Server/Silicons/Borgs/BorgSystem.cs
@@ -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
index 5338351948..0000000000
--- a/Content.Server/Sound/Components/EmitSoundOnTriggerComponent.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Sound.Components;
-
-namespace Content.Server.Sound.Components
-{
- ///
- /// Whenever a is run play a sound in PVS range.
- ///
- [RegisterComponent]
- public sealed partial class EmitSoundOnTriggerComponent : BaseEmitSoundComponent
- {
- }
-}
diff --git a/Content.Server/Sound/EmitSoundSystem.cs b/Content.Server/Sound/EmitSoundSystem.cs
index 9d7e8496c3..1720d67d02 100644
--- a/Content.Server/Sound/EmitSoundSystem.cs
+++ b/Content.Server/Sound/EmitSoundSystem.cs
@@ -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(HandleEmitSoundOnTrigger);
SubscribeLocalEvent(HandleSpamEmitSoundMapInit);
}
- private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args)
- {
- TryEmitSound(uid, component, args.User, false);
- args.Handled = true;
- }
-
private void HandleSpamEmitSoundMapInit(Entity 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
index 4553fafa51..0000000000
--- a/Content.Server/Speech/Components/ActiveListenerComponent.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Content.Server.Chat.Systems;
-
-namespace Content.Server.Speech.Components;
-
-///
-/// This component is used to relay speech events to other systems.
-///
-[RegisterComponent]
-public sealed partial class ActiveListenerComponent : Component
-{
- [DataField("range")]
- public float Range = ChatSystem.VoiceRange;
-}
diff --git a/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs b/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs
index a13eda1f73..6b09877512 100644
--- a/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs
+++ b/Content.Server/Speech/EntitySystems/BlockListeningSystem.cs
@@ -1,4 +1,5 @@
using Content.Server.Speech.Components;
+using Content.Shared.Speech;
namespace Content.Server.Speech.EntitySystems;
diff --git a/Content.Server/Speech/EntitySystems/ListeningSystem.cs b/Content.Server/Speech/EntitySystems/ListeningSystem.cs
index ea3569e055..17513d80e7 100644
--- a/Content.Server/Speech/EntitySystems/ListeningSystem.cs
+++ b/Content.Server/Speech/EntitySystems/ListeningSystem.cs
@@ -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;
diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs
index 4deb5238a5..4029488159 100644
--- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs
+++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraMicrophoneSystem.cs
@@ -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;
diff --git a/Content.Server/Telephone/TelephoneSystem.cs b/Content.Server/Telephone/TelephoneSystem.cs
index 4c87707cc6..46f45d1286 100644
--- a/Content.Server/Telephone/TelephoneSystem.cs
+++ b/Content.Server/Telephone/TelephoneSystem.cs
@@ -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;
diff --git a/Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs b/Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs
similarity index 74%
rename from Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs
rename to Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs
index 0c9734b943..1a3e49dd44 100644
--- a/Content.Server/AlertLevel/Systems/AlertLevelChangeOnTriggerSystem.cs
+++ b/Content.Server/Trigger/Systems/AlertLevelChangeOnTriggerSystem.cs
@@ -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 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
index 0000000000..0fae6ff1c5
--- /dev/null
+++ b/Content.Server/Trigger/Systems/GhostKickUserOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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;
+ }
+}
diff --git a/Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs
similarity index 66%
rename from Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs
rename to Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs
index 0e9dd56622..f4d88b774a 100644
--- a/Content.Server/IgnitionSource/IgniteOnTriggerSystem.cs
+++ b/Content.Server/Trigger/Systems/IgniteOnTriggerSystem.cs
@@ -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;
///
/// 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(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 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
index 0000000000..8b4741b9ad
--- /dev/null
+++ b/Content.Server/Trigger/Systems/PolymorphOnTriggerSystem.cs
@@ -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!;
+
+ ///
+ /// 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.
+ ///
+ private Queue<(EntityUid Uid, ProtoId Polymorph)> _queuedPolymorphUpdates = new();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..963ac36b7f
--- /dev/null
+++ b/Content.Server/Trigger/Systems/RattleOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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.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
index 0000000000..a38ca2a759
--- /dev/null
+++ b/Content.Server/Trigger/Systems/ReleaseGasOnTriggerSystem.cs
@@ -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();
+
+ 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(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(uid);
+ }
+ }
+ }
+}
diff --git a/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs b/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs
new file mode 100644
index 0000000000..97799b9cc6
--- /dev/null
+++ b/Content.Server/Trigger/Systems/SmokeOnTriggerSystem.cs
@@ -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;
+
+///
+/// Handles creating smoke when is triggered.
+///
+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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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(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;
+ }
+}
diff --git a/Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs b/Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs
similarity index 53%
rename from Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs
rename to Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs
index 28be6a5373..6da6f707c1 100644
--- a/Content.Server/Chat/Systems/SpeakOnTriggerSystem.cs
+++ b/Content.Server/Trigger/Systems/SpeakOnTriggerSystem.cs
@@ -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(OnTrigger);
}
private void OnTrigger(Entity ent, ref TriggerEvent args)
{
- TrySpeak(ent);
- args.Handled = true;
- }
-
- private void TrySpeak(Entity ent)
- {
- // If it doesn't have the use delay component, still send the message.
- if (TryComp(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;
}
}
diff --git a/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs b/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs
index 81bbd48a0d..ade861af98 100644
--- a/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs
+++ b/Content.Server/VoiceTrigger/StorageVoiceControlSystem.cs
@@ -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;
diff --git a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs
index b9e23f036b..faef4d5078 100644
--- a/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs
+++ b/Content.Server/Xenoarchaeology/Artifact/XAE/Components/XAETriggerExplosivesComponent.cs
@@ -1,9 +1,9 @@
-using Content.Shared.Explosion.Components.OnTrigger;
+using Content.Shared.Explosion.Components;
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
///
-/// Activates 'trigger' for .
+/// Activates to explode.
///
[RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))]
public sealed partial class XAETriggerExplosivesComponent : Component;
diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs
index 967980302d..34e955a50e 100644
--- a/Content.Shared/Chat/SharedChatSystem.cs
+++ b/Content.Shared/Chat/SharedChatSystem.cs
@@ -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 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
index 87adc0cc90..0000000000
--- a/Content.Shared/Damage/Components/DamageUserOnTriggerComponent.cs
+++ /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!;
-}
diff --git a/Content.Shared/Emp/SharedEmpSystem.cs b/Content.Shared/Emp/SharedEmpSystem.cs
index 6e4478bb6d..72dc874935 100644
--- a/Content.Shared/Emp/SharedEmpSystem.cs
+++ b/Content.Shared/Emp/SharedEmpSystem.cs
@@ -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";
+
+ ///
+ /// Triggers an EMP pulse at the given location, by first raising an , then a raising on all entities in range.
+ ///
+ /// The location to trigger the EMP pulse at.
+ /// The range of the EMP pulse.
+ /// The amount of energy consumed by the EMP pulse.
+ /// The duration of the EMP effects.
+ 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
index 6d43abc9d9..0000000000
--- a/Content.Shared/Explosion/Components/ActiveTimerTriggerComponent.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Robust.Shared.Audio;
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components;
-
-///
-/// Component for tracking active trigger timers. A timers can activated by some other component, e.g. .
-///
-[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
index e14cd12464..0000000000
--- a/Content.Shared/Explosion/Components/OnTrigger/ExplodeOnTriggerComponent.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components.OnTrigger;
-
-///
-/// Explode using the entity's if Triggered.
-///
-[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
index 983b8a31ee..0000000000
--- a/Content.Shared/Explosion/Components/OnUseTimerTriggerComponent.cs
+++ /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;
-
- ///
- /// If not null, a user can use verbs to configure the delay to one of these options.
- ///
- [DataField] public List? DelayOptions = null;
-
- ///
- /// If not null, this timer will periodically play this sound while active.
- ///
- [DataField] public SoundSpecifier? BeepSound;
-
- ///
- /// Time before beeping starts. Defaults to a single beep interval. If set to zero, will emit a beep immediately after use.
- ///
- [DataField] public float? InitialBeepDelay;
-
- [DataField] public float BeepInterval = 1;
-
- ///
- /// Whether the timer should instead be activated through a verb in the right-click menu
- ///
- [DataField] public bool UseVerbInstead = false;
-
- ///
- /// Should timer be started when it was stuck to another entity.
- /// Used for C4 charges and similar behaviour.
- ///
- [DataField] public bool StartOnStick;
-
- ///
- /// Allows changing the start-on-stick quality.
- ///
- [DataField("canToggleStartOnStick")] public bool AllowToggleStartOnStick;
-
- ///
- /// Whether you can examine the item to see its timer or not.
- ///
- [DataField] public bool Examinable = true;
-
- ///
- /// Whether or not to show the user a popup when starting the timer.
- ///
- [DataField] public bool DoPopup = true;
-
- #region GuidebookData
-
- [GuidebookData]
- public float? ShortestDelayOption => DelayOptions?.Min();
-
- [GuidebookData]
- public float? LongestDelayOption => DelayOptions?.Max();
-
- #endregion GuidebookData
- }
-}
diff --git a/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs b/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs
index 059ad189d1..1cab5d1a77 100644
--- a/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs
+++ b/Content.Shared/Explosion/Components/ScatteringGrenadeComponent.cs
@@ -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
///
public bool IsTriggered = false;
+
+ ///
+ /// The trigger key that will activate the grenade.
+ ///
+ [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
index 02d1156104..0000000000
--- a/Content.Shared/Explosion/Components/SharedTriggerOnProximityComponent.cs
+++ /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
index 5027b04517..0000000000
--- a/Content.Shared/Explosion/EntitySystems/SharedReleaseGasOnTriggerSystem.cs
+++ /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
index 386024fbeb..0000000000
--- a/Content.Shared/Explosion/EntitySystems/SharedRepulseAttractOnTriggerSystem.cs
+++ /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
index b206dfa696..0000000000
--- a/Content.Shared/Explosion/EntitySystems/SharedSmokeOnTriggerSystem.cs
+++ /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
index cc5b3f6b74..0000000000
--- a/Content.Shared/Explosion/EntitySystems/SharedTriggerSystem.cs
+++ /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
index e735b3784a..0000000000
--- a/Content.Shared/Flash/Components/FlashOnTriggerComponent.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using Robust.Shared.GameStates;
-namespace Content.Shared.Flash.Components;
-
-///
-/// Upon being triggered will flash in an area around it.
-///
-[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;
-}
diff --git a/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs b/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs
index 9bcd218335..28dbb31324 100644
--- a/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs
+++ b/Content.Shared/HotPotato/ActiveHotPotatoComponent.cs
@@ -12,7 +12,7 @@ public sealed partial class ActiveHotPotatoComponent : Component
///
/// Hot potato effect spawn cooldown in seconds
///
- [DataField("effectCooldown"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float EffectCooldown = 0.3f;
///
diff --git a/Content.Shared/HotPotato/HotPotatoComponent.cs b/Content.Shared/HotPotato/HotPotatoComponent.cs
index f5b2e16189..b077e91e8f 100644
--- a/Content.Shared/HotPotato/HotPotatoComponent.cs
+++ b/Content.Shared/HotPotato/HotPotatoComponent.cs
@@ -14,7 +14,6 @@ public sealed partial class HotPotatoComponent : Component
///
/// If set to true entity can be removed by hitting entities if they have hands
///
- [DataField("canTransfer"), ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public bool CanTransfer = true;
}
diff --git a/Content.Shared/HotPotato/SharedHotPotatoSystem.cs b/Content.Shared/HotPotato/SharedHotPotatoSystem.cs
index cd7a5d6da5..6f2f498782 100644
--- a/Content.Shared/HotPotato/SharedHotPotatoSystem.cs
+++ b/Content.Shared/HotPotato/SharedHotPotatoSystem.cs
@@ -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(OnRemoveAttempt);
+ SubscribeLocalEvent(OnActiveTimer);
+ SubscribeLocalEvent(OnMeleeHit);
}
- private void OnRemoveAttempt(EntityUid uid, HotPotatoComponent comp, ContainerGettingRemovedAttemptEvent args)
+ private void OnRemoveAttempt(Entity ent, ref ContainerGettingRemovedAttemptEvent args)
{
- if (!comp.CanTransfer)
+ if (!_timing.ApplyingState && !ent.Comp.CanTransfer)
args.Cancel();
}
+
+ private void OnActiveTimer(Entity ent, ref ActiveTimerTriggerEvent args)
+ {
+ EnsureComp(ent);
+ ent.Comp.CanTransfer = false;
+ _ambientSound.SetAmbience(ent.Owner, true);
+ _damageOnHolding.SetEnabled(ent.Owner, true);
+ Dirty(ent);
+ }
+
+ private void OnMeleeHit(Entity ent, ref MeleeHitEvent args)
+ {
+ if (!HasComp(ent))
+ return;
+
+ ent.Comp.CanTransfer = true;
+ foreach (var hitEntity in args.HitEntities)
+ {
+ if (!TryComp(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
index 3ec63e8e15..0000000000
--- a/Content.Shared/Implants/Components/RattleComponent.cs
+++ /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 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
index 0f9856f2a4..0000000000
--- a/Content.Shared/Implants/Components/TriggerImplantActionComponent.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Implants.Components;
-
-///
-/// Triggers implants when the action is pressed
-///
-[RegisterComponent, NetworkedComponent]
-public sealed partial class TriggerImplantActionComponent : Component
-{
-
-}
diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs
index 95c3f8664f..177e24ff02 100644
--- a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs
+++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs
@@ -179,7 +179,7 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer))
return;
- var relayEv = new ImplantRelayEvent(args);
+ var relayEv = new ImplantRelayEvent(args, uid);
foreach (var implant in implantContainer.ContainedEntities)
{
if (args is HandledEntityEventArgs { Handled : true })
@@ -194,9 +194,12 @@ public sealed class ImplantRelayEvent 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;
}
}
diff --git a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
index 424bd12bb3..6f6e55b5ef 100644
--- a/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
+++ b/Content.Shared/Item/ItemToggle/Components/ItemToggleComponent.cs
@@ -50,25 +50,37 @@ public sealed partial class ItemToggleComponent : Component
/// ///
/// If server-side systems affect the item's toggle, like charge/fuel systems, then the item is not predictable.
///
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public bool Predictable = true;
///
/// The noise this item makes when it is toggled on.
///
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public SoundSpecifier? SoundActivate;
///
/// The noise this item makes when it is toggled off.
///
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public SoundSpecifier? SoundDeactivate;
+ ///
+ /// The popup to show to someone activating this item.
+ ///
+ [DataField, AutoNetworkedField]
+ public LocId? PopupActivate;
+
+ ///
+ /// The popup to show to someone deactivating this item.
+ ///
+ [DataField, AutoNetworkedField]
+ public LocId? PopupDeactivate;
+
///
/// The noise this item makes when it is toggled on.
///
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public SoundSpecifier? SoundFailToActivate;
}
diff --git a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
index 8008ecf9e5..ff31faaaa1 100644
--- a/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
+++ b/Content.Shared/Item/ItemToggle/ItemToggleSystem.cs
@@ -117,30 +117,30 @@ public sealed class ItemToggleSystem : EntitySystem
/// Sets its state to the opposite of what it is.
///
/// Same as
- public bool Toggle(Entity ent, EntityUid? user = null, bool predicted = true)
+ public bool Toggle(Entity 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);
}
///
/// Tries to set the activated bool from a value.
///
/// false if the attempt fails for any reason
- public bool TrySetActive(Entity ent, bool active, EntityUid? user = null, bool predicted = true)
+ public bool TrySetActive(Entity 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);
}
///
/// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
///
- public bool TryActivate(Entity ent, EntityUid? user = null, bool predicted = true)
+ public bool TryActivate(Entity 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;
}
///
/// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
///
- public bool TryDeactivate(Entity ent, EntityUid? user = null, bool predicted = true)
+ public bool TryDeactivate(Entity 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 ent, bool predicted, EntityUid? user = null)
+ private void Activate(Entity 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
///
/// Used to make the actual changes to the item's components on deactivation.
///
- private void Deactivate(Entity ent, bool predicted, EntityUid? user = null)
+ private void Deactivate(Entity 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));
diff --git a/Content.Shared/Mousetrap/MousetrapComponent.cs b/Content.Shared/Mousetrap/MousetrapComponent.cs
index 6d7483802b..4f48f00b3a 100644
--- a/Content.Shared/Mousetrap/MousetrapComponent.cs
+++ b/Content.Shared/Mousetrap/MousetrapComponent.cs
@@ -2,20 +2,20 @@ using Robust.Shared.GameStates;
namespace Content.Shared.Mousetrap;
-[RegisterComponent, NetworkedComponent]
+///
+/// Component inteded to be used for mouse traps.
+/// Will stop step triggers from happening unless armed via
+/// and will scale damage taken from
+/// depending on mass.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MousetrapComponent : Component
{
- [ViewVariables]
- [DataField("isActive")]
- public bool IsActive = false;
-
///
- /// 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.
///
- [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
index 0000000000..96f74114b2
--- /dev/null
+++ b/Content.Shared/Mousetrap/MousetrapSystem.cs
@@ -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(BeforeDamageOnTrigger);
+ SubscribeLocalEvent(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 ent, ref StepTriggerAttemptEvent args)
+ {
+ if (!TryComp(ent, out var toggle))
+ return;
+
+ args.Continue |= toggle.Activated;
+ }
+
+ // scale the damage according to mass
+ private void BeforeDamageOnTrigger(Entity 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
index 9685157aad..0000000000
--- a/Content.Shared/Mousetrap/MousetrapVisuals.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Mousetrap;
-
-[Serializable, NetSerializable]
-public enum MousetrapVisuals : byte
-{
- Visual,
- Armed,
- Unarmed
-}
diff --git a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
index 3ba4494cca..75053011ff 100644
--- a/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
+++ b/Content.Shared/Ninja/Components/SpiderChargeComponent.cs
@@ -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
+ ///
+ /// Range for planting within the target area.
+ ///
[DataField]
public float Range = 10f;
- /// The ninja that planted this charge
+ ///
+ /// The ninja that planted this charge.
+ ///
[DataField]
public EntityUid? Planter;
+
+ ///
+ /// The trigger that will mark the objective as successful.
+ ///
+ [DataField]
+ public string TriggerKey = "timer";
}
diff --git a/Content.Shared/Payload/Components/PayloadTriggerComponent.cs b/Content.Shared/Payload/Components/PayloadTriggerComponent.cs
index b064e91198..d07da4b24e 100644
--- a/Content.Shared/Payload/Components/PayloadTriggerComponent.cs
+++ b/Content.Shared/Payload/Components/PayloadTriggerComponent.cs
@@ -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;
///
/// 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 , or .
+/// you to add things like , or .
/// 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
///
/// List of components to add or remove from an entity when this trigger is (un)installed.
///
- [DataField("components", serverOnly:true, readOnly: true)]
+ [DataField(serverOnly: true, readOnly: true)]
public ComponentRegistry? Components = null;
///
@@ -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.
///
- [DataField("grantedComponents", serverOnly: true)]
+ [DataField(serverOnly: true)]
public HashSet GrantedComponents = new();
}
diff --git a/Content.Shared/Rootable/SharedRootableSystem.cs b/Content.Shared/Rootable/SharedRootableSystem.cs
index 9a6697cf97..c3deca0769 100644
--- a/Content.Shared/Rootable/SharedRootableSystem.cs
+++ b/Content.Shared/Rootable/SharedRootableSystem.cs
@@ -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(args.SlipCausingEntity))
+ if (args.SlipCausingEntity != null && HasComp(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
index 0000000000..fde108a817
--- /dev/null
+++ b/Content.Shared/Speech/Components/ActiveListenerComponent.cs
@@ -0,0 +1,17 @@
+using Content.Shared.Chat;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+///
+/// This component is used to relay speech events to other systems.
+///
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveListenerComponent : Component
+{
+ ///
+ /// The range in which to listen to speech.
+ ///
+ [DataField]
+ public float Range = SharedChatSystem.VoiceRange;
+}
diff --git a/Content.Server/Speech/ListenEvent.cs b/Content.Shared/Speech/ListenEvent.cs
similarity index 93%
rename from Content.Server/Speech/ListenEvent.cs
rename to Content.Shared/Speech/ListenEvent.cs
index b67aa92f65..8854bd99f4 100644
--- a/Content.Server/Speech/ListenEvent.cs
+++ b/Content.Shared/Speech/ListenEvent.cs
@@ -1,4 +1,4 @@
-namespace Content.Server.Speech;
+namespace Content.Shared.Speech;
public sealed class ListenEvent : EntityEventArgs
{
diff --git a/Content.Shared/Sticky/Components/StickyComponent.cs b/Content.Shared/Sticky/Components/StickyComponent.cs
index 4513091754..a4007b0780 100644
--- a/Content.Shared/Sticky/Components/StickyComponent.cs
+++ b/Content.Shared/Sticky/Components/StickyComponent.cs
@@ -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;
diff --git a/Content.Shared/Timing/UseDelaySystem.cs b/Content.Shared/Timing/UseDelaySystem.cs
index d02752e16b..0950e8981d 100644
--- a/Content.Shared/Timing/UseDelaySystem.cs
+++ b/Content.Shared/Timing/UseDelaySystem.cs
@@ -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
index 0000000000..d7da5cecde
--- /dev/null
+++ b/Content.Shared/Trigger/Components/ActiveTimerTriggerComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components;
+
+///
+/// Component used for tracking active timers triggers.
+/// Used internally for performance reasons.
+///
+[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
index 0000000000..bddbbd402e
--- /dev/null
+++ b/Content.Shared/Trigger/Components/ActiveTwoStageTriggerComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components;
+
+///
+/// Component used for tracking active two-stage triggers.
+/// Used internally for performance reasons.
+///
+[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
index 0000000000..7e77c99d93
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Conditions/BaseTriggerConditionComponent.cs
@@ -0,0 +1,15 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+///
+/// Base class for components that add a condition to triggers.
+///
+public abstract partial class BaseTriggerConditionComponent : Component
+{
+ ///
+ /// The keys that are checked for the condition.
+ ///
+ [DataField, AutoNetworkedField]
+ public HashSet 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
index 0000000000..478f06602d
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Conditions/ToggleTriggerConditionComponent.cs
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+///
+/// Adds an alt verb that can be used to toggle a trigger.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ToggleTriggerConditionComponent : BaseTriggerConditionComponent
+{
+ ///
+ /// Is the component currently enabled?
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
+
+ ///
+ /// The text of the toggle verb.
+ ///
+ [DataField, AutoNetworkedField]
+ public LocId ToggleVerb = "toggle-trigger-condition-default-verb";
+
+ ///
+ /// The popup to show when toggled on.
+ ///
+ [DataField, AutoNetworkedField]
+ public LocId ToggleOn = "toggle-trigger-condition-default-on";
+
+ ///
+ /// The popup to show when toggled off.
+ ///
+ [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
index 0000000000..70a331227e
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Conditions/UseDelayTriggerConditionComponent.cs
@@ -0,0 +1,20 @@
+using Content.Shared.Timing;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+///
+/// Checks if the triggered entity has an active UseDelay.
+///
+///
+/// TODO: Support specific UseDelay IDs for each trigger key.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class UseDelayTriggerConditionComponent : BaseTriggerConditionComponent
+{
+ ///
+ /// Checks if the triggered entity has an active UseDelay.
+ ///
+ [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
index 0000000000..a2779f79c6
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Conditions/WhitelistTriggerConditionComponent.cs
@@ -0,0 +1,24 @@
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+///
+/// 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.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class WhitelistTriggerConditionComponent : BaseTriggerConditionComponent
+{
+ ///
+ /// Whitelist for what entites can cause this trigger.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityWhitelist? UserWhitelist;
+
+ ///
+ /// Blacklist for what entites can cause this trigger.
+ ///
+ [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
index 0000000000..06f5258fbe
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/AddComponentsOnTriggerComponent.cs
@@ -0,0 +1,37 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Adds the specified components when triggered.
+/// If TargetUser is true they will be added to the user.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AddComponentsOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The list of components that will be added.
+ ///
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
+
+ ///
+ /// If this component has been triggered at least once already.
+ /// If this is true the components have been added.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Triggered = false;
+
+ ///
+ /// If this effect can only be triggered once.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool TriggerOnce = false;
+
+ ///
+ /// Should components that already exist on the entity be overwritten?
+ ///
+ [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
index 0000000000..0d161344d8
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/AlertLevelChangeOnTriggerComponent.cs
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Changes the alert level of the station when triggered.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AlertLevelChangeOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The alert level to change to when triggered.
+ ///
+ [DataField, AutoNetworkedField]
+ public string Level = "blue";
+
+ ///
+ /// Whether to play the sound when the alert level changes.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool PlaySound = true;
+
+ ///
+ /// Whether to say the announcement when the alert level changes.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Announce = true;
+
+ ///
+ /// Force the alert change. This applies if the alert level is not selectable or not.
+ ///
+ [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
index 0000000000..ed61876f15
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/AnchorOnTriggerComponent.cs
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will (un)anchor the entity when triggered.
+/// If TargetUser is true they will be (un)anchored instead.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AnchorOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// Anchor the entity on trigger if it is currently unanchored?
+ ///
+ [DataField, AutoNetworkedField]
+ public bool CanAnchor = true;
+
+ ///
+ /// Unanchor the entity on trigger if it is currently anchored?
+ /// If both this and CanAnchor are true then the trigger will toggle between states.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool CanUnanchor = false;
+
+ ///
+ /// Removes this component when triggered so it can only be activated once.
+ ///
+ ///
+ /// TODO: Make this a generic thing for all triggers.
+ /// Or just add a RemoveComponentsOnTriggerComponent.
+ ///
+ [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
index 0000000000..cf93ad6c4a
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/BaseXOnTriggerComponent.cs
@@ -0,0 +1,22 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Base class for components that do something when triggered.
+///
+public abstract partial class BaseXOnTriggerComponent : Component
+{
+ ///
+ /// The keys that will activate the effect.
+ ///
+ [DataField, AutoNetworkedField]
+ public HashSet KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+ ///
+ /// 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.
+ ///
+ [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
index 0000000000..8d35b019b4
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/DamageOnTriggerComponent.cs
@@ -0,0 +1,25 @@
+using Content.Shared.Damage;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will damage an entity when triggered.
+/// If TargetUser is true it the user will take damage instead.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class DamageOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// Should the damage ignore resistances?
+ ///
+ [DataField, AutoNetworkedField]
+ public bool IgnoreResistances;
+
+ ///
+ /// The base damage amount that is dealt.
+ /// May be further modified by subscriptions.
+ ///
+ [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
index 0000000000..9d0a5b7517
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/DeleteOnTriggerComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will delete the entity when triggered.
+/// If TargetUser is true it will delete them instead.
+///
+[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
index 0000000000..f3870225e2
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/EmitSoundOnTriggerComponent.cs
@@ -0,0 +1,31 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will play a sound in PVS range when triggered.
+/// If TargetUser is true it will be played at their position.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EmitSoundOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The to play.
+ ///
+ [DataField(required: true), AutoNetworkedField]
+ public SoundSpecifier? Sound;
+
+ ///
+ /// Play the sound at the position instead of parented to the source entity.
+ /// Useful if the entity is deleted after.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Positional;
+
+ ///
+ /// Should this sound be predicted for the User?
+ ///
+ [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
index 0000000000..327e9f57db
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/EmpOnTriggerComponent.cs
@@ -0,0 +1,29 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will cause an EMP at the entity's location when triggered.
+/// If TargetUser is true then it will be spawned at their position.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EmpOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// EMP range.
+ ///
+ [DataField, AutoNetworkedField]
+ public float Range = 1.0f;
+
+ ///
+ /// How much energy (in Joules) will be consumed per battery in range.
+ ///
+ [DataField, AutoNetworkedField]
+ public float EnergyConsumption;
+
+ ///
+ /// How long it disables targets.
+ ///
+ [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
index 0000000000..2a1af40a2c
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/ExplodeOnTriggerComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will explode using the entity's when triggered.
+/// TargetUser will only work of the user has ExplosiveComponent as well.
+/// The User will be logged in the admin logs.
+///
+///
+/// TODO: Allow this to work without an ExplosiveComponent on the user via QueueExplosion.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ExplodeOnTriggerComponent : BaseXOnTriggerComponent;
diff --git a/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs
new file mode 100644
index 0000000000..8b75417031
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/FlashOnTriggerComponent.cs
@@ -0,0 +1,29 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will cause a flash in an area around the entity when triggered.
+/// If TargetUser is true then their location will be used.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class FlashOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The range in which to flash entities in.
+ ///
+ [DataField, AutoNetworkedField]
+ public float Range = 1.0f;
+
+ ///
+ /// The duration of the status effect.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(8);
+
+ ///
+ /// The probability to apply the status effect.
+ ///
+ [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
index 0000000000..16c1365c05
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/GhostKickOnTriggerComponent.cs
@@ -0,0 +1,19 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// 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.
+///
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GhostKickOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The reason that will be displayed in the server log when a player is kicked.
+ ///
+ [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
index 0000000000..b3475ac447
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/GibOnTriggerComponent.cs
@@ -0,0 +1,17 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will gib the entity when triggered.
+/// If TargetUser is true the user will be gibbed instead.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GibOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// Should gibbing also delete the owners items?
+ ///
+ [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
index 0000000000..36273ef1b2
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/IgniteOnTriggerComponent.cs
@@ -0,0 +1,27 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will ignite for a certain length of time when triggered.
+/// Requires along with triggering components.
+/// The if TargetUser is true they will be ignited instead (they need IgnitionSourceComponent as well).
+///
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class IgniteOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// Once ignited, the time it will unignite at.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan IgnitedUntil = TimeSpan.Zero;
+
+ ///
+ /// How long the ignition source is active for after triggering.
+ ///
+ [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
index 0000000000..4c5360f4d2
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/ItemToggleOnTriggerComponent.cs
@@ -0,0 +1,37 @@
+using Content.Shared.Item.ItemToggle.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will toggle an item when triggered. Requires .
+/// If TargetUser is true and they have that component they will be toggled instead.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ItemToggleOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// Can the item be toggled on using the trigger?
+ ///
+ [DataField, AutoNetworkedField]
+ public bool CanActivate = true;
+
+ ///
+ /// Can the item be toggled on using the trigger?
+ /// If both this and CanActivate are true then the trigger will toggle between states.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool CanDeactivate = true;
+
+ ///
+ /// Can the audio and popups be predicted?
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Predicted = true;
+
+ ///
+ /// Show a popup to the user when toggling the item?
+ ///
+ [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
index 0000000000..3d2021e425
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/PolymorphOnTriggerComponent.cs
@@ -0,0 +1,19 @@
+using Content.Shared.Polymorph;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Polymorphs the enity when triggered.
+/// If TargetUser is true it will polymorph the user instead.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class PolymorphOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// Polymorph settings.
+ ///
+ [DataField(required: true)]
+ public ProtoId Polymorph;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs
new file mode 100644
index 0000000000..599a64339a
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/RattleOnTriggerComponent.cs
@@ -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;
+
+///
+/// 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.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class RattleOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The radio channel the message will be sent to.
+ ///
+ [DataField]
+ public ProtoId RadioChannel = "Syndicate";
+
+ ///
+ /// The message to be send depending on the target's current mob state.
+ ///
+ [DataField]
+ public Dictionary Messages = new()
+ {
+ {MobState.Critical, "deathrattle-implant-critical-message"},
+ {MobState.Dead, "deathrattle-implant-dead-message"}
+ };
+}
diff --git a/Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs
similarity index 90%
rename from Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs
index 28a5c5cf81..4edd5f83f4 100644
--- a/Content.Shared/Explosion/Components/OnTrigger/ReleaseGasOnTriggerComponent.cs
+++ b/Content.Shared/Trigger/Components/Effects/ReleaseGasOnTriggerComponent.cs
@@ -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;
///
/// Contains a GasMixture that will release its contents to the atmosphere when triggered.
///
[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentPause]
-[Access(typeof(SharedReleaseGasOnTriggerSystem))]
-public sealed partial class ReleaseGasOnTriggerComponent : Component
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class ReleaseGasOnTriggerComponent : BaseXOnTriggerComponent
{
///
/// Whether this grenade is active and releasing gas.
diff --git a/Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs
similarity index 57%
rename from Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs
index 43febff03b..68af0bb544 100644
--- a/Content.Shared/Explosion/Components/OnTrigger/SharedRepulseAttractOnTriggerComponent.cs
+++ b/Content.Shared/Trigger/Components/Effects/RepulseAttractOnTriggerComponent.cs
@@ -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;
///
-/// 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.
///
-[RegisterComponent]
-public sealed partial class SharedRepulseAttractOnTriggerComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class RepulseAttractOnTriggerComponent : BaseXOnTriggerComponent
{
///
/// 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.
///
- [DataField]
- public float Speed;
+ [DataField, AutoNetworkedField]
+ public float Speed = 5.0f;
///
/// How close do the entities need to be?
///
- [DataField]
- public float Range;
+ [DataField, AutoNetworkedField]
+ public float Range = 5.0f;
///
/// What kind of entities should this effect apply to?
///
- [DataField]
+ [DataField, AutoNetworkedField]
public EntityWhitelist? Whitelist;
///
/// What collision layers should be excluded?
/// The default excludes ghost mobs, revenants, the AI camera etc.
///
- [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
index 0000000000..e7da7a3801
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/ShockOnTriggerComponent.cs
@@ -0,0 +1,34 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will electrocute the entity when triggered.
+/// If TargetUser is true it will electrocute the user instead.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ShockOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// Electrocute entity containing this entity instead (for example for wearable clothing).
+ /// Has priority over TargetUser.
+ ///
+ ///
+ /// TODO: Make this more generic so it can be used for all triggers.
+ /// Maybe a BeforeTriggerEvent where we modify the target.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool TargetContainer;
+
+ ///
+ /// The force of an electric shock when the trigger is triggered.
+ ///
+ [DataField, AutoNetworkedField]
+ public int Damage = 5;
+
+ ///
+ /// Duration of electric shock when the trigger is triggered.
+ ///
+ [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
index 0000000000..0698f2ca33
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/SignalOnTriggerComponent.cs
@@ -0,0 +1,18 @@
+using Content.Shared.DeviceLinking;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Sends a device link signal when triggered.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SignalOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The port that gets signaled when the switch turns on.
+ ///
+ [DataField]
+ public ProtoId Port = "Trigger";
+}
diff --git a/Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs
similarity index 59%
rename from Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs
index 1138e74af8..07a5bad056 100644
--- a/Content.Shared/Explosion/Components/OnTrigger/SmokeOnTriggerComponent.cs
+++ b/Content.Shared/Trigger/Components/Effects/SmokeOnTriggerComponent.cs
@@ -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;
///
/// Creates a smoke cloud when triggered, with an optional solution to include in it.
-/// No sound is played incase a grenade is stealthy, use if you want a sound.
+/// No sound is played incase a grenade is stealthy, use if you want a sound.
+/// If TargetUser is true the smoke is spawned at their location.
///
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedSmokeOnTriggerSystem))]
-public sealed partial class SmokeOnTriggerComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SmokeOnTriggerComponent : BaseXOnTriggerComponent
{
///
- /// How long the smoke stays for, after it has spread.
+ /// How long the smoke stays for, after it has spread (in seconds).
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float Duration = 10;
+ [DataField, AutoNetworkedField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(10);
///
/// How much the smoke will spread.
///
- [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ [DataField(required: true), AutoNetworkedField]
public int SpreadAmount;
///
/// Smoke entity to spawn.
/// Defaults to smoke but you can use foam if you want.
///
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public EntProtoId SmokePrototype = "Smoke";
///
@@ -37,6 +37,6 @@ public sealed partial class SmokeOnTriggerComponent : Component
///
/// When using repeating trigger this essentially gets multiplied so dont do anything crazy like omnizine or lexorin.
///
- [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
index 0000000000..782626f479
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/SpawnOnTriggerComponent.cs
@@ -0,0 +1,31 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Spawns a protoype when triggered.
+/// If TargetUser is true it will be spawned at their location.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SpawnOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The prototype to spawn.
+ ///
+ [DataField(required: true), AutoNetworkedField]
+ public EntProtoId Proto = string.Empty;
+
+ ///
+ /// Use MapCoordinates for spawning?
+ /// Set to true if you don't want the new entity parented to the spawner.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool UseMapCoords;
+
+ ///
+ /// Whether or not to use predicted spawning.
+ ///
+ [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
index 0000000000..8fe6927046
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/SpeakOnTriggerComponent.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Dataset;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Makes the entity speak a message when triggered.
+/// If TargetUser is true then they will be forced to speak instead.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SpeakOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The text to speak. This has priority over Pack.
+ ///
+ [DataField]
+ public LocId? Text;
+
+ ///
+ /// The identifier for the dataset prototype containing messages to be spoken by this entity.
+ /// The spoken text will be picked randomly from it.
+ ///
+ [DataField]
+ public ProtoId? Pack;
+}
diff --git a/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs b/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs
new file mode 100644
index 0000000000..4d43c2860b
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Effects/UseDelayOnTriggerComponent.cs
@@ -0,0 +1,26 @@
+using Content.Shared.Timing;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+///
+/// Will activate an UseDelay on the target when triggered.
+///
+///
+/// TODO: Support specific UseDelay IDs for each trigger key.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class UseDelayOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///
+ /// The UseDelay Id to delay.
+ ///
+ [DataField, AutoNetworkedField]
+ public string UseDelayId = UseDelaySystem.DefaultId;
+
+ ///
+ /// If true ongoing delays won't be reset.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool CheckDelayed;
+}
diff --git a/Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs b/Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs
similarity index 50%
rename from Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs
rename to Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs
index 3863b9c313..89b5535854 100644
--- a/Content.Server/Explosion/Components/RandomTimerTriggerComponent.cs
+++ b/Content.Shared/Trigger/Components/RandomTimerTriggerComponent.cs
@@ -1,22 +1,22 @@
-using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.GameStates;
-namespace Content.Server.Explosion.Components;
+namespace Content.Shared.Trigger.Components;
///
-/// This is used for randomizing a on MapInit
+/// This is used for randomizing a on MapInit.
///
-[RegisterComponent, Access(typeof(TriggerSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class RandomTimerTriggerComponent : Component
{
///
/// The minimum random trigger time.
///
- [DataField]
+ [DataField, AutoNetworkedField]
public float Min;
///
/// The maximum random trigger time.
///
- [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
index 0000000000..9cc58d3cda
--- /dev/null
+++ b/Content.Shared/Trigger/Components/TimerTriggerComponent.cs
@@ -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;
+
+///
+/// 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 .
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TimerTriggerComponent : Component
+{
+ ///
+ /// The keys that will activate the timer.
+ ///
+ [DataField, AutoNetworkedField]
+ public List KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+ ///
+ /// The key that will trigger once the timer is finished.
+ ///
+ [DataField, AutoNetworkedField]
+ public string? KeyOut = "timer";
+
+ ///
+ /// The time after which this timer will trigger after it is activated.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan Delay = TimeSpan.FromSeconds(1);
+
+ ///
+ /// If not empty, a user can use verbs to configure the delay to one of these options.
+ ///
+ [DataField, AutoNetworkedField]
+ public List DelayOptions = new();
+
+ ///
+ /// The time at which this trigger will activate.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextTrigger = TimeSpan.Zero;
+
+ ///
+ /// Time of the next beeping sound.
+ ///
+ ///
+ /// Not networked because it's only used server side.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoPausedField]
+ public TimeSpan NextBeep = TimeSpan.Zero;
+
+ ///
+ /// Initial beep delay.
+ /// Defaults to a single BeepInterval if null.
+ ///
+ ///
+ /// Not networked because it's only used server side.
+ ///
+ [DataField]
+ public TimeSpan? InitialBeepDelay;
+
+ ///
+ /// The time between beeps.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan BeepInterval = TimeSpan.FromSeconds(1);
+
+ ///
+ /// The entity that activated this trigger.
+ ///
+ [DataField, AutoNetworkedField]
+ public EntityUid? User;
+
+ ///
+ /// The beeping sound, if any.
+ ///
+ [DataField, AutoNetworkedField]
+ public SoundSpecifier? BeepSound;
+
+ ///
+ /// Whether you can examine the item to see its timer or not.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Examinable = true;
+
+ ///
+ /// The popup to show the user when starting the timer, if any.
+ ///
+ [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
index 0000000000..88b68913f0
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/ActiveTriggerOnTimedCollideComponent.cs
@@ -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
index 0000000000..1f4807f253
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/BaseTriggerOnXComponent.cs
@@ -0,0 +1,16 @@
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Base class for components that cause a trigger to be activated.
+///
+public abstract partial class BaseTriggerOnXComponent : Component
+{
+ ///
+ /// The key that the trigger will activate.
+ /// null will activate all triggers.
+ ///
+ [DataField, AutoNetworkedField]
+ public string? KeyOut = TriggerSystem.DefaultTriggerKey;
+}
diff --git a/Content.Server/Explosion/Components/RepeatingTriggerComponent.cs b/Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs
similarity index 56%
rename from Content.Server/Explosion/Components/RepeatingTriggerComponent.cs
rename to Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs
index cc08de53f9..27527d7773 100644
--- a/Content.Server/Explosion/Components/RepeatingTriggerComponent.cs
+++ b/Content.Shared/Trigger/Components/Triggers/RepeatingTriggerComponent.cs
@@ -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;
///
/// Constantly triggers after being added to an entity.
///
-[RegisterComponent, Access(typeof(TriggerSystem))]
-[AutoGenerateComponentPause]
-public sealed partial class RepeatingTriggerComponent : Component
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class RepeatingTriggerComponent : BaseTriggerOnXComponent
{
///
/// How long to wait between triggers.
/// The first trigger starts this long after the component is added.
///
- [DataField]
+ [DataField, AutoNetworkedField]
public TimeSpan Delay = TimeSpan.FromSeconds(1);
///
/// When the next trigger will be.
///
- [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
index 0000000000..9dd145bb26
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateComponent.cs
@@ -0,0 +1,17 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when activated in hand or by clicking on the entity.
+/// The user is the player activating it.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnActivateComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// Is this interaction a complex interaction?
+ ///
+ [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
index 0000000000..b26f3b6875
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnActivateImplantComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when activating an action granted by an implant.
+/// The user is the player activating it.
+///
+[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
index 0000000000..a1e234bd7a
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnCollideComponent.cs
@@ -0,0 +1,23 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when colliding with another entity.
+/// The user is the entity collided with.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnCollideComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// The fixture with which to collide.
+ ///
+ [DataField(required: true), AutoNetworkedField]
+ public string FixtureID = string.Empty;
+
+ ///
+ /// Doesn't trigger if the other colliding fixture is nonhard.
+ ///
+ [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
index 0000000000..40d468ec30
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnEmptyGunshotComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when attempting to shoot a gun while it's empty.
+/// The user is the player holding the gun.
+///
+[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
index 0000000000..a8dab4e6cd
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnMobstateChangeComponent.cs
@@ -0,0 +1,35 @@
+using Content.Shared.Mobs;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// 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.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnMobstateChangeComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// What states should trigger this?
+ ///
+ [DataField(required: true), AutoNetworkedField]
+ public List MobState = new();
+
+ ///
+ /// If true, prevents suicide attempts for the trigger to prevent cheese.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool PreventSuicide = false;
+
+ ///
+ /// 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.
+ ///
+ ///
+ /// Set this to true for implants that apply an effect on the implanted entity.
+ ///
+ [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
index 0000000000..047d6f0374
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnProximityComponent.cs
@@ -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;
+
+///
+/// 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.
+///
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TriggerOnProximityComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// The ID if the fixture that is observed for collisions.
+ ///
+ public const string FixtureID = "trigger-on-proximity-fixture";
+
+ ///
+ /// Currently colliding entities.
+ ///
+ [ViewVariables]
+ public readonly Dictionary Colliding = new();
+
+ ///
+ /// What is the shape of the proximity fixture?
+ ///
+ [ViewVariables]
+ [DataField]
+ public IPhysShape Shape = new PhysShapeCircle(2f);
+
+ ///
+ /// How long the the proximity trigger animation plays for.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
+
+ ///
+ /// Whether the entity needs to be anchored for the proximity to work.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool RequiresAnchored = true;
+
+ ///
+ /// Whether the proximity trigger is currently enabled.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
+
+ ///
+ /// The minimum delay between repeating triggers.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
+
+ ///
+ /// When can the trigger run again?
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextTrigger = TimeSpan.Zero;
+
+ ///
+ /// When will the visual state be updated again after activation?
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextVisualUpdate = TimeSpan.Zero;
+
+ ///
+ /// What speed should the other object be moving at to trigger the proximity fixture?
+ ///
+ [DataField, AutoNetworkedField]
+ public float TriggerSpeed = 3.5f;
+
+ ///
+ /// If this proximity is triggered should we continually repeat it?
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Repeating = true;
+
+ ///
+ /// What layer is the trigger fixture on?
+ ///
+ [DataField(customTypeSerializer: typeof(FlagSerializer))]
+ 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
index 0000000000..6ed81c5fcf
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnSignalComponent.cs
@@ -0,0 +1,19 @@
+using Content.Shared.DeviceLinking;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Sends a trigger when signal is received.
+/// The user is the sender of the signal.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnSignalComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// The sink port prototype we can connect devices to.
+ ///
+ [DataField, AutoNetworkedField]
+ public ProtoId Port = "Trigger";
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs
new file mode 100644
index 0000000000..b0381ee74e
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnSlipComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers an entity when someone slipped on it.
+/// The user is the entity that was slipped.
+///
+[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
index 0000000000..c718a20148
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnSpawnComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when the entity is initialized.
+/// The user is null.
+///
+[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
index 0000000000..70d33a1179
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnStepTriggerComponent.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers if a StepTrigger is activated by someone stepping on this entity.
+/// The user is the mob who stepped on it.
+///
+///
+/// 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.
+///
+[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
index 0000000000..073a64f66e
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnStuckComponent.cs
@@ -0,0 +1,11 @@
+using Content.Shared.Sticky.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when an entity with is stuck to something.
+/// The user is the player doing so.
+///
+[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
index 0000000000..185ea7dbe4
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnTimedCollideComponent.cs
@@ -0,0 +1,26 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when the entity is overlapped for the specified duration.
+/// The user is the entity that passes the time threshold while colliding.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnTimedCollideComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// The time an entity has to collide until the trigger is activated.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan Threshold = TimeSpan.FromSeconds(1);
+
+ ///
+ /// A collection of entities that are currently colliding with this, and their own unique accumulator.
+ ///
+ ///
+ /// TODO: Add AutoPausedField and (de)serialize values as time offsets when https://github.com/space-wizards/RobustToolbox/issues/3768 is fixed.
+ ///
+ [DataField, AutoNetworkedField]
+ public Dictionary Colliding = new();
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs
new file mode 100644
index 0000000000..71d289741e
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnUseComponent.cs
@@ -0,0 +1,10 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers on use in hand.
+/// The user is the player holding the item.
+///
+[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
index 0000000000..463860f077
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnVerb.cs
@@ -0,0 +1,20 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Starts a trigger when a verb is selected.
+/// The user is the player selecting the verb.
+///
+///
+/// TODO: Support multiple verbs and trigger keys.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnVerbComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// The text to display in the verb.
+ ///
+ [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
index 0000000000..a36992d7da
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnVoiceComponent.cs
@@ -0,0 +1,47 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Sends a trigger when the keyphrase is heard.
+/// The User is the speaker.
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// Whether or not the component is actively listening at the moment.
+ ///
+ [ViewVariables]
+ public bool IsListening => IsRecording || !string.IsNullOrWhiteSpace(KeyPhrase);
+
+ ///
+ /// The keyphrase that has been set to trigger it.
+ ///
+ [DataField, AutoNetworkedField]
+ public string? KeyPhrase;
+
+ ///
+ /// Range in which we listen for the keyphrase.
+ ///
+ [DataField, AutoNetworkedField]
+ public int ListenRange = 4;
+
+ ///
+ /// Whether we are currently recording a new keyphrase.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool IsRecording;
+
+ ///
+ /// Minimum keyphrase length.
+ ///
+ [DataField, AutoNetworkedField]
+ public int MinLength = 3;
+
+ ///
+ /// Maximum keyphrase length.
+ ///
+ [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
index 0000000000..f0161c7175
--- /dev/null
+++ b/Content.Shared/Trigger/Components/TwoStageTriggerComponent.cs
@@ -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;
+
+///
+/// After being triggered applies the specified components and runs triggers again.
+///
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TwoStageTriggerComponent : Component
+{
+ ///
+ /// The keys that will activate the timer and add the given components (first stage).
+ ///
+ [DataField, AutoNetworkedField]
+ public List KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+ ///
+ /// The key that will trigger once the timer is finished (second stage).
+ ///
+ [DataField, AutoNetworkedField]
+ public string? KeyOut = "stageTwo";
+
+ ///
+ /// How long it takes for the second stage to be triggered.
+ ///
+ [DataField, AutoNetworkedField]
+ public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10);
+
+ ///
+ /// This list of components that will be added on the first trigger.
+ ///
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
+
+ ///
+ /// The time at which the second stage will trigger.
+ ///
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan? NextTriggerTime;
+
+ ///
+ /// Has this entity been triggered already?
+ /// Used to prevent the components from being added multiple times.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool Triggered = false;
+
+ ///
+ /// The entity that activated this trigger.
+ ///
+ [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
index 0000000000..908307dad0
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/AddComponentsOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..8f30c852ea
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/DamageOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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;
+ }
+}
+
+///
+/// Raised on an entity before it deals damage using DamageOnTriggerComponent.
+/// Used to modify the damage that will be dealt.
+///
+[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
index 0000000000..e296ccc177
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/EmitSoundOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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 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
index 0000000000..136c4474a2
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/EmpOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..1c773b79a6
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/ExplodeOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..6153e228bf
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/FlashOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..95ef5ec1db
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/GibOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..9bedc87b6b
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/RepulseAttractOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..e1871e0435
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/SharedReleaseGasOnTriggerSystem.cs
@@ -0,0 +1,36 @@
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Trigger.Systems;
+
+///
+/// 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.
+///
+public abstract class SharedReleaseGasOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnTrigger);
+ }
+
+ ///
+ /// Shrimply sets the component to active when triggered, allowing it to release over time.
+ ///
+ private void OnTrigger(Entity 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
index 0000000000..c4d34af7e1
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/ShockOnTriggerSystem.cs
@@ -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(OnTrigger);
+ }
+
+ private void OnTrigger(Entity 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
index 0000000000..3825708550
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/TriggerOnActivateImplantSystem.cs
@@ -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(OnActivateImplant);
+ }
+
+ private void OnActivateImplant(Entity 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
index 0000000000..cc23fa2b84
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/TriggerOnEmptyGunshotSystem.cs
@@ -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(OnEmptyGunShot);
+ }
+
+ private void OnEmptyGunShot(Entity ent, ref OnEmptyGunShotEvent args)
+ {
+ _trigger.Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+ }
+}
diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs b/Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs
similarity index 56%
rename from Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs
rename to Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs
index ccd2a6e3df..68c109aef9 100644
--- a/Content.Server/Explosion/EntitySystems/TriggerSystem.Mobstate.cs
+++ b/Content.Shared/Trigger/Systems/TriggerOnMobstateChangeSystem.cs
@@ -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(OnMobStateChanged);
SubscribeLocalEvent(OnSuicide);
- SubscribeLocalEvent>(OnSuicideRelay);
SubscribeLocalEvent>(OnMobStateRelay);
+ SubscribeLocalEvent>(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(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 args)
+ {
+ if (!component.MobState.Contains(args.Event.NewMobState))
+ return;
+
+ _trigger.Trigger(uid, component.TargetMobstateEntity ? args.ImplantedEntity : args.Event.Origin, component.KeyOut);
}
///
/// 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.
///
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 args)
{
- OnSuicide(uid, component, args.Event);
- }
+ if (args.Event.Handled)
+ return;
- private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent 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
index 0000000000..6940ea52e2
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/TriggerOnSlipSystem.cs
@@ -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(OnSlip);
+ }
+
+ private void OnSlip(Entity 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
index 0000000000..d364adccff
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/TriggerOnStuckSystem.cs
@@ -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(OnStuck);
+ }
+
+ private void OnStuck(Entity 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
index 0000000000..d5830dd75d
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/TriggerOnVerbSystem.cs
@@ -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>(OnGetAltVerbs);
+ }
+
+ private void OnGetAltVerbs(Entity ent, ref GetVerbsEvent 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
index 0000000000..5243b13742
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/TriggerSystem.Collide.cs
@@ -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(OnCollide);
+ SubscribeLocalEvent(OnStepTriggered);
+
+ SubscribeLocalEvent(OnTimedCollide);
+ SubscribeLocalEvent(OnTimedEndCollide);
+ SubscribeLocalEvent(OnTimedShutdown);
+ }
+
+ private void OnCollide(Entity 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