Add barebone nuke (#5242)
Co-authored-by: Alexander Evgrashin <evgrashin.adl@gmail.com>
This commit is contained in:
@@ -303,6 +303,8 @@ namespace Content.Client.Entry
|
||||
"ApcNetSwitch",
|
||||
"HandLabeler",
|
||||
"Label",
|
||||
"Nuke",
|
||||
"NukeCodePaper",
|
||||
"GhostRadio",
|
||||
"Armor",
|
||||
"PneumaticCannon"
|
||||
|
||||
79
Content.Client/Nuke/NukeBoundUserInterface.cs
Normal file
79
Content.Client/Nuke/NukeBoundUserInterface.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using Content.Client.Traitor.Uplink;
|
||||
using Content.Shared.Nuke;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Nuke
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class NukeBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private NukeMenu? _menu;
|
||||
|
||||
public NukeBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
_menu = new NukeMenu();
|
||||
_menu.OpenCentered();
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_menu.OnKeypadButtonPressed += i =>
|
||||
{
|
||||
SendMessage(new NukeKeypadMessage(i));
|
||||
};
|
||||
_menu.OnEnterButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new NukeKeypadEnterMessage());
|
||||
};
|
||||
_menu.OnClearButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new NukeKeypadClearMessage());
|
||||
};
|
||||
|
||||
_menu.EjectButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new NukeEjectMessage());
|
||||
};
|
||||
_menu.AnchorButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new NukeAnchorMessage());
|
||||
};
|
||||
_menu.ArmButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new NukeArmedMessage());
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case NukeUiState msg:
|
||||
{
|
||||
_menu.UpdateState(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Close();
|
||||
_menu?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Content.Client/Nuke/NukeMenu.xaml
Normal file
46
Content.Client/Nuke/NukeMenu.xaml
Normal file
@@ -0,0 +1,46 @@
|
||||
<SS14Window xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'nuke-user-interface-title'}"
|
||||
MinSize="256 256"
|
||||
SetSize="256 256">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<!-- First status label -->
|
||||
<PanelContainer Margin="0 0 0 5">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#001c00" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<Label Name="FirstStatusLabel"/>
|
||||
</PanelContainer>
|
||||
<!-- Second status label -->
|
||||
<PanelContainer Margin="0 0 0 5">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#001c00" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<Label Name="SecondStatusLabel"/>
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Horizontal" >
|
||||
<GridContainer Columns="3"
|
||||
Name="KeypadGrid">
|
||||
<!-- Keypad is filled by code -->
|
||||
</GridContainer>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
Margin="5 0">
|
||||
<Button Name="EjectButton"
|
||||
Text="{Loc 'nuke-user-interface-eject-button'}"
|
||||
Margin="0 0 0 5"
|
||||
Access="Public"/>
|
||||
<Button Name="AnchorButton"
|
||||
Text="{Loc 'nuke-user-interface-anchor-button'}"
|
||||
Margin="0 0 0 5"
|
||||
Access="Public"/>
|
||||
<Button Name="ArmButton"
|
||||
Text="{Loc 'nuke-user-interface-arm-button'}"
|
||||
Access="Public"
|
||||
StyleClasses="Caution"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</SS14Window>
|
||||
115
Content.Client/Nuke/NukeMenu.xaml.cs
Normal file
115
Content.Client/Nuke/NukeMenu.xaml.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using Content.Shared.Nuke;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.Nuke
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public partial class NukeMenu : SS14Window
|
||||
{
|
||||
public event Action<int>? OnKeypadButtonPressed;
|
||||
public event Action? OnClearButtonPressed;
|
||||
public event Action? OnEnterButtonPressed;
|
||||
|
||||
public NukeMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
FillKeypadGrid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill keypad buttons in keypad grid
|
||||
/// </summary>
|
||||
private void FillKeypadGrid()
|
||||
{
|
||||
// add 3 rows of keypad buttons (1-9)
|
||||
for (var i = 1; i <= 9; i++)
|
||||
{
|
||||
AddKeypadButton(i);
|
||||
}
|
||||
|
||||
// clear button
|
||||
var clearBtn = new Button()
|
||||
{
|
||||
Text = "C"
|
||||
};
|
||||
clearBtn.OnPressed += _ => OnClearButtonPressed?.Invoke();
|
||||
KeypadGrid.AddChild(clearBtn);
|
||||
|
||||
// zero button
|
||||
AddKeypadButton(0);
|
||||
|
||||
// enter button
|
||||
var enterBtn = new Button()
|
||||
{
|
||||
Text = "E"
|
||||
};
|
||||
enterBtn.OnPressed += _ => OnEnterButtonPressed?.Invoke();
|
||||
KeypadGrid.AddChild(enterBtn);
|
||||
}
|
||||
|
||||
private void AddKeypadButton(int i)
|
||||
{
|
||||
var btn = new Button()
|
||||
{
|
||||
Text = i.ToString()
|
||||
};
|
||||
|
||||
btn.OnPressed += _ => OnKeypadButtonPressed?.Invoke(i);
|
||||
KeypadGrid.AddChild(btn);
|
||||
}
|
||||
|
||||
public void UpdateState(NukeUiState state)
|
||||
{
|
||||
string firstMsg, secondMsg;
|
||||
switch (state.Status)
|
||||
{
|
||||
case NukeStatus.AWAIT_DISK:
|
||||
firstMsg = Loc.GetString("nuke-user-interface-first-status-device-locked");
|
||||
secondMsg = Loc.GetString("nuke-user-interface-second-status-await-disk");
|
||||
break;
|
||||
case NukeStatus.AWAIT_CODE:
|
||||
firstMsg = Loc.GetString("nuke-user-interface-first-status-input-code");
|
||||
secondMsg = Loc.GetString("nuke-user-interface-second-status-current-code",
|
||||
("code", VisualizeCode(state.EnteredCodeLength, state.MaxCodeLength)));
|
||||
break;
|
||||
case NukeStatus.AWAIT_ARM:
|
||||
firstMsg = Loc.GetString("nuke-user-interface-first-status-device-ready");
|
||||
secondMsg = Loc.GetString("nuke-user-interface-second-status-time",
|
||||
("time", state.RemainingTime));
|
||||
break;
|
||||
case NukeStatus.ARMED:
|
||||
firstMsg = Loc.GetString("nuke-user-interface-first-status-device-armed");
|
||||
secondMsg = Loc.GetString("nuke-user-interface-second-status-time",
|
||||
("time", state.RemainingTime));
|
||||
break;
|
||||
default:
|
||||
// shouldn't normally be here
|
||||
firstMsg = Loc.GetString("nuke-user-interface-status-error");
|
||||
secondMsg = Loc.GetString("nuke-user-interface-status-error");
|
||||
break;
|
||||
}
|
||||
|
||||
FirstStatusLabel.Text = firstMsg;
|
||||
SecondStatusLabel.Text = secondMsg;
|
||||
|
||||
EjectButton.Disabled = !state.DiskInserted;
|
||||
AnchorButton.Disabled = !state.DiskInserted;
|
||||
AnchorButton.Pressed = state.IsAnchored;
|
||||
ArmButton.Disabled = !state.AllowArm;
|
||||
}
|
||||
|
||||
private string VisualizeCode(int codeLength, int maxLength)
|
||||
{
|
||||
var code = new string('*', codeLength);
|
||||
var blanksCount = maxLength - codeLength;
|
||||
var blanks = new string('_', blanksCount);
|
||||
return code + blanks;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Content.Server/Nuke/Commands/SendNukeCodesCommand.cs
Normal file
22
Content.Server/Nuke/Commands/SendNukeCodesCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Nuke.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public class SendNukeCodesCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "nukecodes";
|
||||
public string Description => "Send nuke codes to the communication console";
|
||||
public string Help => "nukecodes";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<NukeCodeSystem>().SendNukeCodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Content.Server/Nuke/Commands/ToggleNukeCommand.cs
Normal file
63
Content.Server/Nuke/Commands/ToggleNukeCommand.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Server.Nuke.Commands
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
public class ToggleNukeCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "nukearm";
|
||||
public string Description => "Toggle nuclear bomb timer. You can set timer directly. Uid is optional.";
|
||||
public string Help => "nukearm <timer> <uid>";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntityUid bombUid;
|
||||
NukeComponent? bomb = null;
|
||||
|
||||
if (args.Length >= 2)
|
||||
{
|
||||
if (!EntityUid.TryParse(args[1], out bombUid))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var entManager = IoCManager.Resolve<IEntityManager>();
|
||||
var bombs = entManager.EntityQuery<NukeComponent>();
|
||||
|
||||
bomb = bombs.FirstOrDefault();
|
||||
if (bomb == null)
|
||||
{
|
||||
shell.WriteError("Can't find any entity with a NukeComponent");
|
||||
return;
|
||||
}
|
||||
|
||||
bombUid = bomb.OwnerUid;
|
||||
}
|
||||
|
||||
var nukeSys = EntitySystem.Get<NukeSystem>();
|
||||
if (args.Length >= 1)
|
||||
{
|
||||
if (!float.TryParse(args[0], out var timer))
|
||||
{
|
||||
shell.WriteError("shell-argument-must-be-number");
|
||||
return;
|
||||
}
|
||||
|
||||
nukeSys.SetRemainingTime(bombUid, timer, bomb);
|
||||
}
|
||||
|
||||
nukeSys.ToggleBomb(bombUid, bomb);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Content.Server/Nuke/NukeCodePaperComponent.cs
Normal file
14
Content.Server/Nuke/NukeCodePaperComponent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.Nuke
|
||||
{
|
||||
/// <summary>
|
||||
/// Paper with a written nuclear code in it.
|
||||
/// Can be used in mapping or admins spawn.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class NukeCodePaperComponent : Component
|
||||
{
|
||||
public override string Name => "NukeCodePaper";
|
||||
}
|
||||
}
|
||||
27
Content.Server/Nuke/NukeCodePaperSystem.cs
Normal file
27
Content.Server/Nuke/NukeCodePaperSystem.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Content.Server.Paper;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.Nuke
|
||||
{
|
||||
public class NukeCodePaperSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NukeCodeSystem _codes = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<NukeCodePaperComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, NukeCodePaperComponent component, MapInitEvent args)
|
||||
{
|
||||
PaperComponent? paper = null;
|
||||
if (!Resolve(uid, ref paper))
|
||||
return;
|
||||
|
||||
paper.Content += _codes.Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Content.Server/Nuke/NukeCodeSystem.cs
Normal file
88
Content.Server/Nuke/NukeCodeSystem.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Communications;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Nuke
|
||||
{
|
||||
/// <summary>
|
||||
/// Nuclear code is generated once per round
|
||||
/// One code works for all nukes
|
||||
/// </summary>
|
||||
public class NukeCodeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
|
||||
public const int CodeLength = 6;
|
||||
public string Code { get; private set; } = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
GenerateNewCode();
|
||||
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRestart);
|
||||
}
|
||||
|
||||
private void OnRestart(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
GenerateNewCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if code is equal to current bombs code
|
||||
/// </summary>
|
||||
public bool IsCodeValid(string code)
|
||||
{
|
||||
return code == Code;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new nuclear bomb code. Replacing old one.
|
||||
/// </summary>
|
||||
public void GenerateNewCode()
|
||||
{
|
||||
var ret = "";
|
||||
for (int i = 0; i < CodeLength; i++)
|
||||
{
|
||||
var c = (char) _random.Next('0', '9' + 1);
|
||||
ret += c;
|
||||
}
|
||||
|
||||
Code = ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a nuclear code to all communication consoles
|
||||
/// </summary>
|
||||
/// <returns>True if at least one console received codes</returns>
|
||||
public bool SendNukeCodes()
|
||||
{
|
||||
// todo: this should probably be handled by fax system
|
||||
var wasSent = false;
|
||||
var consoles = EntityManager.EntityQuery<CommunicationsConsoleComponent>();
|
||||
foreach (var console in consoles)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(console.OwnerUid, out TransformComponent? transform))
|
||||
continue;
|
||||
|
||||
var consolePos = transform.MapPosition;
|
||||
EntityManager.SpawnEntity("NukeCodePaper", consolePos);
|
||||
|
||||
wasSent = true;
|
||||
}
|
||||
|
||||
if (wasSent)
|
||||
{
|
||||
var msg = Loc.GetString("nuke-component-announcement-send-codes");
|
||||
_chat.DispatchStationAnnouncement(msg);
|
||||
}
|
||||
|
||||
return wasSent;
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Content.Server/Nuke/NukeComponent.cs
Normal file
101
Content.Server/Nuke/NukeComponent.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Nuke;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.Nuke
|
||||
{
|
||||
/// <summary>
|
||||
/// Nuclear device that can devistate an entire station.
|
||||
/// Basicaly a station self-destruction mechanism.
|
||||
/// To activate it, user needs to insert an authorization disk and enter a secret code.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Friend(typeof(NukeSystem))]
|
||||
public class NukeComponent : Component
|
||||
{
|
||||
public override string Name => "Nuke";
|
||||
|
||||
/// <summary>
|
||||
/// Default bomb timer value in seconds.
|
||||
/// </summary>
|
||||
[DataField("timer")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Timer = 180;
|
||||
|
||||
/// <summary>
|
||||
/// Slot name for to store nuclear disk inside bomb.
|
||||
/// See <see cref="SharedItemSlotsComponent"/> for mor info.
|
||||
/// </summary>
|
||||
[DataField("slot")]
|
||||
public string DiskSlotName = "DiskSlot";
|
||||
|
||||
/// <summary>
|
||||
/// Annihilation radius in which all human players will be gibed
|
||||
/// </summary>
|
||||
[DataField("blastRadius")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int BlastRadius = 200;
|
||||
|
||||
/// <summary>
|
||||
/// After this time nuke will play last alert sound
|
||||
/// </summary>
|
||||
[DataField("alertTime")]
|
||||
public float AlertSoundTime = 10.0f;
|
||||
|
||||
[DataField("keypadPressSound")]
|
||||
public SoundSpecifier KeypadPressSound = new SoundPathSpecifier("/Audio/Machines/Nuke/general_beep.ogg");
|
||||
|
||||
[DataField("accessGrantedSound")]
|
||||
public SoundSpecifier AccessGrantedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/general_beep.ogg");
|
||||
|
||||
[DataField("accessDeniedSound")]
|
||||
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/Nuke/angry_beep.ogg");
|
||||
|
||||
[DataField("alertSound")]
|
||||
public SoundSpecifier AlertSound = new SoundPathSpecifier("/Audio/Machines/alarm.ogg");
|
||||
|
||||
[DataField("armSound")]
|
||||
public SoundSpecifier ArmSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
|
||||
|
||||
[DataField("disarmSound")]
|
||||
public SoundSpecifier DisarmSound = new SoundPathSpecifier("/Audio/Misc/notice2.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Time until explosion in seconds.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float RemainingTime;
|
||||
|
||||
/// <summary>
|
||||
/// Does bomb contains valid entity inside <see cref="DiskSlotName"/>?
|
||||
/// If it is, user can anchor bomb or enter nuclear code to arm it.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool DiskInserted = false;
|
||||
|
||||
/// <summary>
|
||||
/// Curent nuclear code buffer. Entered manually by players.
|
||||
/// If valid it will allow arm/disarm bomb.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string EnteredCode = "";
|
||||
|
||||
/// <summary>
|
||||
/// Current status of a nuclear bomb.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public NukeStatus Status = NukeStatus.AWAIT_DISK;
|
||||
|
||||
/// <summary>
|
||||
/// Check if nuke has already played last alert sound
|
||||
/// </summary>
|
||||
public bool PlayedAlertSound = false;
|
||||
|
||||
public IPlayingAudioStream? AlertAudioStream = default;
|
||||
}
|
||||
}
|
||||
437
Content.Server/Nuke/NukeSystem.cs
Normal file
437
Content.Server/Nuke/NukeSystem.cs
Normal file
@@ -0,0 +1,437 @@
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Helpers;
|
||||
using Content.Shared.Nuke;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Coordinates.Helpers;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Nuke
|
||||
{
|
||||
public class NukeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly NukeCodeSystem _codes = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly SharedItemSlotsSystem _itemSlots = default!;
|
||||
[Dependency] private readonly PopupSystem _popups = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _tickingBombs = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<NukeComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<NukeComponent, ComponentRemove>(OnRemove);
|
||||
SubscribeLocalEvent<NukeComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<NukeComponent, ItemSlotChangedEvent>(OnItemSlotChanged);
|
||||
|
||||
// anchoring logic
|
||||
SubscribeLocalEvent<NukeComponent, AnchorAttemptEvent>(OnAnchorAttempt);
|
||||
SubscribeLocalEvent<NukeComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||
SubscribeLocalEvent<NukeComponent, AnchoredEvent>(OnWasAnchored);
|
||||
SubscribeLocalEvent<NukeComponent, UnanchoredEvent>(OnWasUnanchored);
|
||||
|
||||
// ui events
|
||||
SubscribeLocalEvent<NukeComponent, NukeEjectMessage>(OnEjectButtonPressed);
|
||||
SubscribeLocalEvent<NukeComponent, NukeAnchorMessage>(OnAnchorButtonPressed);
|
||||
SubscribeLocalEvent<NukeComponent, NukeArmedMessage>(OnArmButtonPressed);
|
||||
SubscribeLocalEvent<NukeComponent, NukeKeypadMessage>(OnKeypadButtonPressed);
|
||||
SubscribeLocalEvent<NukeComponent, NukeKeypadClearMessage>(OnClearButtonPressed);
|
||||
SubscribeLocalEvent<NukeComponent, NukeKeypadEnterMessage>(OnEnterButtonPressed);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, NukeComponent component, ComponentInit args)
|
||||
{
|
||||
component.RemainingTime = component.Timer;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
foreach (var uid in _tickingBombs)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NukeComponent nuke))
|
||||
continue;
|
||||
|
||||
nuke.RemainingTime -= frameTime;
|
||||
|
||||
// play alert sound if time is running out
|
||||
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
|
||||
{
|
||||
nuke.AlertAudioStream = SoundSystem.Play(Filter.Broadcast(), nuke.AlertSound.GetSound());
|
||||
nuke.PlayedAlertSound = true;
|
||||
}
|
||||
|
||||
if (nuke.RemainingTime <= 0)
|
||||
{
|
||||
nuke.RemainingTime = 0;
|
||||
ActivateBomb(uid, nuke);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateUserInterface(uid, nuke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args)
|
||||
{
|
||||
_tickingBombs.Remove(uid);
|
||||
}
|
||||
|
||||
private void OnItemSlotChanged(EntityUid uid, NukeComponent component, ItemSlotChangedEvent args)
|
||||
{
|
||||
if (args.SlotName != component.DiskSlotName)
|
||||
return;
|
||||
|
||||
component.DiskInserted = args.ContainedItem != null;
|
||||
UpdateStatus(uid, component);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnActivate(EntityUid uid, NukeComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// standard interactions check
|
||||
if (!args.InRangeUnobstructed())
|
||||
return;
|
||||
if (!_actionBlocker.CanInteract(args.User.Uid) || !_actionBlocker.CanUse(args.User.Uid))
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(args.User.Uid, out ActorComponent? actor))
|
||||
return;
|
||||
|
||||
ShowUI(uid, actor.PlayerSession, component);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#region Anchor
|
||||
private void OnAnchorAttempt(EntityUid uid, NukeComponent component, AnchorAttemptEvent args)
|
||||
{
|
||||
CheckAnchorAttempt(uid, component, args);
|
||||
}
|
||||
|
||||
private void OnUnanchorAttempt(EntityUid uid, NukeComponent component, UnanchorAttemptEvent args)
|
||||
{
|
||||
CheckAnchorAttempt(uid, component, args);
|
||||
}
|
||||
|
||||
private void CheckAnchorAttempt(EntityUid uid, NukeComponent component, BaseAnchoredAttemptEvent args)
|
||||
{
|
||||
// cancel any anchor attempt without nuke disk
|
||||
if (!component.DiskInserted)
|
||||
{
|
||||
var msg = Loc.GetString("nuke-component-cant-anchor");
|
||||
_popups.PopupEntity(msg, uid, Filter.Entities(args.User));
|
||||
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWasUnanchored(EntityUid uid, NukeComponent component, UnanchoredEvent args)
|
||||
{
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnWasAnchored(EntityUid uid, NukeComponent component, AnchoredEvent args)
|
||||
{
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region UI Events
|
||||
private void OnEjectButtonPressed(EntityUid uid, NukeComponent component, NukeEjectMessage args)
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
return;
|
||||
|
||||
_itemSlots.TryEjectContent(uid, component.DiskSlotName, args.Session.AttachedEntity);
|
||||
}
|
||||
|
||||
private async void OnAnchorButtonPressed(EntityUid uid, NukeComponent component, NukeAnchorMessage args)
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
return;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out TransformComponent? transform))
|
||||
return;
|
||||
|
||||
// manually set transform anchor (bypassing anchorable)
|
||||
// todo: it will break pullable system
|
||||
transform.Coordinates = transform.Coordinates.SnapToGrid();
|
||||
transform.Anchored = !transform.Anchored;
|
||||
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnEnterButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadEnterMessage args)
|
||||
{
|
||||
if (component.Status != NukeStatus.AWAIT_CODE)
|
||||
return;
|
||||
|
||||
UpdateStatus(uid, component);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnKeypadButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadMessage args)
|
||||
{
|
||||
PlaydSound(uid, component.KeypadPressSound, 0.125f, component);
|
||||
|
||||
if (component.Status != NukeStatus.AWAIT_CODE)
|
||||
return;
|
||||
|
||||
if (component.EnteredCode.Length >= _codes.Code.Length)
|
||||
return;
|
||||
|
||||
component.EnteredCode += args.Value.ToString();
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnClearButtonPressed(EntityUid uid, NukeComponent component, NukeKeypadClearMessage args)
|
||||
{
|
||||
PlaydSound(uid, component.KeypadPressSound, 0f, component);
|
||||
|
||||
if (component.Status != NukeStatus.AWAIT_CODE)
|
||||
return;
|
||||
|
||||
component.EnteredCode = "";
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void OnArmButtonPressed(EntityUid uid, NukeComponent component, NukeArmedMessage args)
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
return;
|
||||
|
||||
if (component.Status == NukeStatus.AWAIT_ARM)
|
||||
{
|
||||
ArmBomb(uid, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisarmBomb(uid, component);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void UpdateStatus(EntityUid uid, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
switch (component.Status)
|
||||
{
|
||||
case NukeStatus.AWAIT_DISK:
|
||||
if (component.DiskInserted)
|
||||
component.Status = NukeStatus.AWAIT_CODE;
|
||||
break;
|
||||
case NukeStatus.AWAIT_CODE:
|
||||
{
|
||||
if (!component.DiskInserted)
|
||||
{
|
||||
component.Status = NukeStatus.AWAIT_DISK;
|
||||
component.EnteredCode = "";
|
||||
break;
|
||||
}
|
||||
|
||||
var isValid = _codes.IsCodeValid(component.EnteredCode);
|
||||
if (isValid)
|
||||
{
|
||||
component.Status = NukeStatus.AWAIT_ARM;
|
||||
component.RemainingTime = component.Timer;
|
||||
PlaydSound(uid, component.AccessGrantedSound, 0, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.EnteredCode = "";
|
||||
PlaydSound(uid, component.AccessDeniedSound, 0, component);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NukeStatus.AWAIT_ARM:
|
||||
// do nothing, wait for arm button to be pressed
|
||||
break;
|
||||
case NukeStatus.ARMED:
|
||||
// do nothing, wait for arm button to be unpressed
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowUI(EntityUid uid, IPlayerSession session, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var ui = component.Owner.GetUIOrNull(NukeUiKey.Key);
|
||||
ui?.Open(session);
|
||||
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(EntityUid uid, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
var ui = component.Owner.GetUIOrNull(NukeUiKey.Key);
|
||||
if (ui == null)
|
||||
return;
|
||||
|
||||
var anchored = false;
|
||||
if (EntityManager.TryGetComponent(uid, out TransformComponent transform))
|
||||
anchored = transform.Anchored;
|
||||
|
||||
var allowArm = component.DiskInserted &&
|
||||
(component.Status == NukeStatus.AWAIT_ARM ||
|
||||
component.Status == NukeStatus.ARMED);
|
||||
|
||||
var state = new NukeUiState()
|
||||
{
|
||||
Status = component.Status,
|
||||
RemainingTime = (int) component.RemainingTime,
|
||||
DiskInserted = component.DiskInserted,
|
||||
IsAnchored = anchored,
|
||||
AllowArm = allowArm,
|
||||
EnteredCodeLength = component.EnteredCode.Length,
|
||||
MaxCodeLength = _codes.Code.Length
|
||||
};
|
||||
|
||||
ui.SetState(state);
|
||||
}
|
||||
|
||||
private void PlaydSound(EntityUid uid, SoundSpecifier sound, float varyPitch = 0f,
|
||||
NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
SoundSystem.Play(Filter.Pvs(uid), sound.GetSound(),
|
||||
uid, AudioHelpers.WithVariation(varyPitch));
|
||||
}
|
||||
|
||||
#region Public API
|
||||
/// <summary>
|
||||
/// Force a nuclear bomb to start a countdown timer
|
||||
/// </summary>
|
||||
public void ArmBomb(EntityUid uid, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.Status == NukeStatus.ARMED)
|
||||
return;
|
||||
|
||||
// warn a crew
|
||||
var announcement = Loc.GetString("nuke-component-announcement-armed",
|
||||
("time", (int) component.RemainingTime));
|
||||
var sender = Loc.GetString("nuke-component-announcement-sender");
|
||||
_chat.DispatchStationAnnouncement(announcement, sender);
|
||||
|
||||
// todo: move it to announcements system
|
||||
SoundSystem.Play(Filter.Broadcast(), component.ArmSound.GetSound());
|
||||
|
||||
component.Status = NukeStatus.ARMED;
|
||||
_tickingBombs.Add(uid);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop nuclear bomb timer
|
||||
/// </summary>
|
||||
public void DisarmBomb(EntityUid uid, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.Status != NukeStatus.ARMED)
|
||||
return;
|
||||
|
||||
// warn a crew
|
||||
var announcement = Loc.GetString("nuke-component-announcement-unarmed");
|
||||
var sender = Loc.GetString("nuke-component-announcement-sender");
|
||||
_chat.DispatchStationAnnouncement(announcement, sender);
|
||||
|
||||
// todo: move it to announcements system
|
||||
SoundSystem.Play(Filter.Broadcast(), component.DisarmSound.GetSound());
|
||||
|
||||
// disable sound and reset it
|
||||
component.PlayedAlertSound = false;
|
||||
component.AlertAudioStream?.Stop();
|
||||
|
||||
component.Status = NukeStatus.AWAIT_ARM;
|
||||
_tickingBombs.Remove(uid);
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle bomb arm button
|
||||
/// </summary>
|
||||
public void ToggleBomb(EntityUid uid, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.Status == NukeStatus.ARMED)
|
||||
DisarmBomb(uid, component);
|
||||
else
|
||||
ArmBomb(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force bomb to explode immediately
|
||||
/// </summary>
|
||||
public void ActivateBomb(EntityUid uid, NukeComponent? component = null,
|
||||
TransformComponent? transform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, ref transform))
|
||||
return;
|
||||
|
||||
// gib anyone in a blast radius
|
||||
// its lame, but will work for now
|
||||
var pos = transform.Coordinates;
|
||||
var ents = _lookup.GetEntitiesInRange(pos, component.BlastRadius);
|
||||
foreach (var ent in ents)
|
||||
{
|
||||
var entUid = ent.Uid;
|
||||
if (!EntityManager.EntityExists(entUid))
|
||||
continue;;
|
||||
|
||||
if (EntityManager.TryGetComponent(entUid, out SharedBodyComponent? body))
|
||||
body.Gib();
|
||||
}
|
||||
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set remaining time value
|
||||
/// </summary>
|
||||
public void SetRemainingTime(EntityUid uid, float timer, NukeComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.RemainingTime = timer;
|
||||
UpdateUserInterface(uid, component);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Content.Server.Paper
|
||||
{
|
||||
private PaperAction _mode;
|
||||
[DataField("content")]
|
||||
public string Content { get; private set; } = "";
|
||||
public string Content { get; set; } = "";
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PaperUiKey.Key);
|
||||
|
||||
|
||||
42
Content.Shared/Nuke/NukeUiMessages.cs
Normal file
42
Content.Shared/Nuke/NukeUiMessages.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Nuke
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NukeEjectMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NukeAnchorMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NukeKeypadMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public int Value;
|
||||
|
||||
public NukeKeypadMessage(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NukeKeypadClearMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NukeKeypadEnterMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class NukeArmedMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
}
|
||||
32
Content.Shared/Nuke/SharedNuke.cs
Normal file
32
Content.Shared/Nuke/SharedNuke.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Nuke
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public enum NukeUiKey : byte
|
||||
{
|
||||
Key
|
||||
}
|
||||
|
||||
public enum NukeStatus : byte
|
||||
{
|
||||
AWAIT_DISK,
|
||||
AWAIT_CODE,
|
||||
AWAIT_ARM,
|
||||
ARMED
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public class NukeUiState : BoundUserInterfaceState
|
||||
{
|
||||
public bool DiskInserted;
|
||||
public NukeStatus Status;
|
||||
public int RemainingTime;
|
||||
public bool IsAnchored;
|
||||
public int EnteredCodeLength;
|
||||
public int MaxCodeLength;
|
||||
public bool AllowArm;
|
||||
}
|
||||
}
|
||||
BIN
Resources/Audio/Machines/Nuke/angry_beep.ogg
Normal file
BIN
Resources/Audio/Machines/Nuke/angry_beep.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Machines/Nuke/confirm_beep.ogg
Normal file
BIN
Resources/Audio/Machines/Nuke/confirm_beep.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Machines/Nuke/general_beep.ogg
Normal file
BIN
Resources/Audio/Machines/Nuke/general_beep.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Machines/alarm.ogg
Normal file
BIN
Resources/Audio/Machines/alarm.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Machines/terminal_insert_disc.ogg
Normal file
BIN
Resources/Audio/Machines/terminal_insert_disc.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Misc/notice1.ogg
Normal file
BIN
Resources/Audio/Misc/notice1.ogg
Normal file
Binary file not shown.
BIN
Resources/Audio/Misc/notice2.ogg
Normal file
BIN
Resources/Audio/Misc/notice2.ogg
Normal file
Binary file not shown.
27
Resources/Locale/en-US/nuke/nuke-component.ftl
Normal file
27
Resources/Locale/en-US/nuke/nuke-component.ftl
Normal file
@@ -0,0 +1,27 @@
|
||||
nuke-component-cant-anchor = The bolts seems to be blocked without disk!
|
||||
nuke-component-announcement-sender = Nuclear Fission Explosive
|
||||
nuke-component-announcement-armed = Attention! The station's self-destruct mechanism has been engaged. {$time} seconds until detonation.
|
||||
nuke-component-announcement-unarmed = The station's self-destruct was deactivated! Have a nice day!
|
||||
nuke-component-announcement-send-codes = Attention! Requested self-destruction codes was sent to communication consoles.
|
||||
|
||||
# Nuke UI
|
||||
nuke-user-interface-title = Nuclear Fission Explosive
|
||||
nuke-user-interface-arm-button = ARM
|
||||
nuke-user-interface-anchor-button = ANCHOR
|
||||
nuke-user-interface-eject-button = EJECT
|
||||
|
||||
## Upper status
|
||||
nuke-user-interface-first-status-device-locked = DEVICE LOCKED
|
||||
nuke-user-interface-first-status-input-code = INPUT CODE
|
||||
nuke-user-interface-first-status-input-time = INPUT TIME
|
||||
nuke-user-interface-first-status-device-ready = DEVICE READY
|
||||
nuke-user-interface-first-status-device-armed = DEVICE ARMED
|
||||
nuke-user-interface-status-error = ERROR
|
||||
|
||||
## Lower status
|
||||
nuke-user-interface-second-status-await-disk = AWAIT DISK
|
||||
nuke-user-interface-second-status-time = TIME: {$time}
|
||||
nuke-user-interface-second-status-current-code = CODE: {$code}
|
||||
|
||||
|
||||
|
||||
40
Resources/Prototypes/Entities/Objects/Devices/nuke.yml
Normal file
40
Resources/Prototypes/Entities/Objects/Devices/nuke.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
- type: entity
|
||||
parent: BaseStructureDynamic
|
||||
id: NuclearBomb
|
||||
name: nuclear fission explosive
|
||||
description: You probably shouldn't stick around to see if this is armed.
|
||||
components:
|
||||
- type: Transform
|
||||
anchored: true
|
||||
- type: Sprite
|
||||
sprite: Objects/Devices/nuke.rsi
|
||||
netsync: false
|
||||
state: nuclearbomb_base
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
fixtures:
|
||||
- shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.45
|
||||
mass: 150
|
||||
layer:
|
||||
- SmallImpassable
|
||||
mask:
|
||||
- VaultImpassable
|
||||
- type: Nuke
|
||||
- type: InteractionOutline
|
||||
- type: ItemSlots
|
||||
slots:
|
||||
DiskSlot:
|
||||
name: Disk
|
||||
insertSound:
|
||||
path: /Audio/Machines/terminal_insert_disc.ogg
|
||||
ejectSound:
|
||||
path: /Audio/Machines/terminal_insert_disc.ogg
|
||||
whitelist:
|
||||
tags:
|
||||
- NukeDisk
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
- key: enum.NukeUiKey.Key
|
||||
type: NukeBoundUserInterface
|
||||
@@ -36,6 +36,17 @@
|
||||
# something happened, so that ought to override this either way.
|
||||
- state: paper_words
|
||||
|
||||
- type: entity
|
||||
parent: PaperWritten
|
||||
id: NukeCodePaper
|
||||
name: nuclear authentication codes
|
||||
components:
|
||||
- type: NukeCodePaper
|
||||
- type: Paper
|
||||
content: |
|
||||
[color=red]TOP SECRET![/color]
|
||||
Nuclear device activation code:
|
||||
|
||||
- type: entity
|
||||
name: pen
|
||||
parent: BaseItem
|
||||
|
||||
23
Resources/Textures/Objects/Devices/nuke.rsi/meta.json
Normal file
23
Resources/Textures/Objects/Devices/nuke.rsi/meta.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/commit/59f2a4e10e5ba36033c9734ddebfbbdc6157472d",
|
||||
"states": [
|
||||
{
|
||||
"name": "nuclearbomb_base"
|
||||
},
|
||||
{
|
||||
"name": "nuclearbomb_exploding"
|
||||
},
|
||||
{
|
||||
"name": "nuclearbomb_safetyoff"
|
||||
},
|
||||
{
|
||||
"name": "nuclearbomb_timing"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/Objects/Devices/nuke.rsi/nuclearbomb_base.png
Normal file
BIN
Resources/Textures/Objects/Devices/nuke.rsi/nuclearbomb_base.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 801 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 819 B |
Binary file not shown.
|
After Width: | Height: | Size: 939 B |
Reference in New Issue
Block a user