From d02e2dad2666fd10f7e44de23bdae2e6f24e6248 Mon Sep 17 00:00:00 2001 From: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com> Date: Wed, 20 Jul 2022 05:46:23 -0400 Subject: [PATCH] Antag menu (#9900) * Refactor traitor generation code. * RandomTraitorAlive no longer crashes when 1 traitor. Also cleaner/faster * Add Antag menu for admins, add Traitor to the list. * Add zombie to admin-antag menu * Pirates, lone op, make traitor consistent with the rest. * Add name strings * cleaned usings. * Cleanup. Co-authored-by: drakewill Co-authored-by: moonheart08 --- .../Systems/AdminVerbSystem.Antags.cs | 106 +++++++++++ .../Administration/Systems/AdminVerbSystem.cs | 1 + .../GameTicking/Rules/NukeopsRuleSystem.cs | 11 ++ .../GameTicking/Rules/PiratesRuleSystem.cs | 10 +- .../GameTicking/Rules/TraitorRuleSystem.cs | 177 ++++++++++-------- .../Conditions/RandomTraitorAliveCondition.cs | 15 +- Content.Shared/Verbs/VerbCategory.cs | 3 + .../Locale/en-US/administration/antag.ftl | 5 + .../VerbIcons/antag-e_sword-temp.192dpi.png | Bin 0 -> 1061 bytes 9 files changed, 236 insertions(+), 92 deletions(-) create mode 100644 Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs create mode 100644 Resources/Locale/en-US/administration/antag.ftl create mode 100644 Resources/Textures/Interface/VerbIcons/antag-e_sword-temp.192dpi.png diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs new file mode 100644 index 0000000000..f320da3de3 --- /dev/null +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -0,0 +1,106 @@ +using Content.Server.GameTicking.Rules; +using Content.Server.Mind.Components; +using Content.Server.Zombies; +using Content.Shared.Administration; +using Content.Shared.Database; +using Content.Shared.Verbs; +using Robust.Server.GameObjects; +using Robust.Shared.Configuration; + +namespace Content.Server.Administration.Systems; + +public sealed partial class AdminVerbSystem +{ + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly ZombifyOnDeathSystem _zombify = default!; + [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; + [Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!; + [Dependency] private readonly PiratesRuleSystem _piratesRule = default!; + + // All antag verbs have names so invokeverb works. + private void AddAntagVerbs(GetVerbsEvent args) + { + if (!EntityManager.TryGetComponent(args.User, out var actor)) + return; + + var player = actor.PlayerSession; + + if (!_adminManager.HasAdminFlag(player, AdminFlags.Fun)) + return; + + var targetHasMind = TryComp(args.Target, out MindComponent? targetMindComp); + if (!targetHasMind || targetMindComp == null) + return; + + Verb traitor = new() + { + Text = "Make Traitor", + Category = VerbCategory.Antag, + IconTexture = "/Textures/Structures/Wallmounts/posters.rsi/poster5_contraband.png", + Act = () => + { + if (targetMindComp.Mind == null || targetMindComp.Mind.Session == null) + return; + + _traitorRule.MakeTraitor(targetMindComp.Mind.Session); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-traitor"), + }; + args.Verbs.Add(traitor); + + Verb zombie = new() + { + Text = "Make Zombie", + Category = VerbCategory.Antag, + IconTexture = "/Textures/Structures/Wallmounts/signs.rsi/bio.png", + Act = () => + { + TryComp(args.Target, out MindComponent? mindComp); + if (mindComp == null || mindComp.Mind == null) + return; + + _zombify.ZombifyEntity(targetMindComp.Owner); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-zombie"), + }; + args.Verbs.Add(zombie); + + + Verb nukeOp = new() + { + Text = "Make nuclear operative", + Category = VerbCategory.Antag, + IconTexture = "/Textures/Structures/Wallmounts/signs.rsi/radiation.png", + Act = () => + { + if (targetMindComp.Mind == null || targetMindComp.Mind.Session == null) + return; + + _nukeopsRule.MakeLoneNukie(targetMindComp.Mind); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-nuclear-operative"), + }; + args.Verbs.Add(nukeOp); + + Verb pirate = new() + { + Text = "Make Pirate", + Category = VerbCategory.Antag, + IconTexture = "/Textures/Clothing/Head/Hats/pirate.rsi/icon.png", + Act = () => + { + if (targetMindComp.Mind == null || targetMindComp.Mind.Session == null) + return; + + _piratesRule.MakePirate(targetMindComp.Mind); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-pirate"), + }; + args.Verbs.Add(pirate); + + } +} diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs index 45e2ddcfc3..975d7f9151 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs @@ -53,6 +53,7 @@ namespace Content.Server.Administration.Systems SubscribeLocalEvent>(AddAdminVerbs); SubscribeLocalEvent>(AddDebugVerbs); SubscribeLocalEvent>(AddSmiteVerbs); + SubscribeLocalEvent>(AddAntagVerbs); SubscribeLocalEvent(Reset); SubscribeLocalEvent(OnSolutionChanged); } diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 7862cf051e..35801b5596 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -23,6 +23,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; using Content.Server.Traitor; +using System.Data; namespace Content.Server.GameTicking.Rules; @@ -287,6 +288,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem } } + //For admins forcing someone to nukeOps. + public void MakeLoneNukie(Mind.Mind mind) + { + if (!mind.OwnedEntity.HasValue) + return; + + mind.AddRole(new TraitorRole(mind, _prototypeManager.Index(NukeopsPrototypeId))); + _stationSpawningSystem.EquipStartingGear(mind.OwnedEntity.Value, _prototypeManager.Index("SyndicateOperativeGearFull"), null); + } + private void OnStartAttempt(RoundStartAttemptEvent ev) { if (!RuleAdded) diff --git a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs index 00494e88b2..e533f966a7 100644 --- a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs @@ -189,7 +189,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem if (spawns.Count == 0) { spawns.Add(Transform(_pirateShip).Coordinates); - Logger.WarningS("pirates", $"Fell back to default spawn for nukies!"); + Logger.WarningS("pirates", $"Fell back to default spawn for pirates!"); } for (var i = 0; i < ops.Length; i++) @@ -223,6 +223,14 @@ public sealed class PiratesRuleSystem : GameRuleSystem }); // Include the players in the appraisal. } + //Forcing one player to be a pirate. + public void MakePirate(Mind.Mind mind) + { + if (!mind.OwnedEntity.HasValue) + return; + _stationSpawningSystem.EquipStartingGear(mind.OwnedEntity.Value, _prototypeManager.Index("PirateGear"), null); + } + private void OnStartAttempt(RoundStartAttemptEvent ev) { if (!RuleAdded) diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 73d28ec244..575cbae5cd 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using Content.Server.Chat.Managers; using Content.Server.GameTicking.Rules.Configurations; @@ -34,11 +35,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem public override string Prototype => "Traitor"; private readonly SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg"); - private readonly List _traitors = new (); + public List Traitors = new(); private const string TraitorPrototypeID = "Traitor"; - public int TotalTraitors => _traitors.Count; + public int TotalTraitors => Traitors.Count; + public string[] Codewords = new string[3]; public override void Initialize() { @@ -49,15 +51,16 @@ public sealed class TraitorRuleSystem : GameRuleSystem SubscribeLocalEvent(OnRoundEndText); } - public override void Started() {} + public override void Started(){} public override void Ended() { - _traitors.Clear(); + Traitors.Clear(); } private void OnStartAttempt(RoundStartAttemptEvent ev) { + MakeCodewords(); if (!RuleAdded) return; @@ -84,6 +87,21 @@ public sealed class TraitorRuleSystem : GameRuleSystem } } + private void MakeCodewords() + { + + var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount); + var adjectives = _prototypeManager.Index("adjectives").Values; + var verbs = _prototypeManager.Index("verbs").Values; + var codewordPool = adjectives.Concat(verbs).ToList(); + var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count); + Codewords = new string[finalCodewordCount]; + for (var i = 0; i < finalCodewordCount; i++) + { + Codewords[i] = _random.PickAndTake(codewordPool); + } + } + private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) { if (!RuleAdded) @@ -91,13 +109,19 @@ public sealed class TraitorRuleSystem : GameRuleSystem var playersPerTraitor = _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor); var maxTraitors = _cfg.GetCVar(CCVars.TraitorMaxTraitors); - var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount); - var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance); - var maxDifficulty = _cfg.GetCVar(CCVars.TraitorMaxDifficulty); - var maxPicks = _cfg.GetCVar(CCVars.TraitorMaxPicks); + var numTraitors = MathHelper.Clamp(ev.Players.Length / playersPerTraitor, 1, maxTraitors); + var traitorPool = FindPotentialTraitors(ev); + var selectedTraitors = PickTraitors(numTraitors, traitorPool); + + foreach (var traitor in selectedTraitors) + MakeTraitor(traitor); + } + + public List FindPotentialTraitors(RulePlayerJobsAssignedEvent ev) + { var list = new List(ev.Players).Where(x => - x.Data.ContentData()?.Mind?.AllRoles.All(role => role is not Job {CanBeAntag: false}) ?? false + x.Data.ContentData()?.Mind?.AllRoles.All(role => role is not Job { CanBeAntag: false }) ?? false ).ToList(); var prefList = new List(); @@ -114,85 +138,77 @@ public sealed class TraitorRuleSystem : GameRuleSystem prefList.Add(player); } } - - var numTraitors = MathHelper.Clamp(ev.Players.Length / playersPerTraitor, - 1, maxTraitors); - - for (var i = 0; i < numTraitors; i++) + if (prefList.Count == 0) { - IPlayerSession traitor; - if(prefList.Count == 0) - { - if (list.Count == 0) - { - Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection."); - break; - } - traitor = _random.PickAndTake(list); - Logger.InfoS("preset", "Insufficient preferred traitors, picking at random."); - } - else - { - traitor = _random.PickAndTake(prefList); - list.Remove(traitor); - Logger.InfoS("preset", "Selected a preferred traitor."); - } - var mind = traitor.Data.ContentData()?.Mind; - if (mind == null) - { - Logger.ErrorS("preset", "Failed getting mind for picked traitor."); - continue; - } + Logger.InfoS("preset", "Insufficient preferred traitors, picking at random."); + prefList = list; + } + return prefList; + } - // creadth: we need to create uplink for the antag. - // PDA should be in place already, so we just need to - // initiate uplink account. - DebugTools.AssertNotNull(mind.OwnedEntity); + public List PickTraitors(int traitorCount, List prefList) + { + var results = new List(traitorCount); + if (prefList.Count == 0) + { + Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection."); + return results; + } + + for (var i = 0; i < traitorCount; i++) + { + results.Add(_random.PickAndTake(prefList)); + Logger.InfoS("preset", "Selected a preferred traitor."); + } + return results; + } - var uplinkAccount = new UplinkAccount(startingBalance, mind.OwnedEntity!); - var accounts = EntityManager.EntitySysManager.GetEntitySystem(); - accounts.AddNewAccount(uplinkAccount); - - if (!EntityManager.EntitySysManager.GetEntitySystem() - .AddUplink(mind.OwnedEntity!.Value, uplinkAccount)) - continue; - - var antagPrototype = _prototypeManager.Index(TraitorPrototypeID); - var traitorRole = new TraitorRole(mind, antagPrototype); - mind.AddRole(traitorRole); - _traitors.Add(traitorRole); + public bool MakeTraitor(IPlayerSession traitor) + { + var mind = traitor.Data.ContentData()?.Mind; + if (mind == null) + { + Logger.ErrorS("preset", "Failed getting mind for picked traitor."); + return false; } - var adjectives = _prototypeManager.Index("adjectives").Values; - var verbs = _prototypeManager.Index("verbs").Values; + // creadth: we need to create uplink for the antag. + // PDA should be in place already, so we just need to + // initiate uplink account. + DebugTools.AssertNotNull(mind.OwnedEntity); - var codewordPool = adjectives.Concat(verbs).ToList(); - var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count); - var codewords = new string[finalCodewordCount]; - for (var i = 0; i < finalCodewordCount; i++) + var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance); + var uplinkAccount = new UplinkAccount(startingBalance, mind.OwnedEntity!); + var accounts = EntityManager.EntitySysManager.GetEntitySystem(); + accounts.AddNewAccount(uplinkAccount); + + if (!EntityManager.EntitySysManager.GetEntitySystem().AddUplink(mind.OwnedEntity!.Value, uplinkAccount)) + return false; + + var antagPrototype = _prototypeManager.Index(TraitorPrototypeID); + var traitorRole = new TraitorRole(mind, antagPrototype); + mind.AddRole(traitorRole); + Traitors.Add(traitorRole); + traitorRole.GreetTraitor(Codewords); + + var maxDifficulty = _cfg.GetCVar(CCVars.TraitorMaxDifficulty); + var maxPicks = _cfg.GetCVar(CCVars.TraitorMaxPicks); + + //give traitors their objectives + var difficulty = 0f; + for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++) { - codewords[i] = _random.PickAndTake(codewordPool); + var objective = _objectivesManager.GetRandomObjective(traitorRole.Mind); + if (objective == null) continue; + if (traitorRole.Mind.TryAddObjective(objective)) + difficulty += objective.Difficulty; } - foreach (var traitor in _traitors) - { - traitor.GreetTraitor(codewords); + //give traitors their codewords to keep in their character info menu + traitorRole.Mind.Briefing = Loc.GetString("traitor-role-codewords", ("codewords", string.Join(", ", Codewords))); - //give traitors their objectives - var difficulty = 0f; - for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++) - { - var objective = _objectivesManager.GetRandomObjective(traitor.Mind); - if (objective == null) continue; - if (traitor.Mind.TryAddObjective(objective)) - difficulty += objective.Difficulty; - } - - //give traitors their codewords to keep in their character info menu - traitor.Mind.Briefing = Loc.GetString("traitor-role-codewords", ("codewords", string.Join(", ",codewords))); - } - - SoundSystem.Play(_addedSound.GetSound(), Filter.Empty().AddWhere(s => ((IPlayerSession)s).Data.ContentData()?.Mind?.HasRole() ?? false), AudioParams.Default); + SoundSystem.Play(_addedSound.GetSound(), Filter.Empty().AddPlayer(traitor), AudioParams.Default); + return true; } private void OnRoundEndText(RoundEndTextAppendEvent ev) @@ -200,9 +216,9 @@ public sealed class TraitorRuleSystem : GameRuleSystem if (!RuleAdded) return; - var result = Loc.GetString("traitor-round-end-result", ("traitorCount", _traitors.Count)); + var result = Loc.GetString("traitor-round-end-result", ("traitorCount", Traitors.Count)); - foreach (var traitor in _traitors) + foreach (var traitor in Traitors) { var name = traitor.Mind.CharacterName; traitor.Mind.TryGetSession(out var session); @@ -264,7 +280,6 @@ public sealed class TraitorRuleSystem : GameRuleSystem } } } - ev.AddLine(result); } } diff --git a/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs b/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs index 5f33e1d960..c20c203b7c 100644 --- a/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs +++ b/Content.Server/Objectives/Conditions/RandomTraitorAliveCondition.cs @@ -3,6 +3,8 @@ using Content.Server.Objectives.Interfaces; using Robust.Shared.Random; using Robust.Shared.Utility; using Content.Server.Traitor; +using Content.Server.Mind; +using Content.Server.GameTicking.Rules; namespace Content.Server.Objectives.Conditions { @@ -14,17 +16,10 @@ namespace Content.Server.Objectives.Conditions public IObjectiveCondition GetAssigned(Mind.Mind mind) { var entityMgr = IoCManager.Resolve(); - var allOtherTraitors = new List(); + var traitors = EntitySystem.Get().Traitors; - foreach (var targetMind in entityMgr.EntityQuery()) - { - if (targetMind.Mind?.CharacterDeadIC == false && targetMind.Mind != mind && targetMind.Mind?.HasRole() == true) - { - allOtherTraitors.Add(targetMind.Mind); - } - } - - return new RandomTraitorAliveCondition {_target = IoCManager.Resolve().Pick(allOtherTraitors)}; + if (traitors.Count == 0) return new RandomTraitorAliveCondition { _target = mind }; //You were made a traitor by admins, and are the first/only. + return new RandomTraitorAliveCondition { _target = IoCManager.Resolve().Pick(traitors).Mind }; } public string Title diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs index 53a05cacc5..59f3b69206 100644 --- a/Content.Shared/Verbs/VerbCategory.cs +++ b/Content.Shared/Verbs/VerbCategory.cs @@ -38,6 +38,9 @@ namespace Content.Shared.Verbs public static readonly VerbCategory Admin = new("verb-categories-admin", "/Textures/Interface/character.svg.192dpi.png"); + public static readonly VerbCategory Antag = + new("verb-categories-antag", "/Textures/Interface/VerbIcons/antag-e_sword-temp.192dpi.png", iconsOnly: true) { Columns = 5 }; + public static readonly VerbCategory Examine = new("verb-categories-examine", "/Textures/Interface/VerbIcons/examine.svg.192dpi.png"); diff --git a/Resources/Locale/en-US/administration/antag.ftl b/Resources/Locale/en-US/administration/antag.ftl new file mode 100644 index 0000000000..529e960fb3 --- /dev/null +++ b/Resources/Locale/en-US/administration/antag.ftl @@ -0,0 +1,5 @@ +verb-categories-antag = Antag ctrl +admin-verb-make-traitor = Make the target into a traitor. +admin-verb-make-zombie = Zombifies the target immediately. +admin-verb-make-nuclear-operative = Make target a into lone Nuclear Operative. +admin-verb-make-pirate = Make the target into a pirate. Note this doesn't configure the game rule. diff --git a/Resources/Textures/Interface/VerbIcons/antag-e_sword-temp.192dpi.png b/Resources/Textures/Interface/VerbIcons/antag-e_sword-temp.192dpi.png new file mode 100644 index 0000000000000000000000000000000000000000..2c0e88defa5ade078296de38654ab7f430b0a22e GIT binary patch literal 1061 zcmV+=1ls$FP)V=Rgo<@YweEF3yXBvIlRyFduK;~1h=4)%}og`0EKDS zD$5%efG~|qb4|1q&_JRrmoJRO1>lP*0K~ur5G%{g%c16%CP;K*nh$}<<`Eb*zXaR? zoGaE{0O!{30hgFB0?U^G_}4r@VDlz8&ipcP_YwgAGjA1u3C!OelYp`2ZA-va=Eo&q zta&Q{Okw^$um-G{r9RLBym97jNx-D$*MO(ML$mG}cx3?CcM_c1{Nu+@aNR0lxX$4r z93Nxuwe~1Hoq5--^6c4{#Ad%60I=_~z_ra+D(g57grSe)bb!Brt~v3IUXgh+#DG?} z!o1_a!2vXz9a^ndz&`K+I0Jg-^~`LJ=Rn`U$OnK_%r~1IUcC6jH2>#F^SzPgFMwF= z?Bpf^vo;@_LsGVTK$hn7vI%BpK2rdBFrRe-GB=+IfIOMcDgn7MKT`mCG(YPE2J<(afZUtE5db!szo`URV?I>?Hkwa60oIvM1%S=w(@KD~=F-` zD0~Gxf39_T749h4{dU-Va&**A2Id=iGoK28!$I`Ft|ZB7TkF&N5H1Lz`DFfg(|qpC zr}|mow>U0`XJ>rBzkgaA91NVioBu2TTU%Qtz*S1sq?DzJ^0eMHLEfO{3>SP5VQQM_HNw+yuD5k5bBdu~>AalsJyVUbkDO)1jssr&5Td4(B93EE2+?{SMc(UP58xE|aMEaeB