LoadMapRule grid storage rework (#28210)

*
This commit is contained in:
deltanedas
2024-06-04 00:04:19 +00:00
committed by GitHub
parent 7d22897d02
commit 16b3fb1204
13 changed files with 190 additions and 130 deletions

View 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();

View 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;
}

View File

@@ -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;
}

View 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);
}
}

View 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);
}
}

View 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;

View File

@@ -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);

View 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);

View 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
}

View File

@@ -422,6 +422,7 @@
weight: 5.5
minimumPlayers: 20
duration: 1
- type: RuleGrids
- type: LoadMapRule
preloadedGrid: ShuttleStriker
- type: NukeopsRule

View File

@@ -3,7 +3,6 @@
- type: entity
id: Ninja
parent: BaseGameRule
noSpawn: true
components:
- type: GenericAntagRule
agentName: ninja-round-end-agent-name

View File

@@ -78,6 +78,7 @@
- operationPrefix
- operationSuffix
- type: NukeopsRule
- type: RuleGrids
- type: AntagSelection
- type: AntagLoadProfileRule

View 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
@@ -10,65 +10,44 @@
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