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",
|
"ApcNetSwitch",
|
||||||
"HandLabeler",
|
"HandLabeler",
|
||||||
"Label",
|
"Label",
|
||||||
|
"Nuke",
|
||||||
|
"NukeCodePaper",
|
||||||
"GhostRadio",
|
"GhostRadio",
|
||||||
"Armor",
|
"Armor",
|
||||||
"PneumaticCannon"
|
"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;
|
private PaperAction _mode;
|
||||||
[DataField("content")]
|
[DataField("content")]
|
||||||
public string Content { get; private set; } = "";
|
public string Content { get; set; } = "";
|
||||||
|
|
||||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(PaperUiKey.Key);
|
[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.
|
# something happened, so that ought to override this either way.
|
||||||
- state: paper_words
|
- 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
|
- type: entity
|
||||||
name: pen
|
name: pen
|
||||||
parent: BaseItem
|
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