Gives all nuclear bombs unique codes (#11665)

Co-authored-by: Kara <lunarautomaton6@gmail.com>
This commit is contained in:
Flipp Syder
2022-10-09 12:28:08 -07:00
committed by GitHub
parent 410983c2d4
commit 8838f8be24
9 changed files with 181 additions and 102 deletions

View File

@@ -10,12 +10,22 @@ namespace Content.Server.Nuke.Commands
public sealed class SendNukeCodesCommand : IConsoleCommand public sealed class SendNukeCodesCommand : IConsoleCommand
{ {
public string Command => "nukecodes"; public string Command => "nukecodes";
public string Description => "Send nuke codes to the communication console"; public string Description => "Send nuke codes to a station's communication consoles";
public string Help => "nukecodes"; public string Help => "nukecodes [station EntityUid]";
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
EntitySystem.Get<NukeCodeSystem>().SendNukeCodes(); if (args.Length != 1)
{
shell.WriteError("shell-need-exactly-one-argument");
}
if (!EntityUid.TryParse(args[0], out var uid))
{
shell.WriteError(Loc.GetString("shell-entity-uid-must-be-number"));
}
IoCManager.Resolve<EntityManager>().System<NukeCodePaperSystem>().SendNukeCodes(uid);
} }
} }
} }

View File

@@ -1,24 +1,90 @@
using Content.Server.Chat.Systems;
using Content.Server.Communications;
using Content.Server.Paper; using Content.Server.Paper;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
namespace Content.Server.Nuke namespace Content.Server.Nuke
{ {
public sealed class NukeCodePaperSystem : EntitySystem public sealed class NukeCodePaperSystem : EntitySystem
{ {
[Dependency] private readonly NukeCodeSystem _codes = default!; [Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly StationSystem _station = default!;
private const string NukePaperPrototype = "NukeCodePaper";
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<NukeCodePaperComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<NukeCodePaperComponent, MapInitEvent>(OnMapInit,
after: new []{ typeof(NukeLabelSystem) });
} }
private void OnMapInit(EntityUid uid, NukeCodePaperComponent component, MapInitEvent args) private void OnMapInit(EntityUid uid, NukeCodePaperComponent component, MapInitEvent args)
{ {
PaperComponent? paper = null; SetupPaper(uid);
if (!Resolve(uid, ref paper)) }
return;
paper.Content += _codes.Code; private void SetupPaper(EntityUid uid, EntityUid? station = null, PaperComponent? paper = null)
{
if (!Resolve(uid, ref paper))
{
return;
}
var owningStation = station ?? _station.GetOwningStation(uid);
var transform = Transform(uid);
// Find the first nuke that matches the paper's location.
foreach (var nuke in EntityQuery<NukeComponent>())
{
if (owningStation == null && nuke.OriginMapGrid != (transform.MapID, transform.GridUid)
|| nuke.OriginStation != owningStation)
{
continue;
}
paper.Content += $"{MetaData(nuke.Owner).EntityName} - {nuke.Code}";
break;
}
}
/// <summary>
/// Send a nuclear code to all communication consoles
/// </summary>
/// <returns>True if at least one console received codes</returns>
public bool SendNukeCodes(EntityUid station)
{
if (!HasComp<StationDataComponent>(station))
{
return false;
}
// todo: this should probably be handled by fax system
var wasSent = false;
var consoles = EntityQuery<CommunicationsConsoleComponent, TransformComponent>();
foreach (var (console, transform) in consoles)
{
var owningStation = _station.GetOwningStation(console.Owner);
if (owningStation == null || owningStation != station)
{
continue;
}
var consolePos = transform.MapPosition;
var uid = Spawn(NukePaperPrototype, consolePos);
SetupPaper(uid, station);
wasSent = true;
}
if (wasSent)
{
var msg = Loc.GetString("nuke-component-announcement-send-codes");
_chatSystem.DispatchStationAnnouncement(station, msg, colorOverride: Color.Red);
}
return wasSent;
} }
} }
} }

View File

@@ -1,88 +0,0 @@
using Content.Server.Chat;
using Content.Server.Chat.Systems;
using Content.Server.Communications;
using Content.Server.Station.Systems;
using Content.Shared.GameTicking;
using Robust.Shared.Random;
namespace Content.Server.Nuke
{
/// <summary>
/// Nuclear code is generated once per round
/// One code works for all nukes
/// </summary>
public sealed class NukeCodeSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
private 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 (var 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).Owner, out TransformComponent? transform))
continue;
var consolePos = transform.MapPosition;
EntityManager.SpawnEntity("NukeCodePaper", consolePos);
wasSent = true;
}
// TODO: Allow selecting a station for nuke codes
if (wasSent)
{
var msg = Loc.GetString("nuke-component-announcement-send-codes");
_chatSystem.DispatchGlobalAnnouncement(msg, colorOverride: Color.Red);
}
return wasSent;
}
}
}

View File

@@ -3,6 +3,7 @@ using Content.Shared.Containers.ItemSlots;
using Content.Shared.Explosion; using Content.Shared.Explosion;
using Content.Shared.Nuke; using Content.Shared.Nuke;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Nuke namespace Content.Server.Nuke
@@ -121,6 +122,22 @@ namespace Content.Server.Nuke
public bool Exploded; public bool Exploded;
#endregion #endregion
/// <summary>
/// Origin station of this bomb, if it exists.
/// If this doesn't exist, then the origin grid and map will be filled in, instead.
/// </summary>
public EntityUid? OriginStation;
/// <summary>
/// Origin map and grid of this bomb.
/// If a station wasn't tied to a given grid when the bomb was spawned,
/// this will be filled in instead.
/// </summary>
public (MapId, EntityUid?)? OriginMapGrid;
[DataField("codeLength")] public int CodeLength = 6;
[ViewVariables] public string Code = string.Empty;
/// <summary> /// <summary>
/// Time until explosion in seconds. /// Time until explosion in seconds.
/// </summary> /// </summary>

View File

@@ -0,0 +1,14 @@
namespace Content.Server.Nuke;
/// <summary>
/// This generates a label for a nuclear bomb.
/// </summary>
/// <remarks>
/// This is a separate component because the fake nuclear bomb keg exists.
/// </remarks>
[RegisterComponent]
public sealed class NukeLabelComponent : Component
{
[DataField("prefix")] public string NukeLabel = "nuke-label-nanotrasen";
[DataField("serialLength")] public int SerialLength = 6;
}

View File

@@ -0,0 +1,21 @@
namespace Content.Server.Nuke;
/// <summary>
/// This handles labelling an entity with a nuclear bomb label.
/// </summary>
public sealed class NukeLabelSystem : EntitySystem
{
[Dependency] private readonly NukeSystem _nuke = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<NukeLabelComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, NukeLabelComponent nuke, MapInitEvent args)
{
var label = Loc.GetString(nuke.NukeLabel, ("serial", _nuke.GenerateRandomNumberString(nuke.SerialLength)));
var meta = MetaData(uid);
meta.EntityName = $"{meta.EntityName} ({label})";
}
}

View File

@@ -1,3 +1,4 @@
using System.Text;
using Content.Server.AlertLevel; using Content.Server.AlertLevel;
using Content.Server.Audio; using Content.Server.Audio;
using Content.Server.Chat; using Content.Server.Chat;
@@ -17,13 +18,13 @@ using Content.Shared.Popups;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Server.Nuke namespace Content.Server.Nuke
{ {
public sealed class NukeSystem : EntitySystem public sealed class NukeSystem : EntitySystem
{ {
[Dependency] private readonly NukeCodeSystem _codes = default!;
[Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!;
[Dependency] private readonly PopupSystem _popups = default!; [Dependency] private readonly PopupSystem _popups = default!;
[Dependency] private readonly ExplosionSystem _explosions = default!; [Dependency] private readonly ExplosionSystem _explosions = default!;
@@ -32,6 +33,7 @@ namespace Content.Server.Nuke
[Dependency] private readonly ServerGlobalSoundSystem _soundSystem = default!; [Dependency] private readonly ServerGlobalSoundSystem _soundSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
/// <summary> /// <summary>
/// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx /// Used to calculate when the nuke song should start playing for maximum kino with the nuke sfx
@@ -48,6 +50,7 @@ namespace Content.Server.Nuke
base.Initialize(); base.Initialize();
SubscribeLocalEvent<NukeComponent, ComponentInit>(OnInit); SubscribeLocalEvent<NukeComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<NukeComponent, ComponentRemove>(OnRemove); SubscribeLocalEvent<NukeComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<NukeComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<NukeComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged); SubscribeLocalEvent<NukeComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
SubscribeLocalEvent<NukeComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged); SubscribeLocalEvent<NukeComponent, EntRemovedFromContainerMessage>(OnItemSlotChanged);
@@ -97,6 +100,23 @@ namespace Content.Server.Nuke
} }
} }
private void OnMapInit(EntityUid uid, NukeComponent nuke, MapInitEvent args)
{
var originStation = _stationSystem.GetOwningStation(uid);
if (originStation != null)
{
nuke.OriginStation = originStation;
}
else
{
var transform = Transform(uid);
nuke.OriginMapGrid = (transform.MapID, transform.GridUid);
}
nuke.Code = GenerateRandomNumberString(nuke.CodeLength);
}
private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args) private void OnRemove(EntityUid uid, NukeComponent component, ComponentRemove args)
{ {
_itemSlots.RemoveItemSlot(uid, component.DiskSlot); _itemSlots.RemoveItemSlot(uid, component.DiskSlot);
@@ -185,7 +205,7 @@ namespace Content.Server.Nuke
if (component.Status != NukeStatus.AWAIT_CODE) if (component.Status != NukeStatus.AWAIT_CODE)
return; return;
if (component.EnteredCode.Length >= _codes.Code.Length) if (component.EnteredCode.Length >= component.CodeLength)
return; return;
component.EnteredCode += args.Value.ToString(); component.EnteredCode += args.Value.ToString();
@@ -309,8 +329,8 @@ namespace Content.Server.Nuke
break; break;
} }
var isValid = _codes.IsCodeValid(component.EnteredCode); // var isValid = _codes.IsCodeValid(uid, component.EnteredCode);
if (isValid) if (component.EnteredCode == component.Code)
{ {
component.Status = NukeStatus.AWAIT_ARM; component.Status = NukeStatus.AWAIT_ARM;
component.RemainingTime = component.Timer; component.RemainingTime = component.Timer;
@@ -358,7 +378,7 @@ namespace Content.Server.Nuke
IsAnchored = anchored, IsAnchored = anchored,
AllowArm = allowArm, AllowArm = allowArm,
EnteredCodeLength = component.EnteredCode.Length, EnteredCodeLength = component.EnteredCode.Length,
MaxCodeLength = _codes.Code.Length, MaxCodeLength = component.CodeLength,
CooldownTime = (int) component.CooldownTime CooldownTime = (int) component.CooldownTime
}; };
@@ -406,6 +426,18 @@ namespace Content.Server.Nuke
Filter.Pvs(uid), uid, AudioHelpers.WithVariation(varyPitch).WithVolume(-5f)); Filter.Pvs(uid), uid, AudioHelpers.WithVariation(varyPitch).WithVolume(-5f));
} }
public string GenerateRandomNumberString(int length)
{
var ret = "";
for (var i = 0; i < length; i++)
{
var c = (char) _random.Next('0', '9' + 1);
ret += c;
}
return ret;
}
#region Public API #region Public API
/// <summary> /// <summary>

View File

@@ -26,4 +26,9 @@ nuke-user-interface-second-status-time = TIME: {$time}
nuke-user-interface-second-status-current-code = CODE: {$code} nuke-user-interface-second-status-current-code = CODE: {$code}
nuke-user-interface-second-status-cooldown-time = WAIT: {$time} nuke-user-interface-second-status-cooldown-time = WAIT: {$time}
## Nuke labels
nuke-label-nanotrasen = NT-{$serial}
# do you even need this one? It's more funnier to say that
# the Syndicate stole a NT nuke
nuke-label-syndicate = SYN-{$serial}

View File

@@ -23,6 +23,7 @@
- MachineMask - MachineMask
layer: layer:
- HalfWallLayer - HalfWallLayer
- type: NukeLabel
- type: Nuke - type: Nuke
explosionType: Default explosionType: Default
maxIntensity: 100 maxIntensity: 100
@@ -69,6 +70,7 @@
suffix: keg suffix: keg
description: You probably shouldn't stick around to see if this is armed. It has a tap on the side. description: You probably shouldn't stick around to see if this is armed. It has a tap on the side.
components: components:
- type: NukeLabel
- type: Sprite - type: Sprite
sprite: Objects/Devices/nuke.rsi sprite: Objects/Devices/nuke.rsi
netsync: false netsync: false