]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Hardbombs & Defusables (#15380)
authoreclips_e <67359748+Just-a-Unity-Dev@users.noreply.github.com>
Tue, 12 Sep 2023 01:42:38 +0000 (09:42 +0800)
committerGitHub <noreply@github.com>
Tue, 12 Sep 2023 01:42:38 +0000 (18:42 -0700)
Co-authored-by: Kara <lunarautomaton6@gmail.com>
Co-authored-by: Just-a-Unity-Dev <just-a-unity-dev@users.noreply.github.com>
Co-authored-by: LankLTE <twlowe06@gmail.com>
Co-authored-by: LankLTE <135308300+LankLTE@users.noreply.github.com>
31 files changed:
Content.Server/Defusable/Components/DefusableComponent.cs [new file with mode: 0644]
Content.Server/Defusable/Systems/DefusableSystem.cs [new file with mode: 0644]
Content.Server/Defusable/WireActions/ActivateWireAction.cs [new file with mode: 0644]
Content.Server/Defusable/WireActions/BoltWireAction.cs [new file with mode: 0644]
Content.Server/Defusable/WireActions/BoomWireAction.cs [new file with mode: 0644]
Content.Server/Defusable/WireActions/DelayWireAction.cs [new file with mode: 0644]
Content.Server/Defusable/WireActions/ProceedWireAction.cs [new file with mode: 0644]
Content.Server/Explosion/EntitySystems/TriggerSystem.cs
Content.Shared/Defusable/SharedDefusableSystem.cs [new file with mode: 0644]
Resources/Locale/en-US/defusable/examine.ftl [new file with mode: 0644]
Resources/Locale/en-US/defusable/popup.ftl [new file with mode: 0644]
Resources/Locale/en-US/defusable/verb.ftl [new file with mode: 0644]
Resources/Locale/en-US/guidebook/guides.ftl
Resources/Locale/en-US/store/uplink-catalog.ftl
Resources/Locale/en-US/wires/wire-names.ftl
Resources/Prototypes/Catalog/uplink_catalog.yml
Resources/Prototypes/Entities/Structures/Machines/bombs.yml [new file with mode: 0644]
Resources/Prototypes/Guidebook/security.yml
Resources/Prototypes/Wires/layouts.yml
Resources/Prototypes/explosion.yml
Resources/ServerInfo/Guidebook/Security/Defusal.xml [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-active.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-wires.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/base-bomb.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/meta.json [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-active.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-wires.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-active.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-wires.png [new file with mode: 0644]
Resources/Textures/Structures/Machines/bomb.rsi/training-bomb.png [new file with mode: 0644]

diff --git a/Content.Server/Defusable/Components/DefusableComponent.cs b/Content.Server/Defusable/Components/DefusableComponent.cs
new file mode 100644 (file)
index 0000000..4b06c90
--- /dev/null
@@ -0,0 +1,76 @@
+using Content.Server.Defusable.Systems;
+using Content.Server.Explosion.Components;
+using Robust.Shared.Audio;
+
+namespace Content.Server.Defusable.Components;
+
+/// <summary>
+/// This is used for bombs that should be defused. The explosion configuration should be handled by <see cref="ExplosiveComponent"/>.
+/// </summary>
+[RegisterComponent, Access(typeof(DefusableSystem))]
+public sealed partial class DefusableComponent : Component
+{
+    /// <summary>
+    ///     The bomb will play this sound on defusal.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadOnly), DataField("defusalSound")]
+    public SoundSpecifier DefusalSound = new SoundPathSpecifier("/Audio/Misc/notice2.ogg");
+
+    /// <summary>
+    ///     The bomb will play this sound on bolt.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadOnly), DataField("boltSound")]
+    public SoundSpecifier BoltSound = new SoundPathSpecifier("/Audio/Machines/boltsdown.ogg");
+
+    /// <summary>
+    ///     Is this bomb one use?
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("disposable")]
+    public bool Disposable = true;
+
+    /// <summary>
+    /// Is the bomb live? This is different from BombUsable because this tracks whether the bomb is ticking down or not.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("activated")]
+    public bool Activated;
+
+    /// <summary>
+    /// Is the bomb actually usable? This is different from Activated because this tracks whether the bomb can even start in the first place.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool Usable = true;
+
+    /// <summary>
+    /// Does the bomb show how much time remains?
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool DisplayTime = true;
+
+    /// <summary>
+    /// Is this bomb supposed to be stuck to the ground?
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite)]
+    public bool Bolted;
+
+    /// <summary>
+    /// How much time is added when the Activate wire is pulsed?
+    /// </summary>
+    [DataField("delayTime")]
+    public int DelayTime = 30;
+
+    #region Wires
+    // wires, this is so that they're one use
+    [ViewVariables(VVAccess.ReadWrite), Access(Other=AccessPermissions.ReadWrite)]
+    public bool DelayWireUsed;
+
+    [ViewVariables(VVAccess.ReadWrite), Access(Other=AccessPermissions.ReadWrite)]
+    public bool ProceedWireCut;
+
+    [ViewVariables(VVAccess.ReadWrite), Access(Other=AccessPermissions.ReadWrite)]
+    public bool ProceedWireUsed;
+
+    [ViewVariables(VVAccess.ReadWrite), Access(Other=AccessPermissions.ReadWrite)]
+    public bool ActivatedWireUsed;
+
+    #endregion
+}
diff --git a/Content.Server/Defusable/Systems/DefusableSystem.cs b/Content.Server/Defusable/Systems/DefusableSystem.cs
new file mode 100644 (file)
index 0000000..6f9873e
--- /dev/null
@@ -0,0 +1,435 @@
+using Content.Server.Defusable.Components;
+using Content.Server.Explosion.Components;
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Popups;
+using Content.Server.Wires;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Construction.Components;
+using Content.Shared.Database;
+using Content.Shared.Defusable;
+using Content.Shared.Examine;
+using Content.Shared.Popups;
+using Content.Shared.Verbs;
+using Content.Shared.Wires;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Defusable.Systems;
+
+/// <inheritdoc/>
+public sealed class DefusableSystem : SharedDefusableSystem
+{
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly ExplosionSystem _explosion = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly TriggerSystem _trigger = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly TransformSystem _transform = default!;
+    [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+    [Dependency] private readonly WiresSystem _wiresSystem = default!;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DefusableComponent, ExaminedEvent>(OnExamine);
+        SubscribeLocalEvent<DefusableComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
+        SubscribeLocalEvent<DefusableComponent, AnchorAttemptEvent>(OnAnchorAttempt);
+        SubscribeLocalEvent<DefusableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
+    }
+
+    #region Subscribed Events
+    /// <summary>
+    ///     Adds a verb allowing for the bomb to be started easily.
+    /// </summary>
+    private void OnGetAltVerbs(EntityUid uid, DefusableComponent comp, GetVerbsEvent<AlternativeVerb> args)
+    {
+        if (!args.CanInteract || !args.CanAccess)
+            return;
+
+        args.Verbs.Add(new AlternativeVerb
+        {
+            Text = Loc.GetString("defusable-verb-begin"),
+            Disabled = comp is { Activated: true, Usable: true },
+            Priority = 10,
+            Act = () =>
+            {
+                TryStartCountdown(uid, args.User, comp);
+            }
+        });
+    }
+
+    private void OnExamine(EntityUid uid, DefusableComponent comp, ExaminedEvent args)
+    {
+        if (!args.IsInDetailsRange)
+            return;
+
+        if (!comp.Usable)
+        {
+            args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
+        }
+        else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
+        {
+            if (comp.DisplayTime)
+            {
+                args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
+                    ("time", MathF.Floor(activeComp.TimeRemaining))));
+            }
+            else
+            {
+                args.PushMarkup(Loc.GetString("defusable-examine-live-display-off", ("name", uid)));
+            }
+        }
+        else
+        {
+            args.PushMarkup(Loc.GetString("defusable-examine-inactive", ("name", uid)));
+        }
+
+        args.PushMarkup(Loc.GetString("defusable-examine-bolts", ("down", comp.Bolted)));
+    }
+
+    private void OnAnchorAttempt(EntityUid uid, DefusableComponent component, AnchorAttemptEvent args)
+    {
+        if (CheckAnchorAttempt(uid, component, args))
+            args.Cancel();
+    }
+
+    private void OnUnanchorAttempt(EntityUid uid, DefusableComponent component, UnanchorAttemptEvent args)
+    {
+        if (CheckAnchorAttempt(uid, component, args))
+            args.Cancel();
+    }
+
+    private bool CheckAnchorAttempt(EntityUid uid, DefusableComponent component, BaseAnchoredAttemptEvent args)
+    {
+        // Don't allow the thing to be anchored if bolted to the ground
+        if (!component.Bolted)
+            return false;
+
+        var msg = Loc.GetString("defusable-popup-cant-anchor", ("name", uid));
+        _popup.PopupEntity(msg, uid, args.User);
+
+        return true;
+    }
+
+    #endregion
+
+    #region Public
+
+    public void TryStartCountdown(EntityUid uid, EntityUid user, DefusableComponent comp)
+    {
+        if (!comp.Usable)
+        {
+            _popup.PopupEntity(Loc.GetString("defusable-popup-fried", ("name", uid)), uid);
+            return;
+        }
+
+        var xform = Transform(uid);
+        if (!xform.Anchored)
+            _transform.AnchorEntity(uid, xform);
+
+        SetBolt(comp, true);
+        SetActivated(comp, true);
+
+        _popup.PopupEntity(Loc.GetString("defusable-popup-begun", ("name", uid)), uid);
+        if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
+        {
+            _trigger.HandleTimerTrigger(
+                uid,
+                null,
+                timerTrigger.Delay,
+                timerTrigger.BeepInterval,
+                timerTrigger.InitialBeepDelay,
+                timerTrigger.BeepSound
+            );
+        }
+
+        RaiseLocalEvent(uid, new BombArmedEvent(uid));
+
+        _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):entity} begun a countdown on {ToPrettyString(uid):entity}");
+
+        if (TryComp<WiresPanelComponent>(uid, out var wiresPanelComponent))
+            _wiresSystem.TogglePanel(uid, wiresPanelComponent, false);
+    }
+
+    public void TryDetonateBomb(EntityUid uid, DefusableComponent comp)
+    {
+        if (!comp.Activated)
+            return;
+
+        _popup.PopupEntity(Loc.GetString("defusable-popup-boom", ("name", uid)), uid, PopupType.LargeCaution);
+
+        RaiseLocalEvent(uid, new BombDetonatedEvent(uid));
+
+        _explosion.TriggerExplosive(uid);
+        QueueDel(uid);
+
+        _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(uid):entity} has been detonated.");
+    }
+
+    public void TryDefuseBomb(EntityUid uid, DefusableComponent comp)
+    {
+        if (!comp.Activated)
+            return;
+
+        _popup.PopupEntity(Loc.GetString("defusable-popup-defuse", ("name", uid)), uid);
+        SetActivated(comp, false);
+
+        var xform = Transform(uid);
+
+        if (comp.Disposable)
+        {
+            SetUsable(comp, false);
+            RemComp<ExplodeOnTriggerComponent>(uid);
+            RemComp<OnUseTimerTriggerComponent>(uid);
+        }
+        RemComp<ActiveTimerTriggerComponent>(uid);
+
+        _audio.PlayPvs(comp.DefusalSound, uid);
+
+        RaiseLocalEvent(uid, new BombDefusedEvent(uid));
+
+        comp.ActivatedWireUsed = false;
+        comp.DelayWireUsed = false;
+        comp.ProceedWireCut = false;
+        comp.ProceedWireUsed = false;
+        comp.Bolted = false;
+
+        if (xform.Anchored)
+            _transform.Unanchor(uid, xform);
+
+        _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(uid):entity} has been defused!");
+    }
+
+    // jesus christ
+    public void SetUsable(DefusableComponent component, bool value)
+    {
+        component.Usable = value;
+    }
+
+    public void SetDisplayTime(DefusableComponent component, bool value)
+    {
+        component.DisplayTime = value;
+    }
+
+    /// <summary>
+    /// Sets the Activated value of a component to a value.
+    /// </summary>
+    /// <param name="component"></param>
+    /// <param name="value"></param>
+    /// <remarks>
+    /// Use <see cref="TryDefuseBomb"/> to defuse bomb. This is a setter.
+    /// </remarks>
+    public void SetActivated(DefusableComponent component, bool value)
+    {
+        component.Activated = value;
+    }
+
+    public void SetBolt(DefusableComponent component, bool value)
+    {
+        component.Bolted = value;
+    }
+
+    #endregion
+
+    #region Wires
+
+    public void DelayWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (comp is not { Activated: true, DelayWireUsed: false })
+            return;
+
+        _trigger.TryDelay(wire.Owner, 30f);
+        _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
+        comp.DelayWireUsed = true;
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):user} pulsed the DeLAY wire of {ToPrettyString(wire.Owner):entity}.");
+    }
+
+    public bool ProceedWireCut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (comp is not { Activated: true, ProceedWireCut: false })
+            return true;
+
+        _popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
+        SetDisplayTime(comp, false);
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):user} cut the PRoCeeD wire of {ToPrettyString(wire.Owner):entity}.");
+
+        comp.ProceedWireCut = true;
+        return true;
+    }
+
+    public void ProceedWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (comp is { Activated: true, ProceedWireUsed: false })
+        {
+            comp.ProceedWireUsed = true;
+            _trigger.TryDelay(wire.Owner, -15f);
+        }
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):user} pulsed the PRoCeeD wire of {ToPrettyString(wire.Owner):entity}.");
+
+        _popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
+    }
+
+    public bool ActivateWireCut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        // if you cut the wire it just defuses the bomb
+
+        if (comp.Activated)
+        {
+            TryDefuseBomb(wire.Owner, comp);
+
+            _adminLogger.Add(LogType.Explosion, LogImpact.High,
+                $"{ToPrettyString(user):user} has defused {ToPrettyString(wire.Owner):entity}!");
+        }
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):user} cut the LIVE wire of {ToPrettyString(wire.Owner):entity}.");
+
+        return true;
+    }
+
+    public void ActivateWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        // if the component isnt active, just start the countdown
+        // if it is and it isn't already used then delay it
+
+        if (comp.Activated)
+        {
+            if (!comp.ActivatedWireUsed)
+            {
+                _trigger.TryDelay(wire.Owner, 30f);
+                _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
+                comp.ActivatedWireUsed = true;
+                _adminLogger.Add(LogType.Explosion, LogImpact.High,
+                    $"{ToPrettyString(user):user} pulsed the LIVE wire of {ToPrettyString(wire.Owner):entity}.");
+            }
+        }
+        else
+        {
+            TryStartCountdown(wire.Owner, user, comp);
+            _adminLogger.Add(LogType.Explosion, LogImpact.High,
+                $"{ToPrettyString(user):user} pulsed the LIVE wire of {ToPrettyString(wire.Owner):entity} and begun the countdown.");
+        }
+    }
+
+    public bool BoomWireCut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (comp.Activated)
+        {
+            EntityManager.System<DefusableSystem>().TryDetonateBomb(wire.Owner, comp);
+            _adminLogger.Add(LogType.Explosion, LogImpact.Extreme,
+                $"{ToPrettyString(user):user} cut the BOOM wire of {ToPrettyString(wire.Owner):entity} and caused it to detonate!");
+        }
+        else
+        {
+            EntityManager.System<DefusableSystem>().SetUsable(comp, false);
+            _adminLogger.Add(LogType.Explosion, LogImpact.High,
+                $"{ToPrettyString(user):user} cut the BOOM wire of {ToPrettyString(wire.Owner):entity}.");
+        }
+        return true;
+    }
+
+    public bool BoomWireMend(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (comp is { Activated: false, Usable: false })
+        {
+            SetUsable(comp, true);
+
+            _adminLogger.Add(LogType.Explosion, LogImpact.High,
+                $"{ToPrettyString(user):user} mended the BOOM wire of {ToPrettyString(wire.Owner):entity}.");
+        }
+        // you're already dead lol
+        return true;
+    }
+
+    public void BoomWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (comp.Activated)
+        {
+            TryDetonateBomb(wire.Owner, comp);
+        }
+        _adminLogger.Add(LogType.Explosion, LogImpact.Extreme,
+            $"{ToPrettyString(user):user} pulsed the BOOM wire of {ToPrettyString(wire.Owner):entity} and caused it to detonate!");
+    }
+
+    public bool BoltWireMend(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (!comp.Activated)
+            return true;
+
+        SetBolt(comp, true);
+        _audio.PlayPvs(comp.BoltSound, wire.Owner);
+        _popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):user} mended the BOLT wire of {ToPrettyString(wire.Owner):entity}!");
+
+        return true;
+    }
+
+    public bool BoltWireCut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        if (!comp.Activated)
+            return true;
+
+        SetBolt(comp, false);
+        _audio.PlayPvs(comp.BoltSound, wire.Owner);
+        _popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):user} cut the BOLT wire of {ToPrettyString(wire.Owner):entity}!");
+
+        return true;
+    }
+
+    public void BoltWirePulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        _popup.PopupEntity(Loc.GetString("defusable-popup-wire-bolt-pulse", ("name", wire.Owner)), wire.Owner);
+
+        _adminLogger.Add(LogType.Explosion, LogImpact.High,
+            $"{ToPrettyString(user):user} pulsed the BOLT wire of {ToPrettyString(wire.Owner):entity}!");
+    }
+
+    #endregion
+}
+
+public sealed class BombDefusedEvent : EntityEventArgs
+{
+    public EntityUid Entity;
+
+    public BombDefusedEvent(EntityUid entity)
+    {
+        Entity = entity;
+    }
+}
+public sealed class BombArmedEvent : EntityEventArgs
+{
+    public EntityUid Entity;
+
+    public BombArmedEvent(EntityUid entity)
+    {
+        Entity = entity;
+    }
+}
+public sealed class BombDetonatedEvent : EntityEventArgs
+{
+    public EntityUid Entity;
+
+    public BombDetonatedEvent(EntityUid entity)
+    {
+        Entity = entity;
+    }
+}
diff --git a/Content.Server/Defusable/WireActions/ActivateWireAction.cs b/Content.Server/Defusable/WireActions/ActivateWireAction.cs
new file mode 100644 (file)
index 0000000..c5e4980
--- /dev/null
@@ -0,0 +1,42 @@
+using Content.Server.Defusable.Components;
+using Content.Server.Defusable.Systems;
+using Content.Server.Doors.Systems;
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Popups;
+using Content.Server.Wires;
+using Content.Shared.Defusable;
+using Content.Shared.Doors;
+using Content.Shared.Doors.Components;
+using Content.Shared.Wires;
+
+namespace Content.Server.Defusable.WireActions;
+
+public sealed partial class ActivateWireAction : ComponentWireAction<DefusableComponent>
+{
+    public override Color Color { get; set; } = Color.Lime;
+    public override string Name { get; set; } = "wire-name-bomb-live";
+
+    public override StatusLightState? GetLightState(Wire wire, DefusableComponent comp)
+    {
+        return comp.Activated ? StatusLightState.BlinkingFast : StatusLightState.Off;
+    }
+
+    public override object StatusKey { get; } = DefusableWireStatus.LiveIndicator;
+
+    public override bool Cut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return EntityManager.System<DefusableSystem>().ActivateWireCut(user, wire, comp);
+    }
+
+    public override bool Mend(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        // if its not disposable defusable system already handles* this
+        // *probably
+        return true;
+    }
+
+    public override void Pulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        EntityManager.System<DefusableSystem>().ActivateWirePulse(user, wire, comp);
+    }
+}
diff --git a/Content.Server/Defusable/WireActions/BoltWireAction.cs b/Content.Server/Defusable/WireActions/BoltWireAction.cs
new file mode 100644 (file)
index 0000000..cec8f71
--- /dev/null
@@ -0,0 +1,38 @@
+using Content.Server.Defusable.Components;
+using Content.Server.Defusable.Systems;
+using Content.Server.Popups;
+using Content.Server.Wires;
+using Content.Shared.Defusable;
+using Content.Shared.Wires;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Defusable.WireActions;
+
+public sealed partial class BoltWireAction : ComponentWireAction<DefusableComponent>
+{
+    public override Color Color { get; set; } = Color.Red;
+    public override string Name { get; set; } = "wire-name-bomb-bolt";
+    public override bool LightRequiresPower { get; set; } = false;
+
+    public override StatusLightState? GetLightState(Wire wire, DefusableComponent comp)
+    {
+        return comp.Bolted ? StatusLightState.On : StatusLightState.Off;
+    }
+
+    public override object StatusKey { get; } = DefusableWireStatus.BoltIndicator;
+
+    public override bool Cut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return EntityManager.System<DefusableSystem>().BoltWireCut(user, wire, comp);
+    }
+
+    public override bool Mend(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return EntityManager.System<DefusableSystem>().BoltWireMend(user, wire, comp);
+    }
+
+    public override void Pulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        EntityManager.System<DefusableSystem>().BoltWirePulse(user, wire, comp);
+    }
+}
diff --git a/Content.Server/Defusable/WireActions/BoomWireAction.cs b/Content.Server/Defusable/WireActions/BoomWireAction.cs
new file mode 100644 (file)
index 0000000..bb1ae17
--- /dev/null
@@ -0,0 +1,39 @@
+using Content.Server.Defusable.Components;
+using Content.Server.Defusable.Systems;
+using Content.Server.Doors.Systems;
+using Content.Server.Wires;
+using Content.Shared.Defusable;
+using Content.Shared.Doors;
+using Content.Shared.Doors.Components;
+using Content.Shared.Wires;
+
+namespace Content.Server.Defusable.WireActions;
+
+public sealed partial class BoomWireAction : ComponentWireAction<DefusableComponent>
+{
+    public override Color Color { get; set; } = Color.Red;
+    public override string Name { get; set; } = "wire-name-bomb-boom";
+    public override bool LightRequiresPower { get; set; } = false;
+
+    public override StatusLightState? GetLightState(Wire wire, DefusableComponent comp)
+    {
+        return comp.Activated ? StatusLightState.On : StatusLightState.Off;
+    }
+
+    public override object StatusKey { get; } = DefusableWireStatus.BoomIndicator;
+
+    public override bool Cut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return EntityManager.System<DefusableSystem>().BoomWireCut(user, wire, comp);
+    }
+
+    public override bool Mend(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return EntityManager.System<DefusableSystem>().BoomWireMend(user, wire, comp);
+    }
+
+    public override void Pulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        EntityManager.System<DefusableSystem>().BoomWirePulse(user, wire, comp);
+    }
+}
diff --git a/Content.Server/Defusable/WireActions/DelayWireAction.cs b/Content.Server/Defusable/WireActions/DelayWireAction.cs
new file mode 100644 (file)
index 0000000..4d326b3
--- /dev/null
@@ -0,0 +1,44 @@
+using Content.Server.Defusable.Components;
+using Content.Server.Defusable.Systems;
+using Content.Server.Doors.Systems;
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Popups;
+using Content.Server.Wires;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.Defusable;
+using Content.Shared.Doors;
+using Content.Shared.Doors.Components;
+using Content.Shared.Wires;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.Defusable.WireActions;
+
+public sealed partial class DelayWireAction : ComponentWireAction<DefusableComponent>
+{
+    public override Color Color { get; set; } = Color.Yellow;
+    public override string Name { get; set; } = "wire-name-bomb-delay";
+    public override bool LightRequiresPower { get; set; } = false;
+
+    public override StatusLightState? GetLightState(Wire wire, DefusableComponent comp)
+    {
+        return comp.DelayWireUsed ? StatusLightState.On : StatusLightState.Off;
+    }
+
+    public override object StatusKey { get; } = DefusableWireStatus.DelayIndicator;
+
+    public override bool Cut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return true;
+    }
+
+    public override bool Mend(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return true;
+    }
+
+    public override void Pulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        EntityManager.System<DefusableSystem>().DelayWirePulse(user, wire, comp);
+    }
+}
diff --git a/Content.Server/Defusable/WireActions/ProceedWireAction.cs b/Content.Server/Defusable/WireActions/ProceedWireAction.cs
new file mode 100644 (file)
index 0000000..4c8fa87
--- /dev/null
@@ -0,0 +1,41 @@
+using Content.Server.Defusable.Components;
+using Content.Server.Defusable.Systems;
+using Content.Server.Doors.Systems;
+using Content.Server.Explosion.EntitySystems;
+using Content.Server.Popups;
+using Content.Server.Wires;
+using Content.Shared.Defusable;
+using Content.Shared.Doors;
+using Content.Shared.Doors.Components;
+using Content.Shared.Wires;
+
+namespace Content.Server.Defusable.WireActions;
+
+public sealed partial class ProceedWireAction : ComponentWireAction<DefusableComponent>
+{
+    public override Color Color { get; set; } = Color.Blue;
+    public override string Name { get; set; } = "wire-name-bomb-proceed";
+    public override bool LightRequiresPower { get; set; } = false;
+
+    public override StatusLightState? GetLightState(Wire wire, DefusableComponent comp)
+    {
+        return comp.Activated ? StatusLightState.Off : StatusLightState.BlinkingFast;
+    }
+
+    public override object StatusKey { get; } = DefusableWireStatus.ProceedIndicator;
+
+    public override bool Cut(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return EntityManager.System<DefusableSystem>().ProceedWireCut(user, wire, comp);
+    }
+
+    public override bool Mend(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        return true;
+    }
+
+    public override void Pulse(EntityUid user, Wire wire, DefusableComponent comp)
+    {
+        EntityManager.System<DefusableSystem>().ProceedWirePulse(user, wire, comp);
+    }
+}
index 427edf21e31969a3bcb80fc2781a28887b4e1f03..569417f1414174e311a7d391e6df4f545cc25b3f 100644 (file)
@@ -221,6 +221,14 @@ namespace Content.Server.Explosion.EntitySystems
             return triggerEvent.Handled;
         }
 
+        public void TryDelay(EntityUid uid, float amount, ActiveTimerTriggerComponent? comp = null)
+        {
+            if (!Resolve(uid, ref comp, false))
+                return;
+
+            comp.TimeRemaining += amount;
+        }
+
         public void HandleTimerTrigger(EntityUid uid, EntityUid? user, float delay , float beepInterval, float? initialBeepDelay, SoundSpecifier? beepSound)
         {
             if (delay <= 0)
diff --git a/Content.Shared/Defusable/SharedDefusableSystem.cs b/Content.Shared/Defusable/SharedDefusableSystem.cs
new file mode 100644 (file)
index 0000000..19c3314
--- /dev/null
@@ -0,0 +1,30 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Defusable;
+
+/// <summary>
+/// This handles defusable explosives, such as Syndicate Bombs.
+/// </summary>
+/// <remarks>
+/// Most of the logic is in the server
+/// </remarks>
+public abstract class SharedDefusableSystem : EntitySystem
+{
+
+}
+
+[NetSerializable, Serializable]
+public enum DefusableVisuals
+{
+    Active
+}
+
+[NetSerializable, Serializable]
+public enum DefusableWireStatus
+{
+    LiveIndicator,
+    BoltIndicator,
+    BoomIndicator,
+    DelayIndicator,
+    ProceedIndicator,
+}
diff --git a/Resources/Locale/en-US/defusable/examine.ftl b/Resources/Locale/en-US/defusable/examine.ftl
new file mode 100644 (file)
index 0000000..b077839
--- /dev/null
@@ -0,0 +1,8 @@
+defusable-examine-defused = {CAPITALIZE(THE($name))} is [color=lime]defused[/color].
+defusable-examine-live = {CAPITALIZE(THE($name))} is [color=red]ticking[/color] and has [color=red]{$time}[/color] seconds remaining.
+defusable-examine-live-display-off = {CAPITALIZE(THE($name))} is [color=red]ticking[/color], and the timer appears to be off.
+defusable-examine-inactive = {CAPITALIZE(THE($name))} is [color=lime]inactive[/color], but can still be armed.
+defusable-examine-bolts = The bolts are {$down ->
+[true] [color=red]down[/color]
+*[false] [color=green]up[/color]
+}.
diff --git a/Resources/Locale/en-US/defusable/popup.ftl b/Resources/Locale/en-US/defusable/popup.ftl
new file mode 100644 (file)
index 0000000..02cfca8
--- /dev/null
@@ -0,0 +1,10 @@
+defusable-popup-begun = {CAPITALIZE(THE($name))} beeps to life, it's light is on!
+defusable-popup-defuse = {CAPITALIZE(THE($name))} beeps one last time, as the light shuts off forever.
+defusable-popup-boom = {CAPITALIZE(THE($name))} roars as the internal bomb explodes!
+defusable-popup-fried = {CAPITALIZE(THE($name))} sparks, but fails to begin the countdown.
+defusable-popup-cant-anchor = {CAPITALIZE(THE($name))} appears to be bolted to the ground!
+
+defusable-popup-wire-bolt-pulse = The bolts spin in place for a moment.
+defusable-popup-wire-proceed-pulse = {CAPITALIZE(THE($name))} buzzes ominously!
+defusable-popup-wire-proceed-cut = The digital display on {THE($name)} deactivates.
+defusable-popup-wire-chirp = {CAPITALIZE(THE($name))} chirps.
diff --git a/Resources/Locale/en-US/defusable/verb.ftl b/Resources/Locale/en-US/defusable/verb.ftl
new file mode 100644 (file)
index 0000000..706d7b4
--- /dev/null
@@ -0,0 +1 @@
+defusable-verb-begin = Begin countdown
index 4fa42ecede1ce8d0ccdcb4ec79e6561bfffdb6fc..c03f1a8c575a5311c09aac773caac04e6b5edec1 100644 (file)
@@ -44,6 +44,8 @@ guide-entry-machine-upgrading = Machine Upgrading
 guide-entry-robotics = Robotics
 guide-entry-security = Security
 guide-entry-dna = DNA
+guide-entry-defusal = Large Bomb Defusal
+
 guide-entry-antagonists = Antagonists
 guide-entry-nuclear-operatives = Nuclear Operatives
 guide-entry-traitors = Traitors
index d8ad23b9605ac591df774d285bc40c4db07cddbf..0d8c547c8d727c652f33b47d34faacf1db555fec 100644 (file)
@@ -60,6 +60,9 @@ uplink-emp-grenade-desc = A grenade designed to disrupt electronic systems. Usef
 uplink-exploding-pen-name = Exploding pen
 uplink-exploding-pen-desc = A class IV explosive device contained within a standard pen. Comes with a 4 second fuse.
 
+uplink-exploding-syndicate-bomb-name = Syndicate Bomb
+uplink-exploding-syndicate-bomb-desc = A reliable bomb that can put a big hole in the station. Useful as a distraction.
+
 # Ammo
 uplink-pistol-magazine-name = Pistol Magazine (.35 auto)
 uplink-pistol-magazine-desc = Pistol magazine with 10 catridges. Compatible with the Viper.
index 8763a3e462b295238d107e01da1400b730890a2f..04bbca104e3983b139e480536af5af71d10ff2f2 100644 (file)
@@ -54,3 +54,8 @@ wire-name-pa-keyboard = KEYB
 wire-name-pa-limiter = LIMT
 wire-name-pa-power = POWR
 wire-name-pa-strength = STRC
+wire-name-bomb-live = LIVE
+wire-name-bomb-delay = DLAY
+wire-name-bomb-proceed = PRCD
+wire-name-bomb-boom = BOOM
+wire-name-bomb-bolt = BOLT
index 022e22781c3aaeb388fe3978d76055f368b806c2..5260f0b94b501803829b231e07a4213ac94ce6e0 100644 (file)
   categories:
   - UplinkExplosives
 
+- type: listing
+  id: UplinkSyndicateBomb
+  name: uplink-syndicate-bomb-name
+  description: uplink-syndicate-bomb-desc
+  productEntity: SyndicateBomb
+  cost:
+    Telecrystal: 11
+  categories:
+    - UplinkExplosives
+
 # Ammo
 
 - type: listing
diff --git a/Resources/Prototypes/Entities/Structures/Machines/bombs.yml b/Resources/Prototypes/Entities/Structures/Machines/bombs.yml
new file mode 100644 (file)
index 0000000..ba112ce
--- /dev/null
@@ -0,0 +1,156 @@
+- type: entity
+  abstract: true
+  parent: BaseStructure
+  id: BaseHardBomb
+  description: Just keep talking and nobody will explode.
+  name: hardbomb
+  components:
+    - type: Appearance
+    - type: WiresVisuals
+    - type: InteractionOutline
+    - type: UserInterface
+      interfaces:
+        - key: enum.WiresUiKey.Key
+          type: WiresBoundUserInterface
+    - type: Wires
+      LayoutId: Defusable
+      alwaysRandomize: true
+    - type: Defusable
+    - type: Explosive
+      explosionType: Default
+      totalIntensity: 20.0
+      intensitySlope: 5
+      maxIntensity: 4
+    - type: ExplodeOnTrigger
+    - type: OnUseTimerTrigger
+      delay: 90
+      delayOptions: [90, 120, 150, 180, 210, 240, 270, 300]
+      initialBeepDelay: 0
+      beepSound: /Audio/Machines/Nuke/general_beep.ogg
+    - type: Anchorable
+      delay: 5
+    - type: Physics
+      bodyType: Static
+    - type: Transform
+      noRot: true
+    - type: Fixtures
+      fixtures:
+        fix1:
+          shape:
+            !type:PhysShapeAabb
+            bounds: "-0.45,-0.45,0.45,0.45"
+          density: 190
+          mask:
+            - MachineMask
+          layer:
+            - MachineLayer
+    - type: WiresPanel
+    - type: Damageable
+      damageContainer: Inorganic
+      damageModifierSet: Metallic
+    - type: Destructible
+      thresholds:
+        - trigger:
+            !type:DamageTrigger
+            damage: 100
+          behaviors:
+            - !type:DoActsBehavior
+              acts: ["Destruction"]
+            - !type:ExplodeBehavior
+            - !type:PlaySoundBehavior
+              sound:
+                path: /Audio/Effects/metalbreak.ogg
+    - type: GuideHelp
+      openOnActivation: true
+      guides:
+        - Defusal
+
+- type: entity
+  parent: BaseHardBomb
+  id: TrainingBomb
+  name: training bomb
+  description: A bomb for dummies, manual not included.
+  components:
+    - type: Wires
+      LayoutId: TrainingDefusable
+      alwaysRandomize: true
+    - type: Sprite
+      sprite: Structures/Machines/bomb.rsi
+      layers:
+        - state: training-bomb
+        - state: training-bomb-active
+          visible: false
+          map: [ "primed" ]
+        - state: training-bomb-wires
+          visible: false
+          map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+    - type: GenericVisualizer
+      visuals:
+        enum.DefusableVisuals.Active:
+          primed:
+            True: { visible: true }
+            False: { visible: false }
+        enum.WiresVisualLayers.MaintenancePanel:
+          enum.WiresVisualLayers.MaintenancePanel:
+            True: { visible: true }
+            False: { visible: false }
+    - type: PointLight
+      color: "#0063C7"
+      radius: 1.1
+      softness: 1
+    - type: Explosive
+      explosionType: Default
+      totalIntensity: 5.0
+      intensitySlope: 5
+      maxIntensity: 4
+    - type: Defusable
+      disposable: false
+
+- type: entity
+  parent: BaseHardBomb
+  id: SyndicateBomb
+  name: syndicate bomb
+  description: A bomb for Syndicate operatives and agents alike. The real deal, no more training, get to it!
+  components:
+    - type: Sprite
+      sprite: Structures/Machines/bomb.rsi
+      layers:
+        - state: syndicate-bomb
+        - state: syndicate-bomb-active
+          visible: false
+          map: [ "primed" ]
+        - state: syndicate-bomb-wires
+          visible: false
+          map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
+    - type: GenericVisualizer
+      visuals:
+        enum.DefusableVisuals.Active:
+          primed:
+            True: { visible: true }
+            False: { visible: false }
+        enum.WiresVisualLayers.MaintenancePanel:
+          enum.WiresVisualLayers.MaintenancePanel:
+            True: { visible: true }
+            False: { visible: false }
+    - type: PointLight
+      color: "#C7001B"
+      radius: 1.1
+      softness: 1
+    - type: Explosive
+      explosionType: HardBomb
+      totalIntensity: 4000.0
+      intensitySlope: 3
+      maxIntensity: 400
+
+- type: entity
+  parent: SyndicateBomb
+  id: DebugHardBomb
+  name: debug bomb
+  suffix: DEBUG
+  description: Holy shit this is gonna explode
+  components:
+    - type: Defusable
+      disposable: true
+    - type: OnUseTimerTrigger
+      delay: 10
+      delayOptions: [10, 20, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300]
index c5e568ba8c97ce40e358c3b7c45e9d4162d25c3c..75fad71051aa6cfdfe993c4e46f3b05e501e67eb 100644 (file)
@@ -4,8 +4,14 @@
   text: "/ServerInfo/Guidebook/Security/Security.xml"
   children:
     - DNA
+    - Defusal
 
 - type: guideEntry
   id: DNA
   name: guide-entry-dna
   text: "/ServerInfo/Guidebook/Security/DNA.xml"
+
+- type: guideEntry
+  id: Defusal
+  name: guide-entry-defusal
+  text: "/ServerInfo/Guidebook/Security/Defusal.xml"
index a0e9fee95511be7dffdd15187f1291154beebcf8..c6145441b2e70c82c77f5a0a6ffbbee80274ba07 100644 (file)
   - !type:DoorBoltLightWireAction
   - !type:DoorTimingWireAction
   - !type:DoorSafetyWireAction
+
+- type: wireLayout
+  id: Defusable
+  dummyWires: 3
+  wires:
+    - !type:ActivateWireAction
+    - !type:BoltWireAction
+    - !type:DelayWireAction
+    - !type:ProceedWireAction
+    - !type:BoomWireAction
+    - !type:BoomWireAction
+    - !type:BoomWireAction
+
+- type: wireLayout
+  id: TrainingDefusable
+  dummyWires: 0
+  wires:
+    - !type:ActivateWireAction
+    - !type:BoltWireAction
+    - !type:DelayWireAction
+    - !type:ProceedWireAction
+    - !type:BoomWireAction
index de55aaa3f43d1b1fd966eecaf389f1fcffb0d194..6af2b4a39880bb58fb6c713a8a28f02c68aaca04 100644 (file)
   lightColor: Orange
   texturePath: /Textures/Effects/fire.rsi
   fireStates: 6
+
+- type: explosion
+  id: HardBomb
+  damagePerIntensity:
+    types:
+      Heat: 15
+      Blunt: 15
+      Piercing: 6
+      Structural: 15
+  tileBreakChance: [ 0.75, 0.95, 1 ]
+  tileBreakIntensity: [ 1, 10, 15 ]
+  tileBreakRerollReduction: 30
+  intensityPerState: 20
+  lightColor: Orange
+  texturePath: /Textures/Effects/fire.rsi
+  fireStates: 6
diff --git a/Resources/ServerInfo/Guidebook/Security/Defusal.xml b/Resources/ServerInfo/Guidebook/Security/Defusal.xml
new file mode 100644 (file)
index 0000000..63e1b03
--- /dev/null
@@ -0,0 +1,73 @@
+<Document>
+  # Large Bomb Defusal
+  So, you found a large bomb and it's beeping. These bombs take a long time to detonate and punch a big hole into the hull. Just keep reading, and nobody will explode.
+
+  ## Gear
+  You require two essential tools to perform defusal, however, a multitool is extremely helpful in terms of identifying wires.
+  <Box>
+    <GuideEntityEmbed Entity="Wirecutter"/>
+    <GuideEntityEmbed Entity="Screwdriver"/>
+    <GuideEntityEmbed Entity="Multitool"/>
+  </Box>
+
+  For protective equipment, a [color=yellow]bomb suit[/color] or any other protective equipment can assist you in not blowing into gibs.
+  <Box>
+    <GuideEntityEmbed Entity="ClothingHeadHelmetBombSuit"/>
+    <GuideEntityEmbed Entity="ClothingOuterSuitBomb"/>
+    <GuideEntityEmbed Entity="ClothingOuterHardsuitRd"/>
+    <GuideEntityEmbed Entity="ClothingOuterHardsuitAtmos"/>
+  </Box>
+
+  ## Hardbombs
+  Listed below are the two common types of bombs you will encounter while defusing. A training bomb will only provide minor hull damage and generally not kill you. A syndicate bomb however will punch a big hole into the hull, and gib you if you are not wearing protective gear.
+  <Box>
+    <GuideEntityEmbed Entity="SyndicateBomb"/>
+    <GuideEntityEmbed Entity="TrainingBomb"/>
+  </Box>
+
+  ## Arming
+  To arm a bomb, you can either [color=yellow]right click[/color] and click [color=yellow]Begin countdown[/click], or [color=yellow]alt-click[/color] the bomb. It will begin beeping.
+
+  ## Time
+  A bomb has a limited time, at a minimum of 90 and a maximum of 300. You can view the timer by examining it, unless the Proceed wire is cut. Once the timer hits zero, the bomb will detonate.
+
+  ## Bolts
+  By default, once armed, a bomb will bolt itself to the ground. You must find the BOLT wire and cut it to disable the bolts, after which you can unwrench it and throw it into space.
+
+  ## Wires
+  You must access the wiring in order to defuse a bomb. You can use a [color=yellow]screwdriver[/color] to open the access panel. Inside, you will find many types of wires. In a standard syndicate bomb, there are around [color=yellow]10 wires[/color], 3 are dummy wires, [color=red]3 will cause a detonation[/color], and the rest that weren't mentioned can be found below (alongside BOOM wires). With each wire, you can do 3 actions. You can:
+  - [color=yellow]Pulse the wire[/color] with a multitool, this can help you safely identify most wires.
+  - [color=red]Cut the wire[/color] with a wirecutter, this can trigger various effects, be cautious of cutting without reason!
+  - [color=green]Mend the wire[/color] with a wirecutter, this can restore some functionality of the bomb if it isn't disposable.
+
+  Onward for the types of wires.
+
+  ## Wire Types
+  [color=#a4885c]Activation Wire (LIVE)[/color]
+  - [color=yellow]Pulse the wire[/color]: Pulsing the wire will make the wire chirp and delay the bomb by 30 seconds.
+  - [color=red]Cut the wire[/color]: Cutting the wire will defuse the bomb if active, otherwise, will begin the timer.
+  - [color=green]Mend the wire[/color]: Nothing.
+
+  [color=#a4885c]Proceed Wire (PRCD)[/color]
+  - [color=yellow]Pulse the wire[/color]: Pulsing the wire will forward the time by 15 seconds.
+  - [color=red]Cut the wire[/color]: Cutting the wire will disable the timer display on examine.
+  - [color=green]Mend the wire[/color]: Nothing.
+
+  [color=#a4885c]Delay Wire (DLAY)[/color]
+  - [color=yellow]Pulse the wire[/color]: Pulsing the delay wire will delay the bomb by 30 seconds.
+  - [color=red]Cut the wire[/color]: Nothing.
+  - [color=green]Mend the wire[/color]: Nothing.
+
+  [color=#a4885c]Boom Wire (BOOM)[/color]
+  - [color=yellow]Pulse the wire[/color]: [color=red]The bomb will explode if armed![/color]
+  - [color=red]Cut the wire[/color]: [color=red]The bomb will explode if armed![/color] Otherwise, will disable the bomb.
+  - [color=green]Mend the wire[/color]: Re-enables the bomb if disabled previously.
+
+  [color=#a4885c]Bolt Wire (BOLT)[/color]
+  - [color=yellow]Pulse the wire[/color]: Pulsing the wire will make the bolts spin.
+  - [color=red]Cut the wire[/color]: Cutting the wire will disable the bolts, throw it into space!
+  - [color=green]Mend the wire[/color]: Mending the wire will re-enable the bolts.
+
+  [color=#a4885c]Dummy Wire[/color]
+  - Dummy wires don't do anything. You can pulse, cut, and mend them freely and they will not affect the bomb at all.
+</Document>
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-active.png b/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-active.png
new file mode 100644 (file)
index 0000000..d8e08c1
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-active.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-wires.png b/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-wires.png
new file mode 100644 (file)
index 0000000..4078163
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb-wires.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb.png b/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb.png
new file mode 100644 (file)
index 0000000..5da79eb
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/base-bomb.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/meta.json b/Resources/Textures/Structures/Machines/bomb.rsi/meta.json
new file mode 100644 (file)
index 0000000..3dbdee8
--- /dev/null
@@ -0,0 +1,59 @@
+{
+    "version": 1,
+    "license": "CC-BY-SA-3.0",
+    "copyright": "Taken from https://github.com/tgstation/tgstation/blob/fed8ed6d62de3003260303d021f6138bb90a60ce/icons/obj/assemblies/assemblies.dmi",
+    "size": {
+        "x": 32,
+        "y": 32
+    },
+    "states": [
+        {
+            "name": "base-bomb"
+        },
+        {
+            "name": "base-bomb-wires"
+        },
+        {
+            "name": "base-bomb-active",
+            "delays": [
+                [
+                    0.25,
+                    0.25,
+                    0.25
+                ]
+            ]
+        },
+        {
+            "name": "syndicate-bomb"
+        },
+        {
+            "name": "syndicate-bomb-wires"
+        },
+        {
+            "name": "syndicate-bomb-active",
+            "delays": [
+                [
+                    0.25,
+                    0.25,
+                    0.25
+                ]
+            ]
+        },
+        {
+            "name": "training-bomb"
+        },
+        {
+            "name": "training-bomb-wires"
+        },
+        {
+            "name": "training-bomb-active",
+            "delays": [
+                [
+                    0.25,
+                    0.25,
+                    0.25
+                ]
+            ]
+        }
+    ]
+}
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-active.png b/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-active.png
new file mode 100644 (file)
index 0000000..d8adb0f
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-active.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-wires.png b/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-wires.png
new file mode 100644 (file)
index 0000000..6512a21
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb-wires.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb.png b/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb.png
new file mode 100644 (file)
index 0000000..5a38a26
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/syndicate-bomb.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-active.png b/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-active.png
new file mode 100644 (file)
index 0000000..e1305f7
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-active.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-wires.png b/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-wires.png
new file mode 100644 (file)
index 0000000..ea27452
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb-wires.png differ
diff --git a/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb.png b/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb.png
new file mode 100644 (file)
index 0000000..d57728e
Binary files /dev/null and b/Resources/Textures/Structures/Machines/bomb.rsi/training-bomb.png differ