From 37c5020a714cb52bd09d45bb4c4ec90f5513d45c Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Thu, 21 Sep 2023 15:33:18 +0100 Subject: [PATCH] move ninja objectives code into generic antag system (#20186) Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Systems/AdminVerbSystem.Antags.cs | 19 ----- .../Components/GenericAntagRuleComponent.cs | 30 ++++++++ .../Rules/Components/NinjaRuleComponent.cs | 23 ++---- .../Rules/GenericAntagRuleSystem.cs | 53 ++++++++++++++ .../GameTicking/Rules/NinjaRuleSystem.cs | 23 ------ .../GenericAntag/GenericAntagComponent.cs | 30 ++++++++ .../GenericAntag/GenericAntagSystem.cs | 67 +++++++++++++++++ .../Ninja/Systems/SpaceNinjaSystem.cs | 72 ++++--------------- .../Locale/en-US/administration/antag.ftl | 2 - .../Prototypes/Entities/Mobs/Player/human.yml | 2 + Resources/Prototypes/GameRules/midround.yml | 4 +- 11 files changed, 205 insertions(+), 120 deletions(-) create mode 100644 Content.Server/GameTicking/Rules/Components/GenericAntagRuleComponent.cs create mode 100644 Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs delete mode 100644 Content.Server/GameTicking/Rules/NinjaRuleSystem.cs create mode 100644 Content.Server/GenericAntag/GenericAntagComponent.cs create mode 100644 Content.Server/GenericAntag/GenericAntagSystem.cs diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index b6e8a0d300..3471c8bb78 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -1,5 +1,4 @@ using Content.Server.GameTicking.Rules; -using Content.Server.Ninja.Systems; using Content.Server.Zombies; using Content.Shared.Administration; using Content.Shared.Database; @@ -16,7 +15,6 @@ public sealed partial class AdminVerbSystem { [Dependency] private readonly ZombieSystem _zombie = default!; [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; - [Dependency] private readonly SpaceNinjaSystem _ninja = default!; [Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!; [Dependency] private readonly PiratesRuleSystem _piratesRule = default!; [Dependency] private readonly SharedMindSystem _minds = default!; @@ -102,22 +100,5 @@ public sealed partial class AdminVerbSystem Message = Loc.GetString("admin-verb-make-pirate"), }; args.Verbs.Add(pirate); - - Verb spaceNinja = new() - { - Text = Loc.GetString("admin-verb-text-make-space-ninja"), - Category = VerbCategory.Antag, - Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Objects/Weapons/Melee/energykatana.rsi"), "icon"), - Act = () => - { - if (!_minds.TryGetMind(args.Target, out var mindId, out var mind)) - return; - - _ninja.MakeNinja(mindId, mind); - }, - Impact = LogImpact.High, - Message = Loc.GetString("admin-verb-make-space-ninja"), - }; - args.Verbs.Add(spaceNinja); } } diff --git a/Content.Server/GameTicking/Rules/Components/GenericAntagRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/GenericAntagRuleComponent.cs new file mode 100644 index 0000000000..8d212ebaa3 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/GenericAntagRuleComponent.cs @@ -0,0 +1,30 @@ +using Content.Server.GameTicking.Rules; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Server.GameTicking.Rules.Components; + +/// +/// Gamerule for simple antagonists that have fixed objectives. +/// +[RegisterComponent, Access(typeof(GenericAntagRuleSystem))] +public sealed partial class GenericAntagRuleComponent : Component +{ + /// + /// All antag minds that are using this rule. + /// + [DataField] + public List Minds = new(); + + /// + /// Locale id for the name of the antag used by the roundend summary. + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public string AgentName = string.Empty; + + /// + /// List of objective entity prototypes to add to the antag when a mind is added. + /// + [DataField(required: true)] + public List Objectives = new(); +} diff --git a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs index e0789ab753..6a1a4a6a9b 100644 --- a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs @@ -1,36 +1,25 @@ using Content.Server.Ninja.Systems; using Content.Shared.Communications; using Robust.Shared.Audio; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Server.GameTicking.Rules.Components; +/// +/// Stores some configuration used by the ninja system. +/// Objectives and roundend summary are handled by . +/// [RegisterComponent, Access(typeof(SpaceNinjaSystem))] public sealed partial class NinjaRuleComponent : Component { - /// - /// All ninja minds that are using this rule. - /// Their SpaceNinjaComponent Rule field should point back to this rule. - /// - [DataField("minds")] - public List Minds = new(); - - /// - /// List of objective entity prototypes to add - /// - [DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Objectives = new(); - /// /// List of threats that can be called in. Copied onto when gloves are enabled. /// - [DataField("threats", required: true)] + [DataField(required: true)] public List Threats = new(); /// /// Sound played when making the player a ninja via antag control or ghost role /// - [DataField("greetingSound", customTypeSerializer: typeof(SoundSpecifierTypeSerializer))] + [DataField] public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/ninja_greeting.ogg"); } diff --git a/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs new file mode 100644 index 0000000000..81bdda706b --- /dev/null +++ b/Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs @@ -0,0 +1,53 @@ +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Objectives; +using System.Diagnostics.CodeAnalysis; + +namespace Content.Server.GameTicking.Rules; + +/// +/// Handles round end text for simple antags. +/// Adding objectives is handled in its own system. +/// +public sealed class GenericAntagRuleSystem : GameRuleSystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnObjectivesTextGetInfo); + } + + /// + /// Start a simple antag's game rule. + /// If it is invalid the rule is deleted and null is returned. + /// + public bool StartRule(string rule, EntityUid mindId, [NotNullWhen(true)] out EntityUid? ruleId, [NotNullWhen(true)] out GenericAntagRuleComponent? comp) + { + ruleId = GameTicker.AddGameRule(rule); + if (!TryComp(ruleId, out comp)) + { + Log.Error($"Simple antag rule prototype {rule} is invalid, deleting it."); + Del(ruleId); + ruleId = null; + return false; + } + + if (!GameTicker.StartGameRule(ruleId.Value)) + { + Log.Error($"Simple antag rule prototype {rule} failed to start, deleting it."); + Del(ruleId); + ruleId = null; + comp = null; + return false; + } + + comp.Minds.Add(mindId); + return true; + } + + private void OnObjectivesTextGetInfo(EntityUid uid, GenericAntagRuleComponent comp, ref ObjectivesTextGetInfoEvent args) + { + args.Minds = comp.Minds; + args.AgentName = Loc.GetString(comp.AgentName); + } +} diff --git a/Content.Server/GameTicking/Rules/NinjaRuleSystem.cs b/Content.Server/GameTicking/Rules/NinjaRuleSystem.cs deleted file mode 100644 index b75241eaf4..0000000000 --- a/Content.Server/GameTicking/Rules/NinjaRuleSystem.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Server.GameTicking.Rules.Components; -using Content.Server.Objectives; - -namespace Content.Server.GameTicking.Rules; - -/// -/// Only handles round end text for ninja. -/// -public sealed class NinjaRuleSystem : GameRuleSystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnObjectivesTextGetInfo); - } - - private void OnObjectivesTextGetInfo(EntityUid uid, NinjaRuleComponent comp, ref ObjectivesTextGetInfoEvent args) - { - args.Minds = comp.Minds; - args.AgentName = Loc.GetString("ninja-round-end-agent-name"); - } -} diff --git a/Content.Server/GenericAntag/GenericAntagComponent.cs b/Content.Server/GenericAntag/GenericAntagComponent.cs new file mode 100644 index 0000000000..b8ed9cf6ef --- /dev/null +++ b/Content.Server/GenericAntag/GenericAntagComponent.cs @@ -0,0 +1,30 @@ +using Content.Server.GameTicking.Rules.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.GenericAntag; + +/// +/// Added to a mob to make it a generic antagonist where all its objectives are fixed. +/// This is unlike say traitor where it gets objectives picked randomly using difficulty. +/// +/// +/// A GenericAntag is not necessarily an antagonist, that depends on the roles you do or do not add after. +/// +[RegisterComponent, Access(typeof(GenericAntagSystem))] +public sealed partial class GenericAntagComponent : Component +{ + /// + /// Gamerule to start when a mind is added. + /// This must have or it will not work. + /// + [DataField(required: true), ViewVariables(VVAccess.ReadWrite)] + public EntProtoId Rule = string.Empty; + + /// + /// The rule that's been spawned. + /// Used to prevent spawning multiple rules. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityUid? RuleEntity; +} diff --git a/Content.Server/GenericAntag/GenericAntagSystem.cs b/Content.Server/GenericAntag/GenericAntagSystem.cs new file mode 100644 index 0000000000..d789153fbc --- /dev/null +++ b/Content.Server/GenericAntag/GenericAntagSystem.cs @@ -0,0 +1,67 @@ +using Content.Server.GameTicking.Rules; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; + +namespace Content.Server.GenericAntag; + +/// +/// Handles adding objectives to s. +/// Roundend summary is handled by . +/// +public sealed class GenericAntagSystem : EntitySystem +{ + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly GenericAntagRuleSystem _genericAntagRule = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMindAdded); + } + + private void OnMindAdded(EntityUid uid, GenericAntagComponent comp, MindAddedMessage args) + { + if (!TryComp(uid, out var mindContainer) || mindContainer.Mind == null) + return; + + var mindId = mindContainer.Mind.Value; + MakeAntag(uid, mindId, comp); + } + + /// + /// Turns a player into this antagonist. + /// Does the same thing that having a mind added does, use for antag ctrl. + /// + public void MakeAntag(EntityUid uid, EntityUid mindId, GenericAntagComponent? comp = null, MindComponent? mind = null) + { + if (!Resolve(uid, ref comp) || !Resolve(mindId, ref mind)) + return; + + // only add the rule once + if (comp.RuleEntity != null) + return; + + // start the rule + if (!_genericAntagRule.StartRule(comp.Rule, mindId, out comp.RuleEntity, out var rule)) + return; + + // let other systems know the antag was created so they can add briefing, roles, etc. + // its important that this is before objectives are added since they may depend on roles added here + var ev = new GenericAntagCreatedEvent(mindId, mind); + RaiseLocalEvent(uid, ref ev); + + // add the objectives from the rule + foreach (var id in rule.Objectives) + { + _mind.TryAddObjective(mindId, mind, id); + } + } +} + +/// +/// Event raised on a player's entity after its simple antag rule is started and objectives get added. +/// Use this to add a briefing, roles, etc. +/// +[ByRefEvent] +public record struct GenericAntagCreatedEvent(EntityUid MindId, MindComponent Mind); diff --git a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs index 68827f956c..0cc3aea266 100644 --- a/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs +++ b/Content.Server/Ninja/Systems/SpaceNinjaSystem.cs @@ -12,6 +12,7 @@ using Content.Server.Power.EntitySystems; using Content.Server.PowerCell; using Content.Server.Research.Systems; using Content.Server.Roles; +using Content.Server.GenericAntag; using Content.Server.Warps; using Content.Shared.Alert; using Content.Shared.Clothing.EntitySystems; @@ -42,13 +43,14 @@ namespace Content.Server.Ninja.Systems; // TODO: when criminal records is merged, hack it to set everyone to arrest /// -/// Main ninja system that handles ninja setup and greentext, provides helper methods for the rest of the code to use. +/// Main ninja system that handles ninja setup, provides helper methods for the rest of the code to use. /// public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem { [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly BatterySystem _battery = default!; [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly GenericAntagSystem _genericAntag = default!; [Dependency] private readonly IChatManager _chatMan = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly PowerCellSystem _powerCell = default!; @@ -63,7 +65,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem { base.Initialize(); - SubscribeLocalEvent(OnNinjaMindAdded); + SubscribeLocalEvent(OnNinjaCreated); SubscribeLocalEvent(OnDoorjack); SubscribeLocalEvent(OnResearchStolen); SubscribeLocalEvent(OnThreatCalledIn); @@ -78,24 +80,6 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem } } - /// - /// Turns the player into a space ninja - /// - public void MakeNinja(EntityUid mindId, MindComponent mind) - { - if (mind.OwnedEntity == null) - return; - - // prevent double ninja'ing - var user = mind.OwnedEntity.Value; - if (HasComp(user)) - return; - - AddComp(user); - SetOutfitCommand.SetOutfit(user, "SpaceNinjaGear", EntityManager); - GreetNinja(mindId, mind); - } - /// /// Download the given set of nodes, returning how many new nodes were downloaded. /// @@ -114,29 +98,16 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem /// Returns a ninja's gamerule config data. /// If the gamerule was not started then it will be started automatically. /// - public NinjaRuleComponent? NinjaRule(EntityUid uid, SpaceNinjaComponent? comp = null) + public NinjaRuleComponent? NinjaRule(EntityUid uid, GenericAntagComponent? comp = null) { if (!Resolve(uid, ref comp)) return null; - // already exists so just check it - if (comp.Rule != null) - return CompOrNull(comp.Rule); - - // start it - _gameTicker.StartGameRule("Ninja", out var rule); - comp.Rule = rule; - - if (!TryComp(rule, out var ninjaRule)) + // mind not added yet so no rule + if (comp.RuleEntity == null) return null; - // add ninja mind to the rule's list for objective showing - if (TryComp(uid, out var mindContainer) && mindContainer.Mind != null) - { - ninjaRule.Minds.Add(mindContainer.Mind.Value); - } - - return ninjaRule; + return CompOrNull(comp.RuleEntity); } // TODO: can probably copy paste borg code here @@ -185,24 +156,18 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem return GetNinjaBattery(user, out var uid, out var battery) && _battery.TryUseCharge(uid.Value, charge, battery); } - /// - /// Greets the ninja when a ghost takes over a ninja, if that happens. - /// - private void OnNinjaMindAdded(EntityUid uid, SpaceNinjaComponent comp, MindAddedMessage args) - { - if (TryComp(uid, out var mind) && mind.Mind != null) - GreetNinja(mind.Mind.Value); - } - /// /// Set up everything for ninja to work and send the greeting message/sound. + /// Objectives are added by . /// - private void GreetNinja(EntityUid mindId, MindComponent? mind = null) + private void OnNinjaCreated(EntityUid uid, SpaceNinjaComponent comp, ref GenericAntagCreatedEvent args) { - if (!Resolve(mindId, ref mind) || mind.OwnedEntity == null || mind.Session == null) + var mindId = args.MindId; + var mind = args.Mind; + + if (mind.Session == null) return; - var uid = mind.OwnedEntity.Value; var config = NinjaRule(uid); if (config == null) return; @@ -226,15 +191,6 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem if (warps.Count > 0) role.SpiderChargeTarget = _random.Pick(warps); - // assign objectives - must happen after spider charge target so that the obj requirement works - foreach (var objective in config.Objectives) - { - if (!_mind.TryAddObjective(mindId, mind, objective)) - { - Log.Error($"Failed to add {objective} to ninja {mind.OwnedEntity.Value}"); - } - } - var session = mind.Session; _audio.PlayGlobal(config.GreetingSound, Filter.Empty().AddPlayer(session), false, AudioParams.Default); _chatMan.DispatchServerMessage(session, Loc.GetString("ninja-role-greeting")); diff --git a/Resources/Locale/en-US/administration/antag.ftl b/Resources/Locale/en-US/administration/antag.ftl index 68f33f8c4d..ec428b0580 100644 --- a/Resources/Locale/en-US/administration/antag.ftl +++ b/Resources/Locale/en-US/administration/antag.ftl @@ -3,10 +3,8 @@ 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 that this doesn't configure the game rule. -admin-verb-make-space-ninja = Make the target into a space ninja. admin-verb-text-make-traitor = Make Traitor admin-verb-text-make-zombie = Make Zombie admin-verb-text-make-nuclear-operative = Make Nuclear Operative admin-verb-text-make-pirate = Make Pirate -admin-verb-text-make-space-ninja = Make Space Ninja diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index f04e4bce04..985be544e5 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -67,6 +67,8 @@ factions: - Syndicate - type: SpaceNinja + - type: GenericAntag + rule: Ninja - type: AutoImplant implants: - MicroBombImplant diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 6d431a9dcd..41fb9fa432 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -5,13 +5,15 @@ parent: BaseGameRule noSpawn: true components: - - type: NinjaRule + - type: GenericAntagRule + agentName: ninja-round-end-agent-name objectives: - StealResearchObjective - DoorjackObjective - SpiderChargeObjective - TerrorObjective - NinjaSurviveObjective + - type: NinjaRule threats: - announcement: terror-dragon rule: Dragon