// 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));
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))
{
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();
+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;
/// <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;
}
--- /dev/null
+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;
+}
return found;
}
+ protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null)
+ {
+ GameTicker.EndGameRule(uid, component);
+ }
}
-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;
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);
}
}
{
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;
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);
// 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,
/// 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;
--- /dev/null
+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);
[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)
if (GetPreloaderEntity() != null)
return;
- if (!_cfg.GetCVar(CCVars.PreloadGrids))
+ if (!PreloadingEnabled)
return;
var mapUid = _map.CreateMap(out var mapId, false);
}
}
}
-
- #region Helper Functions
-
- protected void ForceEndSelf(EntityUid uid, GameRuleComponent? component = null)
- {
- GameTicker.EndGameRule(uid, component);
- }
-
- #endregion
}
weight: 5.5
minimumPlayers: 20
duration: 1
+ - type: RuleGrids
- type: LoadMapRule
preloadedGrid: ShuttleStriker
- type: NukeopsRule
- type: entity
id: Ninja
parent: BaseGameRule
- noSpawn: true
components:
- type: GenericAntagRule
agentName: ninja-round-end-agent-name
- operationPrefix
- operationSuffix
- type: NukeopsRule
+ - type: RuleGrids
- type: AntagSelection
- type: AntagLoadProfileRule
-- 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