Repository: space-wizards/space-station-14
Pull request #37069: Bomb Deliveries
Author: login: ScarKy0
Labels: name: P3: Standard
description: Priority: Default priority for repository items.
name: T: New Feature
description: Type: New feature or content, or extending existing content
name: D3: Low
description: Difficulty: Some codebase knowledge required.
name: Changes: Sprites
description: Changes: Might require knowledge of spriting or visual design.
name: S: Needs Review
description: Status: Requires additional reviews before being fully accepted. Not to be replaced by S: Approved.
name: A: Cargo/Salvage
description: Area: Cargo department or Salvage.
name: size/M
description: Denotes a PR that changes 100-999 lines.
Created: 2025-04-30T12:17:01Z | Merged: 2025-05-18T08:57:23Z

<!-- Guidelines: https://docs.spacestation14.io/en/getting-started/pr-guideline -->

## About the PR
<!-- What did you change? -->
Added the bomt type delivery.
It start ticking down when unlocked/broken/expired, the longer it takes to blow up the higher the chance it does so.

## Why / Balance
<!-- Discuss how this would affect game balance or explain why it was changed. Link any relevant discussions or issues. -->
Feature for mail, also a very funny thing that can happen like once per game.

## Technical details
<!-- Summary of code changes for easier review. -->
DeliveryBombComponent, the neccessary sprites and layers. You get the deal.

## Media
<!-- Attach media if the PR makes ingame changes (clothing, items, features, etc). 
Small fixes/refactors are exempt. Media may be used in SS14 progress reports with credit. -->
https://github.com/user-attachments/assets/3e4f2048-e0ff-40f4-a969-825894471724

## Requirements
<!-- Confirm the following by placing an X in the brackets [X]: -->
- [X] I have read and am following the [Pull Request and Changelog Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
- [X] I have added media to this PR or it does not require an ingame showcase.
<!-- You should understand that not following the above may get your PR closed at maintainer’s discretion -->

## Breaking changes
<!-- List any breaking changes, including namespaces, public class/method/field changes, prototype renames; and provide instructions for fixing them.
This will be posted in #codebase-changes. -->

**Changelog**
<!-- Add a Changelog entry to make players aware of new features or changes that could affect gameplay.
Make sure to read the guidelines and take this Changelog template out of the comment block in order for it to show up.
Changelog must have a :cl: symbol, so the bot recognizes the changes and adds them to the game's changelog. -->
:cl:
- add: Deliveries can now very rarely spawn as bomb-type! They grant A LOT of spesos... at a price.

<!--
:cl:
- add: Added fun!
- remove: Removed fun!
- tweak: Changed fun!
- fix: Fixed fun!
-->



Diff:
diff --git a/Content.Client/Delivery/DeliveryVisualizerSystem.cs b/Content.Client/Delivery/DeliveryVisualizerSystem.cs
index bf4a8f066b4d7..68149e248d684 100644
--- a/Content.Client/Delivery/DeliveryVisualizerSystem.cs
+++ b/Content.Client/Delivery/DeliveryVisualizerSystem.cs
@@ -42,6 +42,8 @@ public enum DeliveryVisualLayers : byte
     PriorityTape,
     Breakage,
     Trash,
+    Bomb,
+    BombPrimed,
 }
 
 public enum DeliverySpawnerVisualLayers : byte
diff --git a/Content.Shared/Delivery/DeliveryBombComponent.cs b/Content.Shared/Delivery/DeliveryBombComponent.cs
new file mode 100644
index 0000000000000..a51bb839ee10d
--- /dev/null
+++ b/Content.Shared/Delivery/DeliveryBombComponent.cs
@@ -0,0 +1,65 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Component given to deliveries.
+/// This delivery will "prime" based on circumstances defined in the datafield.
+/// When primed, it will attempt to explode every few seconds, with the chance increasing each time it fails to do so.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(DeliveryModifierSystem))]
+public sealed partial class DeliveryBombComponent : Component
+{
+    /// <summary>
+    /// How often will this bomb retry to explode.
+    /// </summary>
+    [DataField]
+    public TimeSpan ExplosionRetryDelay = TimeSpan.FromSeconds(5);
+
+    /// <summary>
+    /// The time at which the next retry will happen
+    /// </summary>
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
+    public TimeSpan NextExplosionRetry;
+
+    /// <summary>
+    /// The chance this bomb explodes each time it attempts to do so.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public float ExplosionChance = 0.01f;
+
+    /// <summary>
+    /// How much should the chance of explosion increase each failed retry?
+    /// </summary>
+    [DataField]
+    public float ExplosionChanceRetryIncrease = 0.01f;
+
+    /// <summary>
+    /// Should this bomb get primed when the delivery is unlocked?
+    /// </summary>
+    [DataField]
+    public bool PrimeOnUnlock = true;
+
+    /// <summary>
+    /// Should this bomb get primed when the delivery is broken?
+    /// Requires to be fragile as well.
+    /// </summary>
+    [DataField]
+    public bool PrimeOnBreakage = true;
+
+    /// <summary>
+    /// Should this bomb get primed when the delivery expires?
+    /// Requires to be priority as well.
+    /// </summary>
+    [DataField]
+    public bool PrimeOnExpire = true;
+
+    /// <summary>
+    /// Multiplier to choose when a crazy person actually opens it.
+    /// Multiplicative, not additive.
+    /// </summary>
+    [DataField]
+    public float SpesoMultiplier = 1.5f;
+}
diff --git a/Content.Shared/Delivery/DeliveryModifierSystem.cs b/Content.Shared/Delivery/DeliveryModifierSystem.cs
index 2b071b18cfb91..0e3f620f26812 100644
--- a/Content.Shared/Delivery/DeliveryModifierSystem.cs
+++ b/Content.Shared/Delivery/DeliveryModifierSystem.cs
@@ -1,6 +1,10 @@
+using Content.Shared.Audio;
 using Content.Shared.Destructible;
 using Content.Shared.Examine;
+using Content.Shared.Explosion.EntitySystems;
 using Content.Shared.NameModifier.EntitySystems;
+using JetBrains.Annotations;
+using Robust.Shared.Network;
 using Robust.Shared.Random;
 using Robust.Shared.Serialization;
 using Robust.Shared.Timing;
@@ -14,8 +18,11 @@ public sealed partial class DeliveryModifierSystem : EntitySystem
 {
     [Dependency] private readonly IRobustRandom _random = default!;
     [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly INetManager _net = default!;
     [Dependency] private readonly NameModifierSystem _nameModifier = default!;
     [Dependency] private readonly SharedDeliverySystem _delivery = default!;
+    [Dependency] private readonly SharedExplosionSystem _explosion = default!;
+    [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
 
     public override void Initialize()
     {
@@ -33,6 +40,14 @@ public override void Initialize()
         SubscribeLocalEvent<DeliveryFragileComponent, BreakageEventArgs>(OnFragileBreakage);
         SubscribeLocalEvent<DeliveryFragileComponent, ExaminedEvent>(OnFragileExamine);
         SubscribeLocalEvent<DeliveryFragileComponent, GetDeliveryMultiplierEvent>(OnGetFragileMultiplier);
+
+        SubscribeLocalEvent<DeliveryBombComponent, ComponentStartup>(OnExplosiveStartup);
+        SubscribeLocalEvent<PrimedDeliveryBombComponent, MapInitEvent>(OnPrimedExplosiveMapInit);
+        SubscribeLocalEvent<DeliveryBombComponent, ExaminedEvent>(OnExplosiveExamine);
+        SubscribeLocalEvent<DeliveryBombComponent, GetDeliveryMultiplierEvent>(OnGetExplosiveMultiplier);
+        SubscribeLocalEvent<DeliveryBombComponent, DeliveryUnlockedEvent>(OnExplosiveUnlock);
+        SubscribeLocalEvent<DeliveryBombComponent, DeliveryPriorityExpiredEvent>(OnExplosiveExpire);
+        SubscribeLocalEvent<DeliveryBombComponent, BreakageEventArgs>(OnExplosiveBreak);
     }
 
     #region Random
@@ -119,12 +134,80 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveStartup(Entity<DeliveryBombComponent> ent, ref ComponentStartup args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
+            return;
+
+        bomb.NextExplosionRetry = _timing.CurTime;
+    }
+
+    private void OnExplosiveExamine(Entity<DeliveryBombComponent> ent, ref ExaminedEvent args)
+    {
+        var trueName = _nameModifier.GetBaseName(ent.Owner);
+
+        var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
+
+        if (isPrimed)
+            args.PushMarkup(Loc.GetString("delivery-bomb-primed-examine", ("type", trueName)));
+        else
+            args.PushMarkup(Loc.GetString("delivery-bomb-examine", ("type", trueName)));
+    }
+
+    private void OnGetExplosiveMultiplier(Entity<DeliveryBombComponent> ent, ref GetDeliveryMultiplierEvent args)
+    {
+        // Big danger for big rewards
+        args.MultiplicativeMultiplier += ent.Comp.SpesoMultiplier;
+    }
+
+    private void OnExplosiveUnlock(Entity<DeliveryBombComponent> ent, ref DeliveryUnlockedEvent args)
+    {
+        if (!ent.Comp.PrimeOnUnlock)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveExpire(Entity<DeliveryBombComponent> ent, ref DeliveryPriorityExpiredEvent args)
+    {
+        if (!ent.Comp.PrimeOnExpire)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveBreak(Entity<DeliveryBombComponent> ent, ref BreakageEventArgs args)
+    {
+        if (!ent.Comp.PrimeOnBreakage)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    [PublicAPI]
+    public void PrimeBombDelivery(Entity<DeliveryBombComponent> ent)
+    {
+        EnsureComp<PrimedDeliveryBombComponent>(ent);
+
+        _delivery.UpdateBombVisuals(ent);
+
+        _ambientSound.SetAmbience(ent, true);
+    }
+    #endregion
+
     #region Update Loops
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
 
         UpdatePriorty(frameTime);
+        UpdateBomb(frameTime);
     }
 
     private void UpdatePriorty(float frameTime)
@@ -148,6 +231,27 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var bombQuery = EntityQueryEnumerator<PrimedDeliveryBombComponent, DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (bombQuery.MoveNext(out var uid, out _, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            // Explosions cannot be predicted.
+            if (_net.IsServer && _random.NextFloat() < bombData.ExplosionChance)
+                _explosion.TriggerExplosive(uid);
+
+            bombData.ExplosionChance += bombData.ExplosionChanceRetryIncrease;
+            Dirty(uid, bombData);
+        }
+    }
     #endregion
 }
 
diff --git a/Content.Shared/Delivery/DeliveryVisuals.cs b/Content.Shared/Delivery/DeliveryVisuals.cs
index 35391854beaa8..b63d6a20bb4b6 100644
--- a/Content.Shared/Delivery/DeliveryVisuals.cs
+++ b/Content.Shared/Delivery/DeliveryVisuals.cs
@@ -9,6 +9,7 @@ public enum DeliveryVisuals : byte
     IsTrash,
     IsBroken,
     IsFragile,
+    IsBomb,
     PriorityState,
     JobIcon,
 }
@@ -21,6 +22,14 @@ public enum DeliveryPriorityState : byte
     Inactive,
 }
 
+[Serializable, NetSerializable]
+public enum DeliveryBombState : byte
+{
+    Off,
+    Inactive,
+    Primed,
+}
+
 [Serializable, NetSerializable]
 public enum DeliverySpawnerVisuals : byte
 {
diff --git a/Content.Shared/Delivery/PrimedDeliveryBombComponent.cs b/Content.Shared/Delivery/PrimedDeliveryBombComponent.cs
new file mode 100644
index 0000000000000..f3fd124f28101
--- /dev/null
+++ b/Content.Shared/Delivery/PrimedDeliveryBombComponent.cs
@@ -0,0 +1,11 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Component given to deliveries.
+/// Indicates this bomb delivery is primed.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[Access(typeof(DeliveryModifierSystem))]
+public sealed partial class PrimedDeliveryBombComponent : Component;
diff --git a/Content.Shared/Delivery/SharedDeliverySystem.cs b/Content.Shared/Delivery/SharedDeliverySystem.cs
index 175dfbe70d011..52f83500e14d0 100644
--- a/Content.Shared/Delivery/SharedDeliverySystem.cs
+++ b/Content.Shared/Delivery/SharedDeliverySystem.cs
@@ -259,6 +259,13 @@ public void UpdateBrokenVisuals(Entity<DeliveryFragileComponent> ent, bool isFra
         _appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
     }
 
+    public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
+    {
+        var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
+
+        _appearance.SetData(ent, DeliveryVisuals.IsBomb, isPrimed ? DeliveryBombState.Primed : DeliveryBombState.Inactive);
+    }
+
     protected void UpdateDeliverySpawnerVisuals(EntityUid uid, int contents)
     {
         _appearance.SetData(uid, DeliverySpawnerVisuals.Contents, contents > 0);
diff --git a/Resources/Locale/en-US/delivery/delivery-component.ftl b/Resources/Locale/en-US/delivery/delivery-component.ftl
index c346b2a813b51..306dd9b044320 100644
--- a/Resources/Locale/en-US/delivery/delivery-component.ftl
+++ b/Resources/Locale/en-US/delivery/delivery-component.ftl
@@ -29,3 +29,6 @@ delivery-priority-expired-examine = This is a [color=orange]priority {$type}[/co
 
 delivery-fragile-examine = This is a [color=red]fragile {$type}[/color]. Deliver it intact for a bonus.
 delivery-fragile-broken-examine = This is a [color=red]fragile {$type}[/color]. It looks badly damaged.
+
+delivery-bomb-examine = This is a [color=purple]bomb {$type}[/color]. Oh no.
+delivery-bomb-primed-examine = This is a [color=purple]bomb {$type}[/color]. Reading this is a bad use of your time.
diff --git a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
index a209f5c6f758b..acf88fc8b34d4 100644
--- a/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
+++ b/Resources/Prototypes/Entities/Objects/Deliveries/deliveries.yml
@@ -27,6 +27,15 @@
         enum.DeliveryVisualLayers.Trash:
           True: { visible: true }
           False: { visible: false }
+      enum.DeliveryVisuals.IsBomb:
+        enum.DeliveryVisualLayers.Bomb:
+          Off: { visible: false }
+          Inactive: { visible: true }
+          Primed: { visible: true }
+        enum.DeliveryVisualLayers.BombPrimed:
+          Off: { visible: false }
+          Inactive: { visible: false }
+          Primed: { visible: true }
   - type: Label
     examinable: false
   - type: FingerprintReader
@@ -74,6 +83,13 @@
     - state: broken
       map: [ "enum.DeliveryVisualLayers.Breakage" ]
       visible: false
+    - state: bomb
+      map: [ "enum.DeliveryVisualLayers.Bomb" ]
+      visible: false
+    - state: bomb_unshaded
+      map: [ "enum.DeliveryVisualLayers.BombPrimed" ]
+      shader: unshaded
+      visible: false
   - type: MultiHandedItem
   - type: Item
     size: Huge
@@ -117,6 +133,13 @@
     - state: broken
       map: [ "enum.DeliveryVisualLayers.Breakage" ]
       visible: false
+    - state: bomb
+      map: [ "enum.DeliveryVisualLayers.Bomb" ]
+      visible: false
+    - state: bomb_unshaded
+      map: [ "enum.DeliveryVisualLayers.BombPrimed" ]
+      shader: unshaded
+      visible: false
   - type: Item
     storedRotation: 90
   - type: Delivery
@@ -139,6 +162,8 @@
       prob: 0.25
     - id: DeliveryModifierFragile
       prob: 0.25
+    - id: DeliveryModifierBomb
+      prob: 0.02 # Should happen maybe once or twice per game.
 
 - type: entity
   id: DeliveryModifierPriority
@@ -177,3 +202,21 @@
           collection: DeliveryOpenSounds
       - !type:DoActsBehavior
         acts: [ "Breakage" ]
+
+- type: entity
+  id: DeliveryModifierBomb
+  description: Components to add when a delivery is rolled as a bomb.
+  categories: [ HideSpawnMenu ]
+  components:
+  - type: DeliveryBomb
+  - type: AmbientSound
+    enabled: false
+    range: 8
+    sound:
+      path: /Audio/Effects/lightburn.ogg
+  - type: Explosive
+    explosionType: MicroBomb
+    totalIntensity: 120
+    intensitySlope: 4
+    maxIntensity: 30
+    canCreateVacuum: false
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/bomb-unshaded.png b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/bomb_unshaded.png
similarity index 100%
rename from Resources/Textures/Objects/Specific/Cargo/mail.rsi/bomb-unshaded.png
rename to Resources/Textures/Objects/Specific/Cargo/mail.rsi/bomb_unshaded.png
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/meta.json b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/meta.json
index 9aa97992b1517..500d1632fc1dd 100644
--- a/Resources/Textures/Objects/Specific/Cargo/mail.rsi/meta.json
+++ b/Resources/Textures/Objects/Specific/Cargo/mail.rsi/meta.json
@@ -43,7 +43,7 @@
             "name": "bomb"
         },
         {
-            "name": "bomb-unshaded",
+            "name": "bomb_unshaded",
             "delays": [
                 [
                     0.2,
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/bomb-unshaded.png b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/bomb_unshaded.png
similarity index 100%
rename from Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/bomb-unshaded.png
rename to Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/bomb_unshaded.png
diff --git a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/meta.json b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/meta.json
index a265c4aada8a7..67fafe31b3f90 100644
--- a/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/meta.json
+++ b/Resources/Textures/Objects/Specific/Cargo/mail_large.rsi/meta.json
@@ -43,7 +43,7 @@
             "name": "bomb"
         },
         {
-            "name": "bomb-unshaded",
+            "name": "bomb_unshaded",
             "delays": [
                 [
                     0.125,


Reviews:
user: login: slarticodefast
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-05-02T01:33:13Z
user: login: ScarKy0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T11:21:47Z
user: login: ScarKy0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T11:22:54Z
user: login: slarticodefast
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T11:26:29Z
user: login: ScarKy0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T11:27:03Z
user: login: slarticodefast
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T11:30:31Z
user: login: slarticodefast
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T20:20:38Z
user: login: slarticodefast
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T15:03:32Z
user: login: ScarKy0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T15:04:58Z
user: login: PJB3005
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-05-04T23:14:54Z
user: login: PJB3005
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T23:16:20Z
user: login: ScarKy0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T04:40:22Z
user: login: ScarKy0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T20:33:50Z
user: login: ScarKy0
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T20:44:43Z
user: login: metalgearsloth
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-18T07:25:03Z

Review comments:
diff_hunk: @@ -259,6 +259,14 @@ public void UpdateBrokenVisuals(Entity<DeliveryFragileComponent> ent, bool isFra
         _appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
     }
 
+    public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
+    {
+        if (!HasComp<DeliveryComponent>(ent))
+            return;
path: Content.Shared/Delivery/SharedDeliverySystem.cs
user: login: slarticodefast
body: is this needed here?
created_at: 2025-05-02T01:15:59Z
author_association: MEMBER
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: slarticodefast
body: you might want to add a `PrimedDeliveryBombComponent` to use in the query so you don't have to update the timespans for all of them
created_at: 2025-05-02T01:21:32Z
author_association: MEMBER
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
+
+            if (_random.NextFloat() < bombData.ExplosionChance)
+                _explosion.TriggerExplosive(uid);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: slarticodefast
body: this will desync between server and client
use randompredicted once it's merged into the engine
created_at: 2025-05-02T01:22:37Z
author_association: MEMBER
diff_hunk: @@ -119,12 +130,73 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+        ent.Comp.NextExplosionRetry = _timing.CurTime;
+        Dirty(ent);
+    }
+
+    private void OnExplosiveExamine(Entity<DeliveryBombComponent> ent, ref ExaminedEvent args)
+    {
+        var trueName = _nameModifier.GetBaseName(ent.Owner);
+
+        if (ent.Comp.Primed)
+            args.PushMarkup(Loc.GetString("delivery-bomb-primed-examine", ("type", trueName)));
+        else
+            args.PushMarkup(Loc.GetString("delivery-bomb-examine", ("type", trueName)));
+    }
+
+    private void OnGetExplosiveMultiplier(Entity<DeliveryBombComponent> ent, ref GetDeliveryMultiplierEvent args)
+    {
+        // Big danger for big rewards
+        args.MultiplicativeMultiplier += ent.Comp.SpesoMultiplier;
+    }
+
+    private void OnExplosiveUnlock(Entity<DeliveryBombComponent> ent, ref DeliveryUnlockedEvent args)
+    {
+        if (!ent.Comp.PrimeOnUnlock)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveExpire(Entity<DeliveryBombComponent> ent, ref DeliveryPriorityExpiredEvent args)
+    {
+        if (!ent.Comp.PrimeOnExpire)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveBreak(Entity<DeliveryBombComponent> ent, ref BreakageEventArgs args)
+    {
+        if (!ent.Comp.PrimeOnBreakage)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void PrimeBombDelivery(Entity<DeliveryBombComponent> ent)
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: slarticodefast
body: I think you can make this one a public API function in case forks want to use it
and make it check if it's already primed
created_at: 2025-05-02T01:31:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,70 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Delivery;
+
+/// <summary>
+/// Component given to deliveries.
+/// This delivery will "prime" based on circumstances defined in the datafield.
+/// When primed, it will attempt to explode every few seconds, with the chance increasing each time it fails to do so.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(DeliveryModifierSystem))]
+public sealed partial class DeliveryBombComponent : Component
+{
+    /// <summary>
+    /// Whether this bomb is primed and actively ticking.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public bool Primed;
+
+    /// <summary>
+    /// How often will this bomb retry to explode.
+    /// </summary>
+    [DataField]
+    public TimeSpan ExplosionRetryDelay = TimeSpan.FromSeconds(5);
+
+    /// <summary>
+    /// The time at which the next retry will happen
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public TimeSpan NextExplosionRetry;
path: Content.Shared/Delivery/DeliveryBombComponent.cs
user: login: slarticodefast
body: time offset serializer, autopausedfield
created_at: 2025-05-02T01:33:06Z
author_association: MEMBER
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: ScarKy0
body: Should all the explosion logic be on PrimedDelivery and all the priming logic stay on DeliveryBomb then?
created_at: 2025-05-02T11:21:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -259,6 +259,14 @@ public void UpdateBrokenVisuals(Entity<DeliveryFragileComponent> ent, bool isFra
         _appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
     }
 
+    public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
+    {
+        if (!HasComp<DeliveryComponent>(ent))
+            return;
path: Content.Shared/Delivery/SharedDeliverySystem.cs
user: login: ScarKy0
body: Just a check to know whether the DeliveryBomb is for sure added to a Delivery, in case its added somewhere else by accident or stuff
created_at: 2025-05-02T11:22:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -259,6 +259,14 @@ public void UpdateBrokenVisuals(Entity<DeliveryFragileComponent> ent, bool isFra
         _appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
     }
 
+    public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
+    {
+        if (!HasComp<DeliveryComponent>(ent))
+            return;
path: Content.Shared/Delivery/SharedDeliverySystem.cs
user: login: slarticodefast
body: It's probably fine without?
All it needs are the sprite layers, deliverycomp doesn't really matter for the visual update
created_at: 2025-05-02T11:26:29Z
author_association: MEMBER
diff_hunk: @@ -259,6 +259,14 @@ public void UpdateBrokenVisuals(Entity<DeliveryFragileComponent> ent, bool isFra
         _appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
     }
 
+    public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
+    {
+        if (!HasComp<DeliveryComponent>(ent))
+            return;
path: Content.Shared/Delivery/SharedDeliverySystem.cs
user: login: ScarKy0
body: Yeah but it also ensures it has the sprite layers since all the deliveries have them, but i can remove it then
created_at: 2025-05-02T11:27:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: slarticodefast
body: you can make it an empty marker component
it's mostly to optimize the entity query
you can see a lot of examples of this in the code, for example `ActiveChargerComponent`
created_at: 2025-05-02T11:30:30Z
author_association: MEMBER
diff_hunk: @@ -259,6 +259,14 @@ public void UpdateBrokenVisuals(Entity<DeliveryFragileComponent> ent, bool isFra
         _appearance.SetData(ent, DeliveryVisuals.IsFragile, isFragile);
     }
 
+    public void UpdateBombVisuals(Entity<DeliveryBombComponent> ent)
+    {
+        if (!HasComp<DeliveryComponent>(ent))
+            return;
path: Content.Shared/Delivery/SharedDeliverySystem.cs
user: login: slarticodefast
body: I would say it's better because in that case you want to get the error message telling you the layers are missing
created_at: 2025-05-02T20:20:38Z
author_association: MEMBER
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
+
+            if (_random.NextFloat() < bombData.ExplosionChance)
+                _explosion.TriggerExplosive(uid);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: slarticodefast
body: I didn't say that we should ignore it.
This will either need to do the manual workaround by setting the seed, wait for random predicted or not be predicted at all. I prefer the second option. I guess we will have to annoy sloth so he merges it.
created_at: 2025-05-04T15:03:32Z
author_association: MEMBER
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
+
+            if (_random.NextFloat() < bombData.ExplosionChance)
+                _explosion.TriggerExplosive(uid);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: ScarKy0
body: Welp guess we can wait then
created_at: 2025-05-04T15:04:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
+            return;
+
+        bomb.NextExplosionRetry = _timing.CurTime;
+        Dirty(ent);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: You're not modifying any networked state, and even if you were, it's on a different component than contained in `ent`. This doesn't make sense.
created_at: 2025-05-04T23:07:56Z
author_association: MEMBER
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: Map init isn't the right place to put this. You need component started instead. This code doesn't even run if a component is added on an existing entity, does it?
created_at: 2025-05-04T23:09:01Z
author_association: MEMBER
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: This should be started or initialized instead, not map init.
created_at: 2025-05-04T23:09:18Z
author_association: MEMBER
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
+            return;
+
+        bomb.NextExplosionRetry = _timing.CurTime;
+        Dirty(ent);
+    }
+
+    private void OnExplosiveExamine(Entity<DeliveryBombComponent> ent, ref ExaminedEvent args)
+    {
+        var trueName = _nameModifier.GetBaseName(ent.Owner);
+
+        var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
+
+        if (isPrimed)
+            args.PushMarkup(Loc.GetString("delivery-bomb-primed-examine", ("type", trueName)));
+        else
+            args.PushMarkup(Loc.GetString("delivery-bomb-examine", ("type", trueName)));
+    }
+
+    private void OnGetExplosiveMultiplier(Entity<DeliveryBombComponent> ent, ref GetDeliveryMultiplierEvent args)
+    {
+        // Big danger for big rewards
+        args.MultiplicativeMultiplier += ent.Comp.SpesoMultiplier;
+    }
+
+    private void OnExplosiveUnlock(Entity<DeliveryBombComponent> ent, ref DeliveryUnlockedEvent args)
+    {
+        if (!ent.Comp.PrimeOnUnlock)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveExpire(Entity<DeliveryBombComponent> ent, ref DeliveryPriorityExpiredEvent args)
+    {
+        if (!ent.Comp.PrimeOnExpire)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveBreak(Entity<DeliveryBombComponent> ent, ref BreakageEventArgs args)
+    {
+        if (!ent.Comp.PrimeOnBreakage)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    [PublicAPI]
+    public void PrimeBombDelivery(Entity<DeliveryBombComponent> ent)
+    {
+        if (HasComp<PrimedDeliveryBombComponent>(ent))
+            return;
+
+        EnsureComp<PrimedDeliveryBombComponent>(ent);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: ```suggestion
        if (EnsureComp<PrimedDeliveryBombComponent>(ent))
            return;
```
created_at: 2025-05-04T23:11:04Z
author_association: MEMBER
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
+            return;
+
+        bomb.NextExplosionRetry = _timing.CurTime;
+        Dirty(ent);
+    }
+
+    private void OnExplosiveExamine(Entity<DeliveryBombComponent> ent, ref ExaminedEvent args)
+    {
+        var trueName = _nameModifier.GetBaseName(ent.Owner);
+
+        var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
+
+        if (isPrimed)
+            args.PushMarkup(Loc.GetString("delivery-bomb-primed-examine", ("type", trueName)));
+        else
+            args.PushMarkup(Loc.GetString("delivery-bomb-examine", ("type", trueName)));
+    }
+
+    private void OnGetExplosiveMultiplier(Entity<DeliveryBombComponent> ent, ref GetDeliveryMultiplierEvent args)
+    {
+        // Big danger for big rewards
+        args.MultiplicativeMultiplier += ent.Comp.SpesoMultiplier;
+    }
+
+    private void OnExplosiveUnlock(Entity<DeliveryBombComponent> ent, ref DeliveryUnlockedEvent args)
+    {
+        if (!ent.Comp.PrimeOnUnlock)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveExpire(Entity<DeliveryBombComponent> ent, ref DeliveryPriorityExpiredEvent args)
+    {
+        if (!ent.Comp.PrimeOnExpire)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveBreak(Entity<DeliveryBombComponent> ent, ref BreakageEventArgs args)
+    {
+        if (!ent.Comp.PrimeOnBreakage)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    [PublicAPI]
+    public void PrimeBombDelivery(Entity<DeliveryBombComponent> ent)
+    {
+        if (HasComp<PrimedDeliveryBombComponent>(ent))
+            return;
+
+        EnsureComp<PrimedDeliveryBombComponent>(ent);
+
+        _delivery.UpdateBombVisuals(ent);
+
+        _ambientSound.SetAmbience(ent, true);
+
+        Dirty(ent);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: Again, you didn't actually change any networked state on `ent`. Does this even do anything?
created_at: 2025-05-04T23:11:46Z
author_association: MEMBER
diff_hunk: @@ -148,6 +235,27 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var bombQuery = EntityQueryEnumerator<DeliveryBombComponent, PrimedDeliveryBombComponent>();
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: Swap the order of these around, so the game enumerates over `PrimedDeliveryBombComponent` as primary component (as those will be less numerous).
created_at: 2025-05-04T23:12:19Z
author_association: MEMBER
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
+
+            if (_random.NextFloat() < bombData.ExplosionChance)
+                _explosion.TriggerExplosive(uid);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: Can we even predict explosions? This feels like a totally reasonable thing to not have prediction for.
created_at: 2025-05-04T23:13:20Z
author_association: MEMBER
diff_hunk: @@ -148,6 +220,29 @@ private void UpdatePriorty(float frameTime)
             }
         }
     }
+
+    private void UpdateBomb(float frameTime)
+    {
+        var priorityQuery = EntityQueryEnumerator<DeliveryBombComponent>();
+        var curTime = _timing.CurTime;
+
+        while (priorityQuery.MoveNext(out var uid, out var bombData))
+        {
+            if (bombData.NextExplosionRetry > curTime)
+                continue;
+
+            bombData.NextExplosionRetry += bombData.ExplosionRetryDelay;
+
+            if (!bombData.Primed)
+                continue;
+
+            if (_random.NextFloat() < bombData.ExplosionChance)
+                _explosion.TriggerExplosive(uid);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: PJB3005
body: Correct, I just checked the code. Explosions can't even be predicted. Just leave this code server-only.
created_at: 2025-05-04T23:16:20Z
author_association: MEMBER
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: ScarKy0
body: It does. MapInit usually seems to be the way to go since ComponentStartup has PVS issues (also seems to run every time entity enters PVS on client, unless I misremember)
created_at: 2025-05-05T04:40:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
+            return;
+
+        bomb.NextExplosionRetry = _timing.CurTime;
+        Dirty(ent);
+    }
+
+    private void OnExplosiveExamine(Entity<DeliveryBombComponent> ent, ref ExaminedEvent args)
+    {
+        var trueName = _nameModifier.GetBaseName(ent.Owner);
+
+        var isPrimed = HasComp<PrimedDeliveryBombComponent>(ent);
+
+        if (isPrimed)
+            args.PushMarkup(Loc.GetString("delivery-bomb-primed-examine", ("type", trueName)));
+        else
+            args.PushMarkup(Loc.GetString("delivery-bomb-examine", ("type", trueName)));
+    }
+
+    private void OnGetExplosiveMultiplier(Entity<DeliveryBombComponent> ent, ref GetDeliveryMultiplierEvent args)
+    {
+        // Big danger for big rewards
+        args.MultiplicativeMultiplier += ent.Comp.SpesoMultiplier;
+    }
+
+    private void OnExplosiveUnlock(Entity<DeliveryBombComponent> ent, ref DeliveryUnlockedEvent args)
+    {
+        if (!ent.Comp.PrimeOnUnlock)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveExpire(Entity<DeliveryBombComponent> ent, ref DeliveryPriorityExpiredEvent args)
+    {
+        if (!ent.Comp.PrimeOnExpire)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    private void OnExplosiveBreak(Entity<DeliveryBombComponent> ent, ref BreakageEventArgs args)
+    {
+        if (!ent.Comp.PrimeOnBreakage)
+            return;
+
+        PrimeBombDelivery(ent);
+    }
+
+    [PublicAPI]
+    public void PrimeBombDelivery(Entity<DeliveryBombComponent> ent)
+    {
+        if (HasComp<PrimedDeliveryBombComponent>(ent))
+            return;
+
+        EnsureComp<PrimedDeliveryBombComponent>(ent);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: ScarKy0
body: The check doesnt even have to be here, cause EnsureComp seems to always return something anyways, so not like it will ever return false
created_at: 2025-05-05T20:33:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        if (!TryComp<DeliveryBombComponent>(ent, out var bomb))
+            return;
+
+        bomb.NextExplosionRetry = _timing.CurTime;
+        Dirty(ent);
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: ScarKy0
body: NextExplosionRetry is networked. Though since this is just startup I think there would be no need to dirty anyways?
Also yeah, i forgot to change the target entity, oops.
created_at: 2025-05-05T20:44:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,12 +132,86 @@ private void OnGetFragileMultiplier(Entity<DeliveryFragileComponent> ent, ref Ge
     }
     #endregion
 
+    #region Explosive
+    private void OnExplosiveMapInit(Entity<DeliveryBombComponent> ent, ref MapInitEvent args)
+    {
+        _delivery.UpdateBombVisuals(ent);
+    }
+
+    private void OnPrimedExplosiveMapInit(Entity<PrimedDeliveryBombComponent> ent, ref MapInitEvent args)
path: Content.Shared/Delivery/DeliveryModifierSystem.cs
user: login: metalgearsloth
body: I think this one is actually fine as mapinit.
created_at: 2025-05-18T07:25:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-30T12:18:05Z
body: RSI Diff Bot; head commit bf5b46c7f1b2c773eec01f17a3c77c1db697e21d merging into feffbea9f97cb99ec8cf17da366cedd810c5d6e2
This PR makes changes to 1 or more RSIs. Here is a summary of all changes:
Edit: diff updated after bf5b46c7f1b2c773eec01f17a3c77c1db697e21d
author_association: CONTRIBUTOR
user: login: lzk228
created_at: 2025-04-30T12:21:16Z
body: hey cap you got a letter from... Urist McKaczynski?...
author_association: CONTRIBUTOR
user: login: SlimmSlamm
created_at: 2025-04-30T13:50:42Z
body: I think this would work well as an Uplink Item as well with letting you change the name and job like an Agent ID.
author_association: NONE
user: login: ps3moira
created_at: 2025-05-02T00:08:28Z
body: way too long, should blow up the instant it's opened
author_association: CONTRIBUTOR
user: login: ScarKy0
created_at: 2025-05-02T00:09:36Z
body: > way too long, should blow up the instant it's opened

People keep saying its unfun(though I agree with you, it would be better)
If anything can be balanced out later
author_association: CONTRIBUTOR
user: login: superjj18
created_at: 2025-05-02T00:12:34Z
body: > way too long, should blow up the instant it's opened

Regular mail should have a small chance of being a hidden bomb, maybe not instant but <5 seconds
author_association: NONE
user: login: TeenSarlacc
created_at: 2025-05-02T00:23:59Z
body: > Regular mail should have a small chance of being a hidden bomb, maybe not instant but \u003C5 seconds

\>open up envelope 
\>primed singularity grenade
\>:godo:
author_association: CONTRIBUTOR
user: login: walksanatora
created_at: 2025-05-05T20:42:58Z
body: I kinda wish it had a package variant. so it could spawn with a creature (mothroach) inside. so now you have a dilema. do you open the box causing it to explode but rescuing the mothroach. or do you let it sit there alone, trapped.
author_association: CONTRIBUTOR
user: login: ScarKy0
created_at: 2025-05-05T20:43:25Z
body: > I kinda wish it had a package variant. so it could spawn with a creature (mothroach) inside. so now you have a dilema. do you open the box causing it to explode but rescuing the mothroach. or do you let it sit there alone, trapped

It can be both letter AND package
author_association: CONTRIBUTOR
user: login: walksanatora
created_at: 2025-05-05T20:44:46Z
body: media didn't show it so I assumed it didn't exists. o7 cant wait to terroize my friends with a mothroach dilema. (I am giving it full size syndicate bomb range)
author_association: CONTRIBUTOR
user: login: ScarKy0
created_at: 2025-05-18T08:56:59Z
body: Approved by sloth over discord, so gonna self merge
author_association: CONTRIBUTOR