diff --git a/Content.Shared/Projectiles/EmbedEvent.cs b/Content.Shared/Projectiles/EmbedEvent.cs
deleted file mode 100644
index 521a691f45..0000000000
--- a/Content.Shared/Projectiles/EmbedEvent.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace Content.Shared.Projectiles;
-
-///
-/// Raised directed on an entity when it embeds in another entity.
-///
-[ByRefEvent]
-public readonly record struct EmbedEvent(EntityUid? Shooter, EntityUid Embedded)
-{
- public readonly EntityUid? Shooter = Shooter;
-
- ///
- /// Entity that is embedded in.
- ///
- public readonly EntityUid Embedded = Embedded;
-}
diff --git a/Content.Shared/Projectiles/EmbedEvents.cs b/Content.Shared/Projectiles/EmbedEvents.cs
new file mode 100644
index 0000000000..6f357a33a1
--- /dev/null
+++ b/Content.Shared/Projectiles/EmbedEvents.cs
@@ -0,0 +1,35 @@
+namespace Content.Shared.Projectiles;
+
+///
+/// Raised directed on an entity when it embeds in another entity.
+///
+[ByRefEvent]
+public readonly record struct EmbedEvent(EntityUid? Shooter, EntityUid Embedded)
+{
+ ///
+ /// The entity that threw/shot the embed, if any.
+ ///
+ public readonly EntityUid? Shooter = Shooter;
+
+ ///
+ /// Entity that is embedded in.
+ ///
+ public readonly EntityUid Embedded = Embedded;
+}
+
+///
+/// Raised directed on an entity when it stops being embedded in another entity.
+///
+[ByRefEvent]
+public readonly record struct EmbedDetachEvent(EntityUid? Detacher, EntityUid Embedded)
+{
+ ///
+ /// The entity that detached the embed, if any.
+ ///
+ public readonly EntityUid? Detacher = Detacher;
+
+ ///
+ /// Entity that it is embedded in.
+ ///
+ public readonly EntityUid Embedded = Embedded;
+}
diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs
index 9a44ca545d..c5aaf3135d 100644
--- a/Content.Shared/Projectiles/SharedProjectileSystem.cs
+++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs
@@ -135,6 +135,8 @@ public abstract partial class SharedProjectileSystem : EntitySystem
if (component.EmbeddedIntoUid == null)
return; // the entity is not embedded, so do nothing
+ var embeddedInto = component.EmbeddedIntoUid;
+
if (TryComp(component.EmbeddedIntoUid.Value, out var embeddedContainer))
{
embeddedContainer.EmbeddedObjects.Remove(uid);
@@ -168,6 +170,9 @@ public abstract partial class SharedProjectileSystem : EntitySystem
Dirty(uid, projectile);
}
+ var ev = new EmbedDetachEvent(user, embeddedInto.Value);
+ RaiseLocalEvent(uid, ref ev);
+
if (user != null)
{
// Land it just coz uhhh yeah
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnEmbedComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnEmbedComponent.cs
new file mode 100644
index 0000000000..e298d3e378
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnEmbedComponent.cs
@@ -0,0 +1,18 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when this entity first embeds into something.
+/// User is the item that was embedded or the actual embed depending on
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnEmbedComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// If false the trigger user will be the mob that shot the embeddable projectile.
+ /// If true, the trigger user will be the entity the projectile was embedded into.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool UserIsEmbeddedInto;
+}
diff --git a/Content.Shared/Trigger/Components/Triggers/TriggerOnUnembedComponent.cs b/Content.Shared/Trigger/Components/Triggers/TriggerOnUnembedComponent.cs
new file mode 100644
index 0000000000..ba0dd801f5
--- /dev/null
+++ b/Content.Shared/Trigger/Components/Triggers/TriggerOnUnembedComponent.cs
@@ -0,0 +1,20 @@
+using Content.Shared.Trigger.Systems;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+///
+/// Triggers when this entity gets un-embedded from something.
+/// User is the item that was embedded or the actual embed depending on
+/// Handled by
+///
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnUnembedComponent : BaseTriggerOnXComponent
+{
+ ///
+ /// If false the trigger user will be the one that detaches the embedded entity.
+ /// If true, the trigger user will be the entity the projectile was embedded into.
+ ///
+ [DataField, AutoNetworkedField]
+ public bool UserIsEmbeddedInto;
+}
diff --git a/Content.Shared/Trigger/Systems/TriggerOnEmbedSystem.cs b/Content.Shared/Trigger/Systems/TriggerOnEmbedSystem.cs
new file mode 100644
index 0000000000..ade48c94a7
--- /dev/null
+++ b/Content.Shared/Trigger/Systems/TriggerOnEmbedSystem.cs
@@ -0,0 +1,31 @@
+using Content.Shared.Projectiles;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+///
+/// This handles subscriptions.
+///
+public sealed class TriggerOnEmbedSystem : TriggerOnXSystem
+{
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnEmbed);
+ SubscribeLocalEvent(OnStopEmbed);
+ }
+
+ private void OnEmbed(Entity ent, ref EmbedEvent args)
+ {
+ var user = ent.Comp.UserIsEmbeddedInto ? args.Embedded : args.Shooter;
+ Trigger.Trigger(ent, user, ent.Comp.KeyOut);
+ }
+
+ private void OnStopEmbed(Entity ent, ref EmbedDetachEvent args)
+ {
+ var user = ent.Comp.UserIsEmbeddedInto ? args.Embedded : args.Detacher;
+ Trigger.Trigger(ent, user, ent.Comp.KeyOut);
+ }
+}