Hardbombs & Defusables (#15380)

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>
This commit is contained in:
eclips_e
2023-09-12 09:42:38 +08:00
committed by GitHub
parent 75bf7bc6e8
commit 133ca98655
31 changed files with 1124 additions and 0 deletions

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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)

View File

@@ -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,
}

View File

@@ -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]
}.

View File

@@ -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.

View File

@@ -0,0 +1 @@
defusable-verb-begin = Begin countdown

View 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

View 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.

View 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

View File

@@ -239,6 +239,16 @@
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

View File

@@ -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]

View 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"

View File

@@ -96,3 +96,25 @@
- !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

View File

@@ -101,3 +101,19 @@
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

View File

@@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

View File

@@ -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
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B