Refactor map loading & saving
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Announcements;
|
||||
using Content.Server.Discord;
|
||||
using Content.Server.GameTicking.Events;
|
||||
@@ -13,10 +14,12 @@ using Content.Shared.Players;
|
||||
using Content.Shared.Preferences;
|
||||
using JetBrains.Annotations;
|
||||
using Prometheus;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
@@ -92,9 +95,6 @@ namespace Content.Server.GameTicking
|
||||
|
||||
AddGamePresetRules();
|
||||
|
||||
DefaultMap = _mapManager.CreateMap();
|
||||
_mapManager.AddUninitializedMap(DefaultMap);
|
||||
|
||||
var maps = new List<GameMapPrototype>();
|
||||
|
||||
// the map might have been force-set by something
|
||||
@@ -132,52 +132,203 @@ namespace Content.Server.GameTicking
|
||||
// Let game rules dictate what maps we should load.
|
||||
RaiseLocalEvent(new LoadingMapsEvent(maps));
|
||||
|
||||
foreach (var map in maps)
|
||||
if (maps.Count == 0)
|
||||
{
|
||||
var toLoad = DefaultMap;
|
||||
if (maps[0] != map)
|
||||
{
|
||||
// Create other maps for the others since we need to.
|
||||
toLoad = _mapManager.CreateMap();
|
||||
_mapManager.AddUninitializedMap(toLoad);
|
||||
}
|
||||
_map.CreateMap(out var mapId, runMapInit: false);
|
||||
DefaultMap = mapId;
|
||||
return;
|
||||
}
|
||||
|
||||
LoadGameMap(map, toLoad, null);
|
||||
for (var i = 0; i < maps.Count; i++)
|
||||
{
|
||||
LoadGameMap(maps[i], out var mapId);
|
||||
DebugTools.Assert(!_map.IsInitialized(mapId));
|
||||
|
||||
if (i == 0)
|
||||
DefaultMap = mapId;
|
||||
}
|
||||
}
|
||||
|
||||
public PreGameMapLoad RaisePreLoad(
|
||||
GameMapPrototype proto,
|
||||
DeserializationOptions? opts = null,
|
||||
Vector2? offset = null,
|
||||
Angle? rot = null)
|
||||
{
|
||||
offset ??= proto.MaxRandomOffset != 0f
|
||||
? _robustRandom.NextVector2(proto.MaxRandomOffset)
|
||||
: Vector2.Zero;
|
||||
|
||||
rot ??= proto.RandomRotation
|
||||
? _robustRandom.NextAngle()
|
||||
: Angle.Zero;
|
||||
|
||||
opts ??= DeserializationOptions.Default;
|
||||
var ev = new PreGameMapLoad(proto, opts.Value, offset.Value, rot.Value);
|
||||
RaiseLocalEvent(ev);
|
||||
return ev;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a new map, allowing systems interested in it to handle loading events.
|
||||
/// In the base game, this is required to be used if you want to load a station.
|
||||
/// This does not initialze maps, unles specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </summary>
|
||||
/// <param name="map">Game map prototype to load in.</param>
|
||||
/// <param name="targetMapId">Map to load into.</param>
|
||||
/// <param name="loadOptions">Map loading options, includes offset.</param>
|
||||
/// <remarks>
|
||||
/// This is basically a wrapper around a <see cref="MapLoaderSystem"/> method that auto generate
|
||||
/// some <see cref="MapLoadOptions"/> using information in a prototype, and raise some events to allow content
|
||||
/// to modify the options and react to the map creation.
|
||||
/// </remarks>
|
||||
/// <param name="proto">Game map prototype to load in.</param>
|
||||
/// <param name="mapId">The id of the map that was loaded.</param>
|
||||
/// <param name="options">Entity loading options, including whether the maps should be initialized.</param>
|
||||
/// <param name="stationName">Name to assign to the loaded station.</param>
|
||||
/// <returns>All loaded entities and grids.</returns>
|
||||
public IReadOnlyList<EntityUid> LoadGameMap(GameMapPrototype map, MapId targetMapId, MapLoadOptions? loadOptions, string? stationName = null)
|
||||
public IReadOnlyList<EntityUid> LoadGameMap(
|
||||
GameMapPrototype proto,
|
||||
out MapId mapId,
|
||||
DeserializationOptions? options = null,
|
||||
string? stationName = null,
|
||||
Vector2? offset = null,
|
||||
Angle? rot = null)
|
||||
{
|
||||
// Okay I specifically didn't set LoadMap here because this is typically called onto a new map.
|
||||
// whereas the command can also be used on an existing map.
|
||||
var loadOpts = loadOptions ?? new MapLoadOptions();
|
||||
var ev = RaisePreLoad(proto, options, offset, rot);
|
||||
|
||||
if (map.MaxRandomOffset != 0f)
|
||||
loadOpts.Offset = _robustRandom.NextVector2(map.MaxRandomOffset);
|
||||
if (ev.GameMap.IsGrid)
|
||||
{
|
||||
var mapUid = _map.CreateMap(out mapId);
|
||||
if (!_loader.TryLoadGrid(mapId,
|
||||
ev.GameMap.MapPath,
|
||||
out var grid,
|
||||
ev.Options,
|
||||
ev.Offset,
|
||||
ev.Rotation))
|
||||
{
|
||||
throw new Exception($"Failed to load game-map grid {ev.GameMap.ID}");
|
||||
}
|
||||
|
||||
if (map.RandomRotation)
|
||||
loadOpts.Rotation = _robustRandom.NextAngle();
|
||||
_metaData.SetEntityName(mapUid, proto.MapName);
|
||||
var g = new List<EntityUid> {grid.Value.Owner};
|
||||
RaiseLocalEvent(new PostGameMapLoad(proto, mapId, g, stationName));
|
||||
return g;
|
||||
}
|
||||
|
||||
var ev = new PreGameMapLoad(targetMapId, map, loadOpts);
|
||||
RaiseLocalEvent(ev);
|
||||
if (!_loader.TryLoadMap(ev.GameMap.MapPath,
|
||||
out var map,
|
||||
out var grids,
|
||||
ev.Options,
|
||||
ev.Offset,
|
||||
ev.Rotation))
|
||||
{
|
||||
throw new Exception($"Failed to load game map {ev.GameMap.ID}");
|
||||
}
|
||||
|
||||
var gridIds = _map.LoadMap(targetMapId, ev.GameMap.MapPath.ToString(), ev.Options);
|
||||
mapId = map.Value.Comp.MapId;
|
||||
_metaData.SetEntityName(map.Value.Owner, proto.MapName);
|
||||
var gridUids = grids.Select(x => x.Owner).ToList();
|
||||
RaiseLocalEvent(new PostGameMapLoad(proto, mapId, gridUids, stationName));
|
||||
return gridUids;
|
||||
}
|
||||
|
||||
_metaData.SetEntityName(_mapManager.GetMapEntityId(targetMapId), map.MapName);
|
||||
/// <summary>
|
||||
/// Variant of <see cref="LoadGameMap"/> that attempts to assign the provided <see cref="MapId"/> to the
|
||||
/// loaded map.
|
||||
/// </summary>
|
||||
public IReadOnlyList<EntityUid> LoadGameMapWithId(
|
||||
GameMapPrototype proto,
|
||||
MapId mapId,
|
||||
DeserializationOptions? opts = null,
|
||||
string? stationName = null,
|
||||
Vector2? offset = null,
|
||||
Angle? rot = null)
|
||||
{
|
||||
var ev = RaisePreLoad(proto, opts, offset, rot);
|
||||
|
||||
var gridUids = gridIds.ToList();
|
||||
RaiseLocalEvent(new PostGameMapLoad(map, targetMapId, gridUids, stationName));
|
||||
if (ev.GameMap.IsGrid)
|
||||
{
|
||||
var mapUid = _map.CreateMap(mapId);
|
||||
if (!_loader.TryLoadGrid(mapId,
|
||||
ev.GameMap.MapPath,
|
||||
out var grid,
|
||||
ev.Options,
|
||||
ev.Offset,
|
||||
ev.Rotation))
|
||||
{
|
||||
throw new Exception($"Failed to load game-map grid {ev.GameMap.ID}");
|
||||
}
|
||||
|
||||
_metaData.SetEntityName(mapUid, proto.MapName);
|
||||
var g = new List<EntityUid> {grid.Value.Owner};
|
||||
RaiseLocalEvent(new PostGameMapLoad(proto, mapId, g, stationName));
|
||||
return g;
|
||||
}
|
||||
|
||||
if (!_loader.TryLoadMapWithId(
|
||||
mapId,
|
||||
ev.GameMap.MapPath,
|
||||
out var map,
|
||||
out var grids,
|
||||
ev.Options,
|
||||
ev.Offset,
|
||||
ev.Rotation))
|
||||
{
|
||||
throw new Exception($"Failed to load map");
|
||||
}
|
||||
|
||||
_metaData.SetEntityName(map.Value.Owner, proto.MapName);
|
||||
var gridUids = grids.Select(x => x.Owner).ToList();
|
||||
RaiseLocalEvent(new PostGameMapLoad(proto, mapId, gridUids, stationName));
|
||||
return gridUids;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="LoadGameMap"/> that loads and then merges a game map onto an existing map.
|
||||
/// </summary>
|
||||
public IReadOnlyList<EntityUid> MergeGameMap(
|
||||
GameMapPrototype proto,
|
||||
MapId targetMap,
|
||||
DeserializationOptions? opts = null,
|
||||
string? stationName = null,
|
||||
Vector2? offset = null,
|
||||
Angle? rot = null)
|
||||
{
|
||||
// TODO MAP LOADING use a new event?
|
||||
// This is quite different from the other methods, which will actually create a **new** map.
|
||||
var ev = RaisePreLoad(proto, opts, offset, rot);
|
||||
|
||||
if (ev.GameMap.IsGrid)
|
||||
{
|
||||
if (!_loader.TryLoadGrid(targetMap,
|
||||
ev.GameMap.MapPath,
|
||||
out var grid,
|
||||
ev.Options,
|
||||
ev.Offset,
|
||||
ev.Rotation))
|
||||
{
|
||||
throw new Exception($"Failed to load game-map grid {ev.GameMap.ID}");
|
||||
}
|
||||
|
||||
var g = new List<EntityUid> {grid.Value.Owner};
|
||||
// TODO MAP LOADING use a new event?
|
||||
RaiseLocalEvent(new PostGameMapLoad(proto, targetMap, g, stationName));
|
||||
return g;
|
||||
}
|
||||
|
||||
if (!_loader.TryMergeMap(targetMap,
|
||||
ev.GameMap.MapPath,
|
||||
out var map,
|
||||
out var grids,
|
||||
ev.Options,
|
||||
ev.Offset,
|
||||
ev.Rotation))
|
||||
{
|
||||
throw new Exception($"Failed to load map");
|
||||
}
|
||||
|
||||
var gridUids = grids.Select(x => x.Owner).ToList();
|
||||
|
||||
// TODO MAP LOADING use a new event?
|
||||
RaiseLocalEvent(new PostGameMapLoad(proto, targetMap, gridUids, stationName));
|
||||
return gridUids;
|
||||
}
|
||||
|
||||
@@ -274,7 +425,7 @@ namespace Content.Server.GameTicking
|
||||
}
|
||||
|
||||
// MapInitialize *before* spawning players, our codebase is too shit to do it afterwards...
|
||||
_mapManager.DoMapInitialize(DefaultMap);
|
||||
_map.InitializeMap(DefaultMap);
|
||||
|
||||
SpawnPlayers(readyPlayers, readyPlayerProfiles, force);
|
||||
|
||||
@@ -714,21 +865,14 @@ namespace Content.Server.GameTicking
|
||||
/// You likely want to subscribe to this after StationSystem.
|
||||
/// </remarks>
|
||||
[PublicAPI]
|
||||
public sealed class PreGameMapLoad : EntityEventArgs
|
||||
public sealed class PreGameMapLoad(GameMapPrototype gameMap, DeserializationOptions options, Vector2 offset, Angle rotation) : EntityEventArgs
|
||||
{
|
||||
public readonly MapId Map;
|
||||
public GameMapPrototype GameMap;
|
||||
public MapLoadOptions Options;
|
||||
|
||||
public PreGameMapLoad(MapId map, GameMapPrototype gameMap, MapLoadOptions options)
|
||||
{
|
||||
Map = map;
|
||||
GameMap = gameMap;
|
||||
Options = options;
|
||||
}
|
||||
public readonly GameMapPrototype GameMap = gameMap;
|
||||
public DeserializationOptions Options = options;
|
||||
public Vector2 Offset = offset;
|
||||
public Angle Rotation = rotation;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event raised after the game loads a given map.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,6 +18,7 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameStates;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
@@ -48,7 +49,8 @@ namespace Content.Server.GameTicking
|
||||
[Dependency] private readonly IServerPreferencesManager _prefsManager = default!;
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _map = default!;
|
||||
[Dependency] private readonly MapLoaderSystem _loader = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||
[Dependency] private readonly PlayTimeTrackingSystem _playTimeTrackings = default!;
|
||||
|
||||
@@ -20,11 +20,17 @@ public sealed partial class LoadMapRuleComponent : Component
|
||||
public ProtoId<GameMapPrototype>? GameMap;
|
||||
|
||||
/// <summary>
|
||||
/// A map path to load on a new map.
|
||||
/// A map to load.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ResPath? MapPath;
|
||||
|
||||
/// <summary>
|
||||
/// A grid to load on a new map.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ResPath? GridPath;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="PreloadedGridPrototype"/> to move to a new map.
|
||||
/// If there are no instances left nothing is done.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.GridPreloader;
|
||||
using Content.Server.StationEvents.Events;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
@@ -26,29 +30,50 @@ public sealed class LoadMapRuleSystem : StationEventSystem<LoadMapRuleComponent>
|
||||
return;
|
||||
}
|
||||
|
||||
// grid preloading needs map to init after moving it
|
||||
var mapUid = _map.CreateMap(out var mapId, runMapInit: comp.PreloadedGrid == null);
|
||||
|
||||
Log.Info($"Created map {mapId} for {ToPrettyString(uid):rule}");
|
||||
|
||||
MapId mapId;
|
||||
IReadOnlyList<EntityUid> grids;
|
||||
if (comp.GameMap != null)
|
||||
{
|
||||
// Component has one of three modes, only one of the three fields should ever be populated.
|
||||
DebugTools.AssertNull(comp.MapPath);
|
||||
DebugTools.AssertNull(comp.GridPath);
|
||||
DebugTools.AssertNull(comp.PreloadedGrid);
|
||||
|
||||
var gameMap = _prototypeManager.Index(comp.GameMap.Value);
|
||||
grids = GameTicker.LoadGameMap(gameMap, mapId, new MapLoadOptions());
|
||||
grids = GameTicker.LoadGameMap(gameMap, out mapId, null);
|
||||
Log.Info($"Created map {mapId} for {ToPrettyString(uid):rule}");
|
||||
}
|
||||
else if (comp.MapPath is {} path)
|
||||
{
|
||||
var options = new MapLoadOptions { LoadMap = true };
|
||||
if (!_mapLoader.TryLoad(mapId, path.ToString(), out var roots, options))
|
||||
DebugTools.AssertNull(comp.GridPath);
|
||||
DebugTools.AssertNull(comp.PreloadedGrid);
|
||||
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
if (!_mapLoader.TryLoadMap(path, out var map, out var gridSet, opts))
|
||||
{
|
||||
Log.Error($"Failed to load map from {path}!");
|
||||
Del(mapUid);
|
||||
ForceEndSelf(uid, rule);
|
||||
return;
|
||||
}
|
||||
|
||||
grids = roots;
|
||||
grids = gridSet.Select( x => x.Owner).ToList();
|
||||
mapId = map.Value.Comp.MapId;
|
||||
}
|
||||
else if (comp.GridPath is { } gPath)
|
||||
{
|
||||
DebugTools.AssertNull(comp.PreloadedGrid);
|
||||
|
||||
// I fucking love it when "map paths" choses to ar
|
||||
_map.CreateMap(out mapId);
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
if (!_mapLoader.TryLoadGrid(mapId, gPath, out var grid, opts))
|
||||
{
|
||||
Log.Error($"Failed to load grid from {gPath}!");
|
||||
ForceEndSelf(uid, rule);
|
||||
return;
|
||||
}
|
||||
|
||||
grids = new List<EntityUid> {grid.Value.Owner};
|
||||
}
|
||||
else if (comp.PreloadedGrid is {} preloaded)
|
||||
{
|
||||
@@ -56,11 +81,11 @@ public sealed class LoadMapRuleSystem : StationEventSystem<LoadMapRuleComponent>
|
||||
if (!_gridPreloader.TryGetPreloadedGrid(preloaded, out var loadedShuttle))
|
||||
{
|
||||
Log.Error($"Failed to get a preloaded grid with {preloaded}!");
|
||||
Del(mapUid);
|
||||
ForceEndSelf(uid, rule);
|
||||
return;
|
||||
}
|
||||
|
||||
var mapUid = _map.CreateMap(out mapId, runMapInit: false);
|
||||
_transform.SetParent(loadedShuttle.Value, mapUid);
|
||||
grids = new List<EntityUid>() { loadedShuttle.Value };
|
||||
_map.InitializeMap(mapUid);
|
||||
@@ -68,7 +93,6 @@ public sealed class LoadMapRuleSystem : StationEventSystem<LoadMapRuleComponent>
|
||||
else
|
||||
{
|
||||
Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}");
|
||||
Del(mapUid);
|
||||
ForceEndSelf(uid, rule);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user