diff --git a/Content.Server/Nuke/Commands/SendNukeCodesCommand.cs b/Content.Server/Nuke/Commands/SendNukeCodesCommand.cs index b7e36a9911..e555f45f60 100644 --- a/Content.Server/Nuke/Commands/SendNukeCodesCommand.cs +++ b/Content.Server/Nuke/Commands/SendNukeCodesCommand.cs @@ -10,12 +10,22 @@ namespace Content.Server.Nuke.Commands public sealed class SendNukeCodesCommand : IConsoleCommand { public string Command => "nukecodes"; - public string Description => "Send nuke codes to the communication console"; - public string Help => "nukecodes"; + public string Description => "Send nuke codes to a station's communication consoles"; + public string Help => "nukecodes [station EntityUid]"; public void Execute(IConsoleShell shell, string argStr, string[] args) { - EntitySystem.Get().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().System().SendNukeCodes(uid); } } } diff --git a/Content.Server/Nuke/NukeCodePaperSystem.cs b/Content.Server/Nuke/NukeCodePaperSystem.cs index a99c42730a..d2df5cdf57 100644 --- a/Content.Server/Nuke/NukeCodePaperSystem.cs +++ b/Content.Server/Nuke/NukeCodePaperSystem.cs @@ -1,24 +1,90 @@ +using Content.Server.Chat.Systems; +using Content.Server.Communications; using Content.Server.Paper; +using Content.Server.Station.Components; +using Content.Server.Station.Systems; namespace Content.Server.Nuke { 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() { base.Initialize(); - SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnMapInit, + after: new []{ typeof(NukeLabelSystem) }); } private void OnMapInit(EntityUid uid, NukeCodePaperComponent component, MapInitEvent args) { - PaperComponent? paper = null; - if (!Resolve(uid, ref paper)) - return; + SetupPaper(uid); + } - 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()) + { + if (owningStation == null && nuke.OriginMapGrid != (transform.MapID, transform.GridUid) + || nuke.OriginStation != owningStation) + { + continue; + } + + paper.Content += $"{MetaData(nuke.Owner).EntityName} - {nuke.Code}"; + break; + } + } + + /// + /// Send a nuclear code to all communication consoles + /// + /// True if at least one console received codes + public bool SendNukeCodes(EntityUid station) + { + if (!HasComp(station)) + { + return false; + } + + // todo: this should probably be handled by fax system + var wasSent = false; + var consoles = EntityQuery(); + 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; } } } diff --git a/Content.Server/Nuke/NukeCodeSystem.cs b/Content.Server/Nuke/NukeCodeSystem.cs deleted file mode 100644 index ff08006849..0000000000 --- a/Content.Server/Nuke/NukeCodeSystem.cs +++ /dev/null @@ -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 -{ - /// - /// Nuclear code is generated once per round - /// One code works for all nukes - /// - 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(OnRestart); - } - - private void OnRestart(RoundRestartCleanupEvent ev) - { - GenerateNewCode(); - } - - /// - /// Checks if code is equal to current bombs code - /// - public bool IsCodeValid(string code) - { - return code == Code; - } - - /// - /// Generate a new nuclear bomb code. Replacing old one. - /// - public void GenerateNewCode() - { - var ret = ""; - for (var i = 0; i < CodeLength; i++) - { - var c = (char) _random.Next('0', '9' + 1); - ret += c; - } - - Code = ret; - } - - /// - /// Send a nuclear code to all communication consoles - /// - /// True if at least one console received codes - public bool SendNukeCodes() - { - // todo: this should probably be handled by fax system - var wasSent = false; - var consoles = EntityManager.EntityQuery(); - 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; - } - } -} diff --git a/Content.Server/Nuke/NukeComponent.cs b/Content.Server/Nuke/NukeComponent.cs index f4036f7063..d92b9c7b17 100644 --- a/Content.Server/Nuke/NukeComponent.cs +++ b/Content.Server/Nuke/NukeComponent.cs @@ -3,6 +3,7 @@ using Content.Shared.Containers.ItemSlots; using Content.Shared.Explosion; using Content.Shared.Nuke; using Robust.Shared.Audio; +using Robust.Shared.Map; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Server.Nuke @@ -121,6 +122,22 @@ namespace Content.Server.Nuke public bool Exploded; #endregion + /// + /// Origin station of this bomb, if it exists. + /// If this doesn't exist, then the origin grid and map will be filled in, instead. + /// + public EntityUid? OriginStation; + + /// + /// 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. + /// + public (MapId, EntityUid?)? OriginMapGrid; + + [DataField("codeLength")] public int CodeLength = 6; + [ViewVariables] public string Code = string.Empty; + /// /// Time until explosion in seconds. /// diff --git a/Content.Server/Nuke/NukeLabelComponent.cs b/Content.Server/Nuke/NukeLabelComponent.cs new file mode 100644 index 0000000000..9156747628 --- /dev/null +++ b/Content.Server/Nuke/NukeLabelComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Nuke; + +/// +/// This generates a label for a nuclear bomb. +/// +/// +/// This is a separate component because the fake nuclear bomb keg exists. +/// +[RegisterComponent] +public sealed class NukeLabelComponent : Component +{ + [DataField("prefix")] public string NukeLabel = "nuke-label-nanotrasen"; + [DataField("serialLength")] public int SerialLength = 6; +} diff --git a/Content.Server/Nuke/NukeLabelSystem.cs b/Content.Server/Nuke/NukeLabelSystem.cs new file mode 100644 index 0000000000..e47d16e077 --- /dev/null +++ b/Content.Server/Nuke/NukeLabelSystem.cs @@ -0,0 +1,21 @@ +namespace Content.Server.Nuke; + +/// +/// This handles labelling an entity with a nuclear bomb label. +/// +public sealed class NukeLabelSystem : EntitySystem +{ + [Dependency] private readonly NukeSystem _nuke = default!; + /// + public override void Initialize() + { + SubscribeLocalEvent(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})"; + } +} diff --git a/Content.Server/Nuke/NukeSystem.cs b/Content.Server/Nuke/NukeSystem.cs index df45d49edb..12169a061b 100644 --- a/Content.Server/Nuke/NukeSystem.cs +++ b/Content.Server/Nuke/NukeSystem.cs @@ -1,3 +1,4 @@ +using System.Text; using Content.Server.AlertLevel; using Content.Server.Audio; using Content.Server.Chat; @@ -17,13 +18,13 @@ using Content.Shared.Popups; using Robust.Shared.Audio; using Robust.Shared.Containers; using Robust.Shared.Player; +using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server.Nuke { public sealed class NukeSystem : EntitySystem { - [Dependency] private readonly NukeCodeSystem _codes = default!; [Dependency] private readonly ItemSlotsSystem _itemSlots = default!; [Dependency] private readonly PopupSystem _popups = default!; [Dependency] private readonly ExplosionSystem _explosions = default!; @@ -32,6 +33,7 @@ namespace Content.Server.Nuke [Dependency] private readonly ServerGlobalSoundSystem _soundSystem = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; /// /// 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(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnItemSlotChanged); SubscribeLocalEvent(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) { _itemSlots.RemoveItemSlot(uid, component.DiskSlot); @@ -185,7 +205,7 @@ namespace Content.Server.Nuke if (component.Status != NukeStatus.AWAIT_CODE) return; - if (component.EnteredCode.Length >= _codes.Code.Length) + if (component.EnteredCode.Length >= component.CodeLength) return; component.EnteredCode += args.Value.ToString(); @@ -309,8 +329,8 @@ namespace Content.Server.Nuke break; } - var isValid = _codes.IsCodeValid(component.EnteredCode); - if (isValid) + // var isValid = _codes.IsCodeValid(uid, component.EnteredCode); + if (component.EnteredCode == component.Code) { component.Status = NukeStatus.AWAIT_ARM; component.RemainingTime = component.Timer; @@ -358,7 +378,7 @@ namespace Content.Server.Nuke IsAnchored = anchored, AllowArm = allowArm, EnteredCodeLength = component.EnteredCode.Length, - MaxCodeLength = _codes.Code.Length, + MaxCodeLength = component.CodeLength, CooldownTime = (int) component.CooldownTime }; @@ -406,6 +426,18 @@ namespace Content.Server.Nuke 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 /// diff --git a/Resources/Locale/en-US/nuke/nuke-component.ftl b/Resources/Locale/en-US/nuke/nuke-component.ftl index 615aa70197..0624d2b6b3 100644 --- a/Resources/Locale/en-US/nuke/nuke-component.ftl +++ b/Resources/Locale/en-US/nuke/nuke-component.ftl @@ -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-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} diff --git a/Resources/Prototypes/Entities/Objects/Devices/nuke.yml b/Resources/Prototypes/Entities/Objects/Devices/nuke.yml index 54f5d8fbc3..bf8584d5ce 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/nuke.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/nuke.yml @@ -23,6 +23,7 @@ - MachineMask layer: - HalfWallLayer + - type: NukeLabel - type: Nuke explosionType: Default maxIntensity: 100 @@ -69,6 +70,7 @@ suffix: keg description: You probably shouldn't stick around to see if this is armed. It has a tap on the side. components: + - type: NukeLabel - type: Sprite sprite: Objects/Devices/nuke.rsi netsync: false