]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
move ninja objectives code into generic antag system (#20186)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Thu, 21 Sep 2023 14:33:18 +0000 (15:33 +0100)
committerGitHub <noreply@github.com>
Thu, 21 Sep 2023 14:33:18 +0000 (07:33 -0700)
Co-authored-by: deltanedas <@deltanedas:kde.org>
Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
Content.Server/GameTicking/Rules/Components/GenericAntagRuleComponent.cs [new file with mode: 0644]
Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs
Content.Server/GameTicking/Rules/GenericAntagRuleSystem.cs [new file with mode: 0644]
Content.Server/GameTicking/Rules/NinjaRuleSystem.cs [deleted file]
Content.Server/GenericAntag/GenericAntagComponent.cs [new file with mode: 0644]
Content.Server/GenericAntag/GenericAntagSystem.cs [new file with mode: 0644]
Content.Server/Ninja/Systems/SpaceNinjaSystem.cs
Resources/Locale/en-US/administration/antag.ftl
Resources/Prototypes/Entities/Mobs/Player/human.yml
Resources/Prototypes/GameRules/midround.yml

index b6e8a0d30098cec97818b76d7053227ba451d58b..3471c8bb7864d387ae77a0461a28b34e9fb81d39 100644 (file)
@@ -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 (file)
index 0000000..8d212eb
--- /dev/null
@@ -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;
+
+/// <summary>
+/// Gamerule for simple antagonists that have fixed objectives.
+/// </summary>
+[RegisterComponent, Access(typeof(GenericAntagRuleSystem))]
+public sealed partial class GenericAntagRuleComponent : Component
+{
+    /// <summary>
+    /// All antag minds that are using this rule.
+    /// </summary>
+    [DataField]
+    public List<EntityUid> Minds = new();
+
+    /// <summary>
+    /// Locale id for the name of the antag used by the roundend summary.
+    /// </summary>
+    [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+    public string AgentName = string.Empty;
+
+    /// <summary>
+    /// List of objective entity prototypes to add to the antag when a mind is added.
+    /// </summary>
+    [DataField(required: true)]
+    public List<EntProtoId> Objectives = new();
+}
index e0789ab753b5f386743b1bd242152a3981ed052d..6a1a4a6a9b008c8feecfb851e0859d79183464a6 100644 (file)
@@ -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;
 
+/// <summary>
+/// Stores some configuration used by the ninja system.
+/// Objectives and roundend summary are handled by <see cref="GenericAntagRuleComponent/">.
+/// </summary>
 [RegisterComponent, Access(typeof(SpaceNinjaSystem))]
 public sealed partial class NinjaRuleComponent : Component
 {
-    /// <summary>
-    /// All ninja minds that are using this rule.
-    /// Their SpaceNinjaComponent Rule field should point back to this rule.
-    /// </summary>
-    [DataField("minds")]
-    public List<EntityUid> Minds = new();
-
-    /// <summary>
-    /// List of objective entity prototypes to add
-    /// </summary>
-    [DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
-    public List<string> Objectives = new();
-
     /// <summary>
     /// List of threats that can be called in. Copied onto <see cref="CommsHackerComponent"/> when gloves are enabled.
     /// </summary>
-    [DataField("threats", required: true)]
+    [DataField(required: true)]
     public List<Threat> Threats = new();
 
     /// <summary>
     /// Sound played when making the player a ninja via antag control or ghost role
     /// </summary>
-    [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 (file)
index 0000000..81bdda7
--- /dev/null
@@ -0,0 +1,53 @@
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Objectives;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Content.Server.GameTicking.Rules;
+
+/// <summary>
+/// Handles round end text for simple antags.
+/// Adding objectives is handled in its own system.
+/// </summary>
+public sealed class GenericAntagRuleSystem : GameRuleSystem<GenericAntagRuleComponent>
+{
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<GenericAntagRuleComponent, ObjectivesTextGetInfoEvent>(OnObjectivesTextGetInfo);
+    }
+
+    /// <summary>
+    /// Start a simple antag's game rule.
+    /// If it is invalid the rule is deleted and null is returned.
+    /// </summary>
+    public bool StartRule(string rule, EntityUid mindId, [NotNullWhen(true)] out EntityUid? ruleId, [NotNullWhen(true)] out GenericAntagRuleComponent? comp)
+    {
+        ruleId = GameTicker.AddGameRule(rule);
+        if (!TryComp<GenericAntagRuleComponent>(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 (file)
index b75241e..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-using Content.Server.GameTicking.Rules.Components;
-using Content.Server.Objectives;
-
-namespace Content.Server.GameTicking.Rules;
-
-/// <summary>
-/// Only handles round end text for ninja.
-/// </summary>
-public sealed class NinjaRuleSystem : GameRuleSystem<NinjaRuleComponent>
-{
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<NinjaRuleComponent, ObjectivesTextGetInfoEvent>(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 (file)
index 0000000..b8ed9cf
--- /dev/null
@@ -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;
+
+/// <summary>
+/// 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.
+/// </summary>
+/// <remarks>
+/// A GenericAntag is not necessarily an antagonist, that depends on the roles you do or do not add after.
+/// </remarks>
+[RegisterComponent, Access(typeof(GenericAntagSystem))]
+public sealed partial class GenericAntagComponent : Component
+{
+    /// <summary>
+    /// Gamerule to start when a mind is added.
+    /// This must have <see cref="GenericAntagRuleComponent"/> or it will not work.
+    /// </summary>
+    [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+    public EntProtoId Rule = string.Empty;
+
+    /// <summary>
+    /// The rule that's been spawned.
+    /// Used to prevent spawning multiple rules.
+    /// </summary>
+    [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 (file)
index 0000000..d789153
--- /dev/null
@@ -0,0 +1,67 @@
+using Content.Server.GameTicking.Rules;
+using Content.Shared.Mind;
+using Content.Shared.Mind.Components;
+
+namespace Content.Server.GenericAntag;
+
+/// <summary>
+/// Handles adding objectives to <see cref="GenericAntagComponent"/>s.
+/// Roundend summary is handled by <see cref="GenericAntagRuleSystem"/>.
+/// </summary>
+public sealed class GenericAntagSystem : EntitySystem
+{
+    [Dependency] private readonly SharedMindSystem _mind = default!;
+    [Dependency] private readonly GenericAntagRuleSystem _genericAntagRule = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<GenericAntagComponent, MindAddedMessage>(OnMindAdded);
+    }
+
+    private void OnMindAdded(EntityUid uid, GenericAntagComponent comp, MindAddedMessage args)
+    {
+        if (!TryComp<MindContainerComponent>(uid, out var mindContainer) || mindContainer.Mind == null)
+            return;
+
+        var mindId = mindContainer.Mind.Value;
+        MakeAntag(uid, mindId, comp);
+    }
+
+    /// <summary>
+    /// Turns a player into this antagonist.
+    /// Does the same thing that having a mind added does, use for antag ctrl.
+    /// </summary>
+    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);
+        }
+    }
+}
+
+/// <summary>
+/// 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.
+/// </summary>
+[ByRefEvent]
+public record struct GenericAntagCreatedEvent(EntityUid MindId, MindComponent Mind);
index 68827f956c19490f5333ed9141a8d58e30c7b9b1..0cc3aea2662f21fdaa72ae0b3ca0bc80c3cc35e5 100644 (file)
@@ -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
 
 /// <summary>
-/// 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.
 /// </summary>
 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<SpaceNinjaComponent, MindAddedMessage>(OnNinjaMindAdded);
+        SubscribeLocalEvent<SpaceNinjaComponent, GenericAntagCreatedEvent>(OnNinjaCreated);
         SubscribeLocalEvent<SpaceNinjaComponent, EmaggedSomethingEvent>(OnDoorjack);
         SubscribeLocalEvent<SpaceNinjaComponent, ResearchStolenEvent>(OnResearchStolen);
         SubscribeLocalEvent<SpaceNinjaComponent, ThreatCalledInEvent>(OnThreatCalledIn);
@@ -78,24 +80,6 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
         }
     }
 
-    /// <summary>
-    /// Turns the player into a space ninja
-    /// </summary>
-    public void MakeNinja(EntityUid mindId, MindComponent mind)
-    {
-        if (mind.OwnedEntity == null)
-            return;
-
-        // prevent double ninja'ing
-        var user = mind.OwnedEntity.Value;
-        if (HasComp<SpaceNinjaComponent>(user))
-            return;
-
-        AddComp<SpaceNinjaComponent>(user);
-        SetOutfitCommand.SetOutfit(user, "SpaceNinjaGear", EntityManager);
-        GreetNinja(mindId, mind);
-    }
-
     /// <summary>
     /// Download the given set of nodes, returning how many new nodes were downloaded.
     /// </summary>
@@ -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.
     /// </summary>
-    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<NinjaRuleComponent>(comp.Rule);
-
-        // start it
-        _gameTicker.StartGameRule("Ninja", out var rule);
-        comp.Rule = rule;
-
-        if (!TryComp<NinjaRuleComponent>(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<MindContainerComponent>(uid, out var mindContainer) && mindContainer.Mind != null)
-        {
-            ninjaRule.Minds.Add(mindContainer.Mind.Value);
-        }
-
-        return ninjaRule;
+        return CompOrNull<NinjaRuleComponent>(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);
     }
 
-    /// <summary>
-    /// Greets the ninja when a ghost takes over a ninja, if that happens.
-    /// </summary>
-    private void OnNinjaMindAdded(EntityUid uid, SpaceNinjaComponent comp, MindAddedMessage args)
-    {
-        if (TryComp<MindContainerComponent>(uid, out var mind) && mind.Mind != null)
-            GreetNinja(mind.Mind.Value);
-    }
-
     /// <summary>
     /// Set up everything for ninja to work and send the greeting message/sound.
+    /// Objectives are added by <see cref="GenericAntagSystem"/>.
     /// </summary>
-    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"));
index 68f33f8c4ddf4045502aa18b57f5698153d34a91..ec428b058008ec829ec82ff37e3ddb098e445b99 100644 (file)
@@ -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
index f04e4bce04faed4b6f5d7f787034b8a6f561c3c8..985be544e5fc50de30d09b8199fffe9352dbc478 100644 (file)
@@ -67,6 +67,8 @@
     factions:
     - Syndicate
   - type: SpaceNinja
+  - type: GenericAntag
+    rule: Ninja
   - type: AutoImplant
     implants:
     - MicroBombImplant
index 6d431a9dcd19852bbbcc9200cdd5dc7ea2fccd74..41fb9fa432f641eba08447d8722b017254fc16a6 100644 (file)
@@ -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