]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
LoadMapRule grid storage rework (#28210)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Tue, 4 Jun 2024 00:04:19 +0000 (00:04 +0000)
committerGitHub <noreply@github.com>
Tue, 4 Jun 2024 00:04:19 +0000 (20:04 -0400)
*

13 files changed:
Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs
Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs [new file with mode: 0644]
Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs
Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs
Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
Content.Server/GameTicking/Rules/RuleGridsSystem.cs [new file with mode: 0644]
Content.Server/GridPreloader/GridPreloaderSystem.cs
Content.Server/StationEvents/Events/StationEventSystem.cs
Resources/Prototypes/GameRules/events.yml
Resources/Prototypes/GameRules/midround.yml
Resources/Prototypes/GameRules/roundstart.yml
Resources/Prototypes/GameRules/unknown_shuttles.yml

index ea19a30005ee2aafe01bd4df16e22eab629dc6cc..2360ea0bf4f2bfa7d8c7ce255349d44c4721881e 100644 (file)
@@ -114,8 +114,8 @@ public sealed class NukeOpsTest
 
         // The game rule exists, and all the stations/shuttles/maps are properly initialized
         var rule = entMan.AllComponents<NukeopsRuleComponent>().Single().Component;
-        var mapRule = entMan.AllComponents<LoadMapRuleComponent>().Single().Component;
-        foreach (var grid in mapRule.MapGrids)
+        var gridsRule = entMan.AllComponents<RuleGridsComponent>().Single().Component;
+        foreach (var grid in gridsRule.MapGrids)
         {
             Assert.That(entMan.EntityExists(grid));
             Assert.That(entMan.HasComponent<MapGridComponent>(grid));
@@ -129,7 +129,7 @@ public sealed class NukeOpsTest
         Assert.That(entMan.EntityExists(nukieShuttlEnt));
 
         EntityUid? nukieStationEnt = null;
-        foreach (var grid in mapRule.MapGrids)
+        foreach (var grid in gridsRule.MapGrids)
         {
             if (entMan.HasComponent<StationMemberComponent>(grid))
             {
@@ -144,8 +144,8 @@ public sealed class NukeOpsTest
         Assert.That(entMan.EntityExists(nukieStation.Station));
         Assert.That(nukieStation.Station, Is.Not.EqualTo(rule.TargetStation));
 
-        Assert.That(server.MapMan.MapExists(mapRule.Map));
-        var nukieMap = mapSys.GetMap(mapRule.Map!.Value);
+        Assert.That(server.MapMan.MapExists(gridsRule.Map));
+        var nukieMap = mapSys.GetMap(gridsRule.Map!.Value);
 
         var targetStation = entMan.GetComponent<StationDataComponent>(rule.TargetStation!.Value);
         var targetGrid = targetStation.Grids.First();
index b7aef0c61dc202355abe86961ca38f2146328883..1f0505c60fc0ecd421c3e42047dba430fc573e17 100644 (file)
@@ -1,8 +1,6 @@
+using Content.Server.GameTicking.Rules;
 using Content.Server.Maps;
 using Content.Shared.GridPreloader.Prototypes;
-using Content.Shared.Storage;
-using Content.Shared.Whitelist;
-using Robust.Shared.Map;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Utility;
 
@@ -10,25 +8,27 @@ namespace Content.Server.GameTicking.Rules.Components;
 
 /// <summary>
 /// This is used for a game rule that loads a map when activated.
+/// Works with <see cref="RuleGridsComponent"/>.
 /// </summary>
-[RegisterComponent]
+[RegisterComponent, Access(typeof(LoadMapRuleSystem))]
 public sealed partial class LoadMapRuleComponent : Component
 {
-    [DataField]
-    public MapId? Map;
-
+    /// <summary>
+    /// A <see cref="GameMapPrototype"/> to load on a new map.
+    /// </summary>
     [DataField]
     public ProtoId<GameMapPrototype>? GameMap;
 
+    /// <summary>
+    /// A map path to load on a new map.
+    /// </summary>
     [DataField]
     public ResPath? MapPath;
 
+    /// <summary>
+    /// A <see cref="PreloadedGridPrototype"/> to move to a new map.
+    /// If there are no instances left nothing is done.
+    /// </summary>
     [DataField]
     public ProtoId<PreloadedGridPrototype>? PreloadedGrid;
-
-    [DataField]
-    public List<EntityUid> MapGrids = new();
-
-    [DataField]
-    public EntityWhitelist? SpawnerWhitelist;
 }
diff --git a/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs b/Content.Server/GameTicking/Rules/Components/RuleGridsComponent.cs
new file mode 100644 (file)
index 0000000..eec6f88
--- /dev/null
@@ -0,0 +1,30 @@
+using Content.Server.GameTicking.Rules;
+using Content.Shared.Whitelist;
+using Robust.Shared.Map;
+
+/// <summary>
+/// Stores grids created by another gamerule component.
+/// With <c>AntagSelection</c>, spawners on these grids can be used for its antags.
+/// </summary>
+[RegisterComponent, Access(typeof(RuleGridsSystem))]
+public sealed partial class RuleGridsComponent : Component
+{
+    /// <summary>
+    /// The map that was loaded.
+    /// </summary>
+    [DataField]
+    public MapId? Map;
+
+    /// <summary>
+    /// The grid entities that have been loaded.
+    /// </summary>
+    [DataField]
+    public List<EntityUid> MapGrids = new();
+
+    /// <summary>
+    /// Whitelist for a spawner to be considered for an antag.
+    /// All spawners must have <c>SpawnPointComponent</c> regardless to be found.
+    /// </summary>
+    [DataField]
+    public EntityWhitelist? SpawnerWhitelist;
+}
index cbd981e99e69f92f43dbf6b92968d953390275d4..9ac7a6edbb909bd58ea1e6643ef5f98c40659662 100644 (file)
@@ -126,4 +126,8 @@ public abstract partial class GameRuleSystem<T> where T: IComponent
         return found;
     }
 
+    protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null)
+    {
+        GameTicker.EndGameRule(uid, component);
+    }
 }
index c60cf2513e0e266b2d28cb1cf7cdde4eaaffcb12..1c09d6e86e107e401339eb3fe3016b9ec19a361c 100644 (file)
@@ -1,9 +1,6 @@
-using Content.Server.Antag;
 using Content.Server.GameTicking.Components;
 using Content.Server.GameTicking.Rules.Components;
 using Content.Server.GridPreloader;
-using Content.Server.Spawners.Components;
-using Content.Shared.Whitelist;
 using Robust.Server.GameObjects;
 using Robust.Server.Maps;
 using Robust.Shared.Map;
@@ -14,97 +11,70 @@ namespace Content.Server.GameTicking.Rules;
 public sealed class LoadMapRuleSystem : GameRuleSystem<LoadMapRuleComponent>
 {
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-    [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly MapSystem _map = default!;
     [Dependency] private readonly MapLoaderSystem _mapLoader = default!;
     [Dependency] private readonly MetaDataSystem _metaData = default!;
     [Dependency] private readonly TransformSystem _transform = default!;
     [Dependency] private readonly GridPreloaderSystem _gridPreloader = default!;
-    [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
-
-    public override void Initialize()
-    {
-        base.Initialize();
-
-        SubscribeLocalEvent<LoadMapRuleComponent, AntagSelectLocationEvent>(OnSelectLocation);
-        SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
-    }
-
-    private void OnGridSplit(ref GridSplitEvent args)
-    {
-        var rule = QueryActiveRules();
-        while (rule.MoveNext(out _, out var mapComp, out _))
-        {
-            if (!mapComp.MapGrids.Contains(args.Grid))
-                continue;
-
-            mapComp.MapGrids.AddRange(args.NewGrids);
-            break;
-        }
-    }
 
     protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRuleComponent rule, GameRuleAddedEvent args)
     {
-        if (comp.Map != null)
+        if (comp.PreloadedGrid != null && !_gridPreloader.PreloadingEnabled)
+        {
+            // Preloading will never work if it's disabled, duh
+            Log.Debug($"Immediately ending {ToPrettyString(uid):rule} as preloading grids is disabled by cvar.");
+            ForceEndSelf(uid, rule);
             return;
+        }
 
         // grid preloading needs map to init after moving it
-        var mapUid = comp.PreloadedGrid != null ? _map.CreateMap(out var mapId, false) : _map.CreateMap(out mapId);
-        _metaData.SetEntityName(mapUid, $"LoadMapRule destination for rule {ToPrettyString(uid)}");
-        comp.Map = mapId;
+        var mapUid = _map.CreateMap(out var mapId, runMapInit: comp.PreloadedGrid == null);
+
+        Log.Info($"Created map {mapId} for {ToPrettyString(uid):rule}");
 
+        IReadOnlyList<EntityUid> grids;
         if (comp.GameMap != null)
         {
             var gameMap = _prototypeManager.Index(comp.GameMap.Value);
-            comp.MapGrids.AddRange(GameTicker.LoadGameMap(gameMap, comp.Map.Value, new MapLoadOptions()));
+            grids = GameTicker.LoadGameMap(gameMap, mapId, new MapLoadOptions());
         }
-        else if (comp.MapPath != null)
+        else if (comp.MapPath is {} path)
         {
-            if (!_mapLoader.TryLoad(comp.Map.Value,
-                    comp.MapPath.Value.ToString(),
-                    out var roots,
-                    new MapLoadOptions { LoadMap = true }))
+            var options = new MapLoadOptions { LoadMap = true };
+            if (!_mapLoader.TryLoad(mapId, path.ToString(), out var roots, options))
             {
-                _mapManager.DeleteMap(mapId);
+                Log.Error($"Failed to load map from {path}!");
+                Del(mapUid);
+                ForceEndSelf(uid, rule);
                 return;
             }
 
-            comp.MapGrids.AddRange(roots);
+            grids = roots;
         }
-        else if (comp.PreloadedGrid != null)
+        else if (comp.PreloadedGrid is {} preloaded)
         {
             // TODO: If there are no preloaded grids left, any rule announcements will still go off!
-            if (!_gridPreloader.TryGetPreloadedGrid(comp.PreloadedGrid.Value, out var loadedShuttle))
+            if (!_gridPreloader.TryGetPreloadedGrid(preloaded, out var loadedShuttle))
             {
-                _mapManager.DeleteMap(mapId);
+                Log.Error($"Failed to get a preloaded grid with {preloaded}!");
+                Del(mapUid);
+                ForceEndSelf(uid, rule);
                 return;
             }
 
             _transform.SetParent(loadedShuttle.Value, mapUid);
-            comp.MapGrids.Add(loadedShuttle.Value);
-            _map.InitializeMap(mapId);
+            grids = new List<EntityUid>() { loadedShuttle.Value };
+            _map.InitializeMap(mapUid);
         }
         else
         {
             Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}");
+            Del(mapUid);
+            ForceEndSelf(uid, rule);
+            return;
         }
-    }
-
-    private void OnSelectLocation(Entity<LoadMapRuleComponent> ent, ref AntagSelectLocationEvent args)
-    {
-        var query = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
-        while (query.MoveNext(out var uid, out _, out var xform))
-        {
-            if (xform.MapID != ent.Comp.Map)
-                continue;
-
-            if (xform.GridUid == null || !ent.Comp.MapGrids.Contains(xform.GridUid.Value))
-                continue;
 
-            if (_whitelistSystem.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid))
-                continue;
-
-            args.Coordinates.Add(_transform.GetMapCoordinates(xform));
-        }
+        var ev = new RuleLoadedGridsEvent(mapId, grids);
+        RaiseLocalEvent(uid, ref ev);
     }
 }
index 1b62778d75858cb3164709c33200662957b6f4e5..6688bfd980c6ac35dad7fe5076a16d03394e8147 100644 (file)
@@ -260,10 +260,10 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
     {
         var map = Transform(ent).MapID;
 
-        var rules = EntityQueryEnumerator<NukeopsRuleComponent, LoadMapRuleComponent>();
-        while (rules.MoveNext(out var uid, out _, out var mapRule))
+        var rules = EntityQueryEnumerator<NukeopsRuleComponent, RuleGridsComponent>();
+        while (rules.MoveNext(out var uid, out _, out var grids))
         {
-            if (map != mapRule.Map)
+            if (map != grids.Map)
                 continue;
             ent.Comp.AssociatedRule = uid;
             break;
@@ -324,7 +324,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
             if (nukeops.WarDeclaredTime != null)
                 continue;
 
-            if (TryComp<LoadMapRuleComponent>(uid, out var mapComp) && Transform(ev.DeclaratorEntity).MapID != mapComp.Map)
+            if (TryComp<RuleGridsComponent>(uid, out var grids) && Transform(ev.DeclaratorEntity).MapID != grids.Map)
                 continue;
 
             var newStatus = GetWarCondition(nukeops, ev.Status);
@@ -445,7 +445,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
 
         // Check that there are spawns available and that they can access the shuttle.
         var spawnsAvailable = EntityQuery<NukeOperativeSpawnerComponent>(true).Any();
-        if (spawnsAvailable && CompOrNull<LoadMapRuleComponent>(ent)?.Map == shuttleMapId)
+        if (spawnsAvailable && CompOrNull<RuleGridsComponent>(ent)?.Map == shuttleMapId)
             return; // Ghost spawns can still access the shuttle. Continue the round.
 
         // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives,
@@ -478,7 +478,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
     /// Is this method the shitty glue holding together the last of my sanity? yes.
     /// Do i have a better solution? not presently.
     /// </remarks>
-    private EntityUid? GetOutpost(Entity<LoadMapRuleComponent?> ent)
+    private EntityUid? GetOutpost(Entity<RuleGridsComponent?> ent)
     {
         if (!Resolve(ent, ref ent.Comp, false))
             return null;
diff --git a/Content.Server/GameTicking/Rules/RuleGridsSystem.cs b/Content.Server/GameTicking/Rules/RuleGridsSystem.cs
new file mode 100644 (file)
index 0000000..9eae9e3
--- /dev/null
@@ -0,0 +1,78 @@
+using Content.Server.Antag;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Spawners.Components;
+using Content.Shared.Whitelist;
+using Robust.Server.Physics;
+using Robust.Shared.Map;
+
+namespace Content.Server.GameTicking.Rules;
+
+/// <summary>
+/// Handles storing grids from <see cref="RuleLoadedGridsEvent"/> and antags spawning on their spawners.
+/// </summary>
+public sealed class RuleGridsSystem : GameRuleSystem<RuleGridsComponent>
+{
+    [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
+
+        SubscribeLocalEvent<RuleGridsComponent, RuleLoadedGridsEvent>(OnLoadedGrids);
+        SubscribeLocalEvent<RuleGridsComponent, AntagSelectLocationEvent>(OnSelectLocation);
+    }
+
+    private void OnGridSplit(ref GridSplitEvent args)
+    {
+        var rule = QueryActiveRules();
+        while (rule.MoveNext(out _, out var comp, out _))
+        {
+            if (!comp.MapGrids.Contains(args.Grid))
+                continue;
+
+            comp.MapGrids.AddRange(args.NewGrids);
+            break; // only 1 rule can own a grid, not multiple
+        }
+    }
+
+    private void OnLoadedGrids(Entity<RuleGridsComponent> ent, ref RuleLoadedGridsEvent args)
+    {
+        var (uid, comp) = ent;
+        if (comp.Map != null && args.Map != comp.Map)
+        {
+            Log.Warning($"{ToPrettyString(uid):rule} loaded grids on multiple maps {comp.Map} and {args.Map}, the second will be ignored.");
+            return;
+        }
+
+        comp.Map = args.Map;
+        comp.MapGrids.AddRange(args.Grids);
+    }
+
+    private void OnSelectLocation(Entity<RuleGridsComponent> ent, ref AntagSelectLocationEvent args)
+    {
+        var query = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
+        while (query.MoveNext(out var uid, out _, out var xform))
+        {
+            if (xform.MapID != ent.Comp.Map)
+                continue;
+
+            if (xform.GridUid is not {} grid || !ent.Comp.MapGrids.Contains(grid))
+                continue;
+
+            if (_whitelist.IsWhitelistFail(ent.Comp.SpawnerWhitelist, uid))
+                continue;
+
+            args.Coordinates.Add(_transform.GetMapCoordinates(xform));
+        }
+    }
+}
+
+/// <summary>
+/// Raised by another gamerule system to store loaded grids, and have other systems work with it.
+/// A single rule can only load grids for a single map, attempts to load more are ignored.
+/// </summary>
+[ByRefEvent]
+public record struct RuleLoadedGridsEvent(MapId Map, IReadOnlyList<EntityUid> Grids);
index 569fe54141c864ef5af8ada99e44945e3534b6cc..e12ce41a31e89bfa4a6e129cbdb06b4da48b7ad3 100644 (file)
@@ -24,12 +24,19 @@ public sealed class GridPreloaderSystem : SharedGridPreloaderSystem
     [Dependency] private readonly IPrototypeManager _prototype = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
 
+    /// <summary>
+    /// Whether the preloading CVar is set or not.
+    /// </summary>
+    public bool PreloadingEnabled;
+
     public override void Initialize()
     {
         base.Initialize();
 
         SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
         SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad);
+
+        Subs.CVar(_cfg, CCVars.PreloadGrids, value => PreloadingEnabled = value, true);
     }
 
     private void OnRoundRestart(RoundRestartCleanupEvent ev)
@@ -52,7 +59,7 @@ public sealed class GridPreloaderSystem : SharedGridPreloaderSystem
         if (GetPreloaderEntity() != null)
             return;
 
-        if (!_cfg.GetCVar(CCVars.PreloadGrids))
+        if (!PreloadingEnabled)
             return;
 
         var mapUid = _map.CreateMap(out var mapId, false);
index 35dc646bce6795a5431170ff3dad84bd870fe637..06dfdd5276bef8fada5d66c9f4205bfa1e3e62b2 100644 (file)
@@ -108,13 +108,4 @@ public abstract class StationEventSystem<T> : GameRuleSystem<T> where T : ICompo
             }
         }
     }
-
-    #region Helper Functions
-
-    protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null)
-    {
-        GameTicker.EndGameRule(uid, component);
-    }
-
-    #endregion
 }
index f421cf68183c58c55328c6eee53204810ea65300..d3b0971be081c72d0ba494c5216e27e584e380b8 100644 (file)
     weight: 5.5
     minimumPlayers: 20
     duration: 1
+  - type: RuleGrids
   - type: LoadMapRule
     preloadedGrid: ShuttleStriker
   - type: NukeopsRule
index c190b0333ad69db514fe67d3c47d7333aab26d82..5a38c22215458bbe6d25105db05d329e807c21fd 100644 (file)
@@ -3,7 +3,6 @@
 - type: entity
   id: Ninja
   parent: BaseGameRule
-  noSpawn: true
   components:
   - type: GenericAntagRule
     agentName: ninja-round-end-agent-name
index 3493749457d52e8f1b4dfa3f3259c50b4ebfe39e..88f80fd4f6a2e391d91d2e47fabd4397fccc7c8d 100644 (file)
@@ -78,6 +78,7 @@
     - operationPrefix
     - operationSuffix
   - type: NukeopsRule
+  - type: RuleGrids
   - type: AntagSelection
   - type: AntagLoadProfileRule
 
index 359d7bffc0425a4dcbc21df11189cf87b07cc560..1ce365cd816b90d8452e877bbc5bae6e1a52d9bd 100644 (file)
@@ -1,7 +1,7 @@
-- type: entity
-  id: UnknownShuttleCargoLost
+- type: entity
+  abstract: true
   parent: BaseGameRule
-  noSpawn: true
+  id: BaseUnknownShuttleRule
   components:
   - type: StationEvent
     startAnnouncement: station-event-unknown-shuttle-incoming
     weight: 5
     reoccurrenceDelay: 30
     duration: 1
+  - type: RuleGrids
+  - type: LoadMapRule
+
+- type: entity
+  parent: BaseUnknownShuttleRule
+  id: UnknownShuttleCargoLost
+  components:
   - type: LoadMapRule
     preloadedGrid: ShuttleCargoLost
 
 - type: entity
+  parent: BaseUnknownShuttleRule
   id: UnknownShuttleTravelingCuisine
-  parent: BaseGameRule
-  noSpawn: true
   components:
-  - type: StationEvent
-    startAnnouncement: station-event-unknown-shuttle-incoming
-    startAudio:
-      path: /Audio/Announcements/attention.ogg
-    weight: 5
-    reoccurrenceDelay: 30
-    duration: 1
   - type: LoadMapRule
     preloadedGrid: TravelingCuisine
 
 - type: entity
+  parent: BaseUnknownShuttleRule
   id: UnknownShuttleDisasterEvacPod
-  parent: BaseGameRule
-  noSpawn: true
   components:
-  - type: StationEvent
-    startAnnouncement: station-event-unknown-shuttle-incoming
-    startAudio:
-      path: /Audio/Announcements/attention.ogg
-    weight: 5
-    reoccurrenceDelay: 30
-    duration: 1
   - type: LoadMapRule
     preloadedGrid: DisasterEvacPod
 
 - type: entity
+  parent: BaseUnknownShuttleRule
   id: UnknownShuttleHonki
-  parent: BaseGameRule
-  noSpawn: true
   components:
   - type: StationEvent
-    startAnnouncement: station-event-unknown-shuttle-incoming
-    startAudio:
-      path: /Audio/Announcements/attention.ogg
     weight: 2
-    reoccurrenceDelay: 30
-    duration: 1
   - type: LoadMapRule
     preloadedGrid: Honki
 
 - type: entity
+  parent: BaseUnknownShuttleRule
   id: UnknownShuttleSyndieEvacPod
-  parent: BaseGameRule
-  noSpawn: true
   components:
   - type: StationEvent
-    startAnnouncement: station-event-unknown-shuttle-incoming
-    startAudio:
-      path: /Audio/Announcements/attention.ogg
     weight: 2
-    reoccurrenceDelay: 30
-    duration: 1
   - type: LoadMapRule
     preloadedGrid: SyndieEvacPod