diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 3c353700b9..5f1bb06fe1 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -1,6 +1,9 @@ using System.Linq; +using Content.Server.CharacterAppearance.Components; using Content.Server.Chat.Managers; using Content.Server.Nuke; +using Content.Server.Players; +using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Spawners.Components; using Content.Server.Station.Components; @@ -12,7 +15,6 @@ using Robust.Server.Maps; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Map; -using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; @@ -36,6 +38,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem public override string Prototype => "Nukeops"; + private const string NukeopsPrototypeId = "Nukeops"; + private const string NukeopsCommanderPrototypeId = "NukeopsCommander"; + public override void Initialize() { base.Initialize(); @@ -91,16 +96,93 @@ public sealed class NukeopsRuleSystem : GameRuleSystem _aliveNukeops.Clear(); - // Between 1 and : needs at least n players per op. - var numOps = Math.Max(1, - (int)Math.Min( - Math.Floor((double)ev.PlayerPool.Count / _cfg.GetCVar(CCVars.NukeopsPlayersPerOp)), _cfg.GetCVar(CCVars.NukeopsMaxOps))); - var ops = new IPlayerSession[numOps]; - for (var i = 0; i < numOps; i++) + // Basically copied verbatim from traitor code + var playersPerOperative = _cfg.GetCVar(CCVars.NukeopsPlayersPerOp); + var maxOperatives = _cfg.GetCVar(CCVars.NukeopsMaxOps); + + var everyone = new List(ev.PlayerPool); + var prefList = new List(); + var cmdrPrefList = new List(); + var operatives = new List(); + + // The LINQ expression ReSharper keeps suggesting is completely unintelligible so I'm disabling it + // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator + foreach (var player in everyone) { - ops[i] = _random.PickAndTake(ev.PlayerPool); + if(player.ContentData()?.Mind?.AllRoles.All(role => role is not Job {CanBeAntag: false}) ?? false) continue; + if (!ev.Profiles.ContainsKey(player.UserId)) + { + continue; + } + var profile = ev.Profiles[player.UserId]; + if (profile.AntagPreferences.Contains(NukeopsPrototypeId)) + { + prefList.Add(player); + } + if (profile.AntagPreferences.Contains(NukeopsCommanderPrototypeId)) + { + cmdrPrefList.Add(player); + } } + var numNukies = MathHelper.Clamp(ev.PlayerPool.Count / playersPerOperative, 1, maxOperatives); + + for (var i = 0; i < numNukies; i++) + { + IPlayerSession nukeOp; + // Only one commander, so we do it at the start + if (i == 0) + { + if (cmdrPrefList.Count == 0) + { + if (prefList.Count == 0) + { + if (everyone.Count == 0) + { + Logger.InfoS("preset", "Insufficient ready players to fill up with nukeops, stopping the selection"); + break; + } + nukeOp = _random.PickAndTake(everyone); + Logger.InfoS("preset", "Insufficient preferred nukeop commanders or nukies, picking at random."); + } + else + { + nukeOp = _random.PickAndTake(prefList); + everyone.Remove(nukeOp); + Logger.InfoS("preset", "Insufficient preferred nukeop commanders, picking at random from regular op list."); + } + } + else + { + nukeOp = _random.PickAndTake(cmdrPrefList); + everyone.Remove(nukeOp); + prefList.Remove(nukeOp); + Logger.InfoS("preset", "Selected a preferred nukeop commander."); + } + } + else + { + if (prefList.Count == 0) + { + if (everyone.Count == 0) + { + Logger.InfoS("preset", "Insufficient ready players to fill up with nukeops, stopping the selection"); + break; + } + nukeOp = _random.PickAndTake(everyone); + Logger.InfoS("preset", "Insufficient preferred nukeops, picking at random."); + } + else + { + nukeOp = _random.PickAndTake(prefList); + everyone.Remove(nukeOp); + Logger.InfoS("preset", "Selected a preferred nukeop."); + } + } + operatives.Add(nukeOp); + } + + // TODO: Make this a prototype var map = "/Maps/infiltrator.yml"; var aabbs = _stationSystem.Stations.SelectMany(x => @@ -119,7 +201,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem if (!gridId.HasValue) { Logger.ErrorS("NUKEOPS", $"Gridid was null when loading \"{map}\", aborting."); - foreach (var session in ops) + foreach (var session in operatives) { ev.PlayerPool.Add(session); } @@ -149,7 +231,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem } // TODO: This should spawn the nukies in regardless and transfer if possible; rest should go to shot roles. - for (var i = 0; i < ops.Length; i++) + for (var i = 0; i < operatives.Count; i++) { string name; StartingGearPrototype gear; @@ -170,7 +252,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem break; } - var session = ops[i]; + var session = operatives[i]; var newMind = new Mind.Mind(session.UserId) { CharacterName = name @@ -179,6 +261,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem var mob = EntityManager.SpawnEntity("MobHuman", _random.Pick(spawns)); EntityManager.GetComponent(mob).EntityName = name; + EntityManager.AddComponent(mob); newMind.TransferTo(mob); _stationSpawningSystem.EquipStartingGear(mob, gear, null); diff --git a/Resources/Prototypes/Roles/Antags/nukeops.yml b/Resources/Prototypes/Roles/Antags/nukeops.yml new file mode 100644 index 0000000000..506b7dabec --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/nukeops.yml @@ -0,0 +1,13 @@ +- type: antag + id: Nukeops + name: "Nuclear Operative" + antagonist: true + setPreference: true + objective: "Find the nuke disk and blow up the station." + +- type: antag + id: NukeopsCommander + name: "Nuclear Operative Commander" + antagonist: true + setPreference: true + objective: "Lead your team to the destruction of the station."