diff --git a/Content.Benchmarks/ComponentQueryBenchmark.cs b/Content.Benchmarks/ComponentQueryBenchmark.cs index 11c7ab9d5f..49e9984c09 100644 --- a/Content.Benchmarks/ComponentQueryBenchmark.cs +++ b/Content.Benchmarks/ComponentQueryBenchmark.cs @@ -9,13 +9,14 @@ using Content.IntegrationTests.Pair; using Content.Shared.Clothing.Components; using Content.Shared.Doors.Components; using Content.Shared.Item; -using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Benchmarks; @@ -32,7 +33,6 @@ public class ComponentQueryBenchmark private TestPair _pair = default!; private IEntityManager _entMan = default!; - private MapId _mapId = new(10); private EntityQuery _itemQuery; private EntityQuery _clothingQuery; private EntityQuery _mapQuery; @@ -54,10 +54,10 @@ public class ComponentQueryBenchmark _pair.Server.ResolveDependency().SetSeed(42); _pair.Server.WaitPost(() => { - var success = _entMan.System().TryLoad(_mapId, Map, out _); - if (!success) + var map = new ResPath(Map); + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + if (!_entMan.System().TryLoadMap(map, out _, out _, opts)) throw new Exception("Map load failed"); - _pair.Server.MapMan.DoMapInitialize(_mapId); }).GetAwaiter().GetResult(); _items = new EntityUid[_entMan.Count()]; diff --git a/Content.Benchmarks/MapLoadBenchmark.cs b/Content.Benchmarks/MapLoadBenchmark.cs index 8182ba8ec6..2bb71bb59d 100644 --- a/Content.Benchmarks/MapLoadBenchmark.cs +++ b/Content.Benchmarks/MapLoadBenchmark.cs @@ -6,12 +6,13 @@ using BenchmarkDotNet.Attributes; using Content.IntegrationTests; using Content.IntegrationTests.Pair; using Content.Server.Maps; -using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Benchmarks; @@ -20,7 +21,7 @@ public class MapLoadBenchmark { private TestPair _pair = default!; private MapLoaderSystem _mapLoader = default!; - private IMapManager _mapManager = default!; + private SharedMapSystem _mapSys = default!; [GlobalSetup] public void Setup() @@ -36,7 +37,7 @@ public class MapLoadBenchmark .ToDictionary(x => x.ID, x => x.MapPath.ToString()); _mapLoader = server.ResolveDependency().GetEntitySystem(); - _mapManager = server.ResolveDependency(); + _mapSys = server.ResolveDependency().GetEntitySystem(); } [GlobalCleanup] @@ -52,17 +53,19 @@ public class MapLoadBenchmark public string Map; public Dictionary Paths; + private MapId _mapId; [Benchmark] public async Task LoadMap() { - var mapPath = Paths[Map]; + var mapPath = new ResPath(Paths[Map]); var server = _pair.Server; await server.WaitPost(() => { - var success = _mapLoader.TryLoad(new MapId(10), mapPath, out _); + var success = _mapLoader.TryLoadMap(mapPath, out var map, out _); if (!success) throw new Exception("Map load failed"); + _mapId = map.Value.Comp.MapId; }); } @@ -70,9 +73,7 @@ public class MapLoadBenchmark public void IterationCleanup() { var server = _pair.Server; - server.WaitPost(() => - { - _mapManager.DeleteMap(new MapId(10)); - }).Wait(); + server.WaitPost(() => _mapSys.DeleteMap(_mapId)) + .Wait(); } } diff --git a/Content.Benchmarks/PvsBenchmark.cs b/Content.Benchmarks/PvsBenchmark.cs index fa7f9d4542..2f87545426 100644 --- a/Content.Benchmarks/PvsBenchmark.cs +++ b/Content.Benchmarks/PvsBenchmark.cs @@ -7,13 +7,15 @@ using Content.IntegrationTests; using Content.IntegrationTests.Pair; using Content.Server.Mind; using Content.Server.Warps; -using Robust.Server.GameObjects; using Robust.Shared; using Robust.Shared.Analyzers; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Benchmarks; @@ -34,7 +36,6 @@ public class PvsBenchmark private TestPair _pair = default!; private IEntityManager _entMan = default!; - private MapId _mapId = new(10); private ICommonSession[] _players = default!; private EntityCoordinates[] _spawns = default!; public int _cycleOffset = 0; @@ -65,10 +66,10 @@ public class PvsBenchmark _pair.Server.ResolveDependency().SetSeed(42); await _pair.Server.WaitPost(() => { - var success = _entMan.System().TryLoad(_mapId, Map, out _); - if (!success) + var path = new ResPath(Map); + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + if (!_entMan.System().TryLoadMap(path, out _, out _, opts)) throw new Exception("Map load failed"); - _pair.Server.MapMan.DoMapInitialize(_mapId); }); // Get list of ghost warp positions diff --git a/Content.IntegrationTests/Tests/Body/LungTest.cs b/Content.IntegrationTests/Tests/Body/LungTest.cs index 9b5ee431f1..8ac3a3021f 100644 --- a/Content.IntegrationTests/Tests/Body/LungTest.cs +++ b/Content.IntegrationTests/Tests/Body/LungTest.cs @@ -11,6 +11,8 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using System.Linq; using System.Numerics; +using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.Body { @@ -57,7 +59,6 @@ namespace Content.IntegrationTests.Tests.Body await server.WaitIdleAsync(); - var mapManager = server.ResolveDependency(); var entityManager = server.ResolveDependency(); var mapLoader = entityManager.System(); var mapSys = entityManager.System(); @@ -69,17 +70,13 @@ namespace Content.IntegrationTests.Tests.Body GridAtmosphereComponent relevantAtmos = default; var startingMoles = 0.0f; - var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml"; + var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml"); await server.WaitPost(() => { mapSys.CreateMap(out var mapId); - Assert.That(mapLoader.TryLoad(mapId, testMapName, out var roots)); - - var query = entityManager.GetEntityQuery(); - var grids = roots.Where(x => query.HasComponent(x)); - Assert.That(grids, Is.Not.Empty); - grid = grids.First(); + Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt)); + grid = gridEnt!.Value.Owner; }); Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found."); @@ -148,18 +145,13 @@ namespace Content.IntegrationTests.Tests.Body RespiratorComponent respirator = null; EntityUid human = default; - var testMapName = "Maps/Test/Breathing/3by3-20oxy-80nit.yml"; + var testMapName = new ResPath("Maps/Test/Breathing/3by3-20oxy-80nit.yml"); await server.WaitPost(() => { mapSys.CreateMap(out var mapId); - - Assert.That(mapLoader.TryLoad(mapId, testMapName, out var ents), Is.True); - var query = entityManager.GetEntityQuery(); - grid = ents - .Select(x => x) - .FirstOrDefault((uid) => uid.HasValue && query.HasComponent(uid.Value), null); - Assert.That(grid, Is.Not.Null); + Assert.That(mapLoader.TryLoadGrid(mapId, testMapName, out var gridEnt)); + grid = gridEnt!.Value.Owner; }); Assert.That(grid, Is.Not.Null, $"Test blueprint {testMapName} not found."); diff --git a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs index 01482ba8ee..67163d0796 100644 --- a/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs +++ b/Content.IntegrationTests/Tests/Body/SaveLoadReparentTest.cs @@ -2,10 +2,11 @@ using System.Linq; using Content.Shared.Body.Components; using Content.Shared.Body.Systems; -using Robust.Server.GameObjects; using Robust.Shared.Containers; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; +using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.Body; @@ -111,13 +112,12 @@ public sealed class SaveLoadReparentTest Is.Not.Empty ); - const string mapPath = $"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml"; + var mapPath = new ResPath($"/{nameof(SaveLoadReparentTest)}{nameof(Test)}map.yml"); - mapLoader.SaveMap(mapId, mapPath); - maps.DeleteMap(mapId); + Assert.That(mapLoader.TrySaveMap(mapId, mapPath)); + mapSys.DeleteMap(mapId); - mapSys.CreateMap(out mapId); - Assert.That(mapLoader.TryLoad(mapId, mapPath, out _), Is.True); + Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _), Is.True); var query = EnumerateQueryEnumerator( entities.EntityQueryEnumerator() @@ -173,7 +173,7 @@ public sealed class SaveLoadReparentTest }); } - maps.DeleteMap(mapId); + entities.DeleteEntity(map); } }); diff --git a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs index 7bc62dfe2b..ad4ddc2612 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTest.DeleteAllThenGhost.cs @@ -1,5 +1,6 @@ #nullable enable using Robust.Shared.Console; +using Robust.Shared.GameObjects; using Robust.Shared.Map; namespace Content.IntegrationTests.Tests.Minds; @@ -37,8 +38,8 @@ public sealed partial class MindTests Assert.That(pair.Client.EntMan.EntityCount, Is.EqualTo(0)); // Create a new map. - int mapId = 1; - await pair.Server.WaitPost(() => conHost.ExecuteCommand($"addmap {mapId}")); + MapId mapId = default; + await pair.Server.WaitPost(() => pair.Server.System().CreateMap(out mapId)); await pair.RunTicksSync(5); // Client is not attached to anything @@ -54,7 +55,7 @@ public sealed partial class MindTests Assert.That(pair.Client.EntMan.EntityExists(pair.Client.AttachedEntity)); Assert.That(pair.Server.EntMan.EntityExists(pair.PlayerData?.Mind)); var xform = pair.Client.Transform(pair.Client.AttachedEntity!.Value); - Assert.That(xform.MapID, Is.EqualTo(new MapId(mapId))); + Assert.That(xform.MapID, Is.EqualTo(mapId)); await pair.CleanReturnAsync(); } diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index 4a22092e70..94214c609a 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -9,7 +9,6 @@ using Content.Server.Spawners.Components; using Content.Server.Station.Components; using Content.Shared.CCVar; using Content.Shared.Roles; -using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; @@ -17,6 +16,9 @@ using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Content.Shared.Station.Components; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.IoC; using Robust.Shared.Utility; using YamlDotNet.RepresentationModel; @@ -36,10 +38,7 @@ namespace Content.IntegrationTests.Tests private static readonly string[] Grids = { - "/Maps/centcomm.yml", - "/Maps/Shuttles/cargo.yml", - "/Maps/Shuttles/emergency.yml", - "/Maps/Shuttles/infiltrator.yml", + "/Maps/centcomm.yml" }; private static readonly string[] GameMaps = @@ -81,33 +80,72 @@ namespace Content.IntegrationTests.Tests var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); var mapSystem = entManager.System(); - var mapManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + var path = new ResPath(mapFile); await server.WaitPost(() => { mapSystem.CreateMap(out var mapId); try { -#pragma warning disable NUnit2045 - Assert.That(mapLoader.TryLoad(mapId, mapFile, out var roots)); - Assert.That(roots.Where(uid => entManager.HasComponent(uid)), Is.Not.Empty); -#pragma warning restore NUnit2045 + Assert.That(mapLoader.TryLoadGrid(mapId, path, out var grid)); } catch (Exception ex) { throw new Exception($"Failed to load map {mapFile}, was it saved as a map instead of a grid?", ex); } - try + mapSystem.DeleteMap(mapId); + }); + await server.WaitRunTicks(1); + + await pair.CleanReturnAsync(); + } + + /// + /// Asserts that shuttles are loadable and have been saved as grids and not maps. + /// + [Test] + public async Task ShuttlesLoadableTest() + { + await using var pair = await PoolManager.GetServerClient(); + var server = pair.Server; + + var entManager = server.ResolveDependency(); + var resMan = server.ResolveDependency(); + var mapLoader = entManager.System(); + var mapSystem = entManager.System(); + var cfg = server.ResolveDependency(); + Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + + var shuttleFolder = new ResPath("/Maps/Shuttles"); + var shuttles = resMan + .ContentFindFiles(shuttleFolder) + .Where(filePath => + filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal)) + .ToArray(); + + await server.WaitPost(() => + { + Assert.Multiple(() => { - mapManager.DeleteMap(mapId); - } - catch (Exception ex) - { - throw new Exception($"Failed to delete map {mapFile}", ex); - } + foreach (var path in shuttles) + { + mapSystem.CreateMap(out var mapId); + try + { + Assert.That(mapLoader.TryLoadGrid(mapId, path, out _), + $"Failed to load shuttle {path}, was it saved as a map instead of a grid?"); + } + catch (Exception ex) + { + throw new Exception($"Failed to load shuttle {path}, was it saved as a map instead of a grid?", + ex); + } + mapSystem.DeleteMap(mapId); + } + }); }); await server.WaitRunTicks(1); @@ -121,12 +159,15 @@ namespace Content.IntegrationTests.Tests var server = pair.Server; var resourceManager = server.ResolveDependency(); + var loader = server.System(); + var mapFolder = new ResPath("/Maps"); var maps = resourceManager .ContentFindFiles(mapFolder) .Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal)) .ToArray(); + var v7Maps = new List(); foreach (var map in maps) { var rootedPath = map.ToRootedPath(); @@ -149,13 +190,69 @@ namespace Content.IntegrationTests.Tests var root = yamlStream.Documents[0].RootNode; var meta = root["meta"]; - var postMapInit = meta["postmapinit"].AsBool(); + var version = meta["format"].AsInt(); + if (version >= 7) + { + v7Maps.Add(map); + continue; + } + + var postMapInit = meta["postmapinit"].AsBool(); Assert.That(postMapInit, Is.False, $"Map {map.Filename} was saved postmapinit"); } + + var deps = server.ResolveDependency().DependencyCollection; + foreach (var map in v7Maps) + { + Assert.That(IsPreInit(map, loader, deps)); + } + + // Check that the test actually does manage to catch post-init maps and isn't just blindly passing everything. + // To that end, create a new post-init map and try verify it. + var mapSys = server.System(); + MapId id = default; + await server.WaitPost(() => mapSys.CreateMap(out id, runMapInit: false)); + await server.WaitPost(() => server.EntMan.Spawn(null, new MapCoordinates(0, 0, id))); + + // First check that a pre-init version passes + var path = new ResPath($"{nameof(NoSavedPostMapInitTest)}.yml"); + Assert.That(loader.TrySaveMap(id, path)); + Assert.That(IsPreInit(path, loader, deps)); + + // and the post-init version fails. + await server.WaitPost(() => mapSys.InitializeMap(id)); + Assert.That(loader.TrySaveMap(id, path)); + Assert.That(IsPreInit(path, loader, deps), Is.False); + await pair.CleanReturnAsync(); } + private bool IsPreInit(ResPath map, MapLoaderSystem loader, IDependencyCollection deps) + { + if (!loader.TryReadFile(map, out var data)) + { + Assert.Fail($"Failed to read {map}"); + return false; + } + + var reader = new EntityDeserializer(deps, data, DeserializationOptions.Default); + if (!reader.TryProcessData()) + { + Assert.Fail($"Failed to process {map}"); + return false; + } + + foreach (var mapId in reader.MapYamlIds) + { + var mapData = reader.YamlEntities[mapId]; + if (mapData.PostInit) + return false; + } + + return true; + } + [Test, TestCaseSource(nameof(GameMaps))] public async Task GameMapsLoadableTest(string mapProto) { @@ -172,16 +269,16 @@ namespace Content.IntegrationTests.Tests var protoManager = server.ResolveDependency(); var ticker = entManager.EntitySysManager.GetEntitySystem(); var shuttleSystem = entManager.EntitySysManager.GetEntitySystem(); - var xformQuery = entManager.GetEntityQuery(); var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); await server.WaitPost(() => { - mapSystem.CreateMap(out var mapId); + MapId mapId; try { - ticker.LoadGameMap(protoManager.Index(mapProto), mapId, null); + var opts = DeserializationOptions.Default with {InitializeMaps = true}; + ticker.LoadGameMap(protoManager.Index(mapProto), out mapId, opts); } catch (Exception ex) { @@ -218,21 +315,17 @@ namespace Content.IntegrationTests.Tests if (entManager.TryGetComponent(station, out var stationEvac)) { var shuttlePath = stationEvac.EmergencyShuttlePath; -#pragma warning disable NUnit2045 - Assert.That(mapLoader.TryLoad(shuttleMap, shuttlePath.ToString(), out var roots)); - EntityUid shuttle = default!; - Assert.DoesNotThrow(() => - { - shuttle = roots.First(uid => entManager.HasComponent(uid)); - }, $"Failed to load {shuttlePath}"); + Assert.That(mapLoader.TryLoadGrid(shuttleMap, shuttlePath, out var shuttle), + $"Failed to load {shuttlePath}"); + Assert.That( - shuttleSystem.TryFTLDock(shuttle, - entManager.GetComponent(shuttle), targetGrid.Value), + shuttleSystem.TryFTLDock(shuttle!.Value.Owner, + entManager.GetComponent(shuttle!.Value.Owner), + targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}"); -#pragma warning restore NUnit2045 } - mapManager.DeleteMap(shuttleMap); + mapSystem.DeleteMap(shuttleMap); if (entManager.HasComponent(station)) { @@ -269,7 +362,7 @@ namespace Content.IntegrationTests.Tests try { - mapManager.DeleteMap(mapId); + mapSystem.DeleteMap(mapId); } catch (Exception ex) { @@ -333,11 +426,9 @@ namespace Content.IntegrationTests.Tests var server = pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); - var mapManager = server.ResolveDependency(); var resourceManager = server.ResolveDependency(); var protoManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); - var mapSystem = server.System(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); var gameMaps = protoManager.EnumeratePrototypes().Select(o => o.MapPath).ToHashSet(); @@ -348,7 +439,7 @@ namespace Content.IntegrationTests.Tests .Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith(".", StringComparison.Ordinal)) .ToArray(); - var mapNames = new List(); + var mapPaths = new List(); foreach (var map in maps) { if (gameMaps.Contains(map)) @@ -359,32 +450,46 @@ namespace Content.IntegrationTests.Tests { continue; } - mapNames.Add(rootedPath.ToString()); + mapPaths.Add(rootedPath); } await server.WaitPost(() => { Assert.Multiple(() => { - foreach (var mapName in mapNames) + // This bunch of files contains a random mixture of both map and grid files. + // TODO MAPPING organize files + var opts = MapLoadOptions.Default with + { + DeserializationOptions = DeserializationOptions.Default with + { + InitializeMaps = true, + LogOrphanedGrids = false + } + }; + + HashSet> maps; + foreach (var path in mapPaths) { - mapSystem.CreateMap(out var mapId); try { - Assert.That(mapLoader.TryLoad(mapId, mapName, out _)); + Assert.That(mapLoader.TryLoadGeneric(path, out maps, out _, opts)); } catch (Exception ex) { - throw new Exception($"Failed to load map {mapName}", ex); + throw new Exception($"Failed to load map {path}", ex); } try { - mapManager.DeleteMap(mapId); + foreach (var map in maps) + { + server.EntMan.DeleteEntity(map); + } } catch (Exception ex) { - throw new Exception($"Failed to delete map {mapName}", ex); + throw new Exception($"Failed to delete map {path}", ex); } } }); diff --git a/Content.IntegrationTests/Tests/SalvageTest.cs b/Content.IntegrationTests/Tests/SalvageTest.cs index 5dfba82308..0059db6292 100644 --- a/Content.IntegrationTests/Tests/SalvageTest.cs +++ b/Content.IntegrationTests/Tests/SalvageTest.cs @@ -1,11 +1,8 @@ -using System.Linq; -using Content.Shared.CCVar; +using Content.Shared.CCVar; using Content.Shared.Salvage; -using Robust.Server.GameObjects; using Robust.Shared.Configuration; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests; @@ -24,7 +21,6 @@ public sealed class SalvageTest var entManager = server.ResolveDependency(); var mapLoader = entManager.System(); - var mapManager = server.ResolveDependency(); var prototypeManager = server.ResolveDependency(); var cfg = server.ResolveDependency(); var mapSystem = entManager.System(); @@ -34,13 +30,10 @@ public sealed class SalvageTest { foreach (var salvage in prototypeManager.EnumeratePrototypes()) { - var mapFile = salvage.MapPath; - mapSystem.CreateMap(out var mapId); try { - Assert.That(mapLoader.TryLoad(mapId, mapFile.ToString(), out var roots)); - Assert.That(roots.Where(uid => entManager.HasComponent(uid)), Is.Not.Empty); + Assert.That(mapLoader.TryLoadGrid(mapId, salvage.MapPath, out var grid)); } catch (Exception ex) { @@ -49,7 +42,7 @@ public sealed class SalvageTest try { - mapManager.DeleteMap(mapId); + mapSystem.DeleteMap(mapId); } catch (Exception ex) { diff --git a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs index 1bfcd8ab60..eb3dc14720 100644 --- a/Content.IntegrationTests/Tests/SaveLoadMapTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadMapTest.cs @@ -3,6 +3,7 @@ using Content.Shared.CCVar; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -16,7 +17,7 @@ namespace Content.IntegrationTests.Tests [Test] public async Task SaveLoadMultiGridMap() { - const string mapPath = @"/Maps/Test/TestMap.yml"; + var mapPath = new ResPath("/Maps/Test/TestMap.yml"); await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; @@ -31,7 +32,7 @@ namespace Content.IntegrationTests.Tests await server.WaitAssertion(() => { - var dir = new ResPath(mapPath).Directory; + var dir = mapPath.Directory; resManager.UserData.CreateDir(dir); mapSystem.CreateMap(out var mapId); @@ -47,15 +48,17 @@ namespace Content.IntegrationTests.Tests mapSystem.SetTile(mapGrid, new Vector2i(0, 0), new Tile(typeId: 2, flags: 1, variant: 254)); } - Assert.Multiple(() => mapLoader.SaveMap(mapId, mapPath)); - Assert.Multiple(() => mapManager.DeleteMap(mapId)); + Assert.That(mapLoader.TrySaveMap(mapId, mapPath)); + mapSystem.DeleteMap(mapId); }); await server.WaitIdleAsync(); + MapId newMap = default; await server.WaitAssertion(() => { - Assert.That(mapLoader.TryLoad(new MapId(10), mapPath, out _)); + Assert.That(mapLoader.TryLoadMap(mapPath, out var map, out _)); + newMap = map!.Value.Comp.MapId; }); await server.WaitIdleAsync(); @@ -63,7 +66,7 @@ namespace Content.IntegrationTests.Tests await server.WaitAssertion(() => { { - if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(10, 10), out var gridUid, out var mapGrid) || + if (!mapManager.TryFindGridAt(newMap, new Vector2(10, 10), out var gridUid, out var mapGrid) || !sEntities.TryGetComponent(gridUid, out var gridXform)) { Assert.Fail(); @@ -77,7 +80,7 @@ namespace Content.IntegrationTests.Tests }); } { - if (!mapManager.TryFindGridAt(new MapId(10), new Vector2(-8, -8), out var gridUid, out var mapGrid) || + if (!mapManager.TryFindGridAt(newMap, new Vector2(-8, -8), out var gridUid, out var mapGrid) || !sEntities.TryGetComponent(gridUid, out var gridXform)) { Assert.Fail(); diff --git a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs index 4facd5ee40..b41aa0bf2f 100644 --- a/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs +++ b/Content.IntegrationTests/Tests/SaveLoadSaveTest.cs @@ -1,25 +1,25 @@ using System.IO; using System.Linq; using Content.Shared.CCVar; -using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; -using Robust.Shared.Map.Components; +using Robust.Shared.Map.Events; +using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests { /// - /// Tests that a map's yaml does not change when saved consecutively. + /// Tests that a grid's yaml does not change when saved consecutively. /// [TestFixture] public sealed class SaveLoadSaveTest { [Test] - public async Task SaveLoadSave() + public async Task CreateSaveLoadSaveGrid() { await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; @@ -30,22 +30,21 @@ namespace Content.IntegrationTests.Tests var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + var testSystem = server.System(); + testSystem.Enabled = true; + + var rp1 = new ResPath("/save load save 1.yml"); + var rp2 = new ResPath("/save load save 2.yml"); + await server.WaitPost(() => { mapSystem.CreateMap(out var mapId0); - // TODO: Properly find the "main" station grid. var grid0 = mapManager.CreateGridEntity(mapId0); - mapLoader.Save(grid0.Owner, "save load save 1.yml"); + entManager.RunMapInit(grid0.Owner, entManager.GetComponent(grid0)); + Assert.That(mapLoader.TrySaveGrid(grid0.Owner, rp1)); mapSystem.CreateMap(out var mapId1); - EntityUid grid1 = default!; -#pragma warning disable NUnit2045 - Assert.That(mapLoader.TryLoad(mapId1, "save load save 1.yml", out var roots, new MapLoadOptions() { LoadMap = false }), $"Failed to load test map {TestMap}"); - Assert.DoesNotThrow(() => - { - grid1 = roots.First(uid => entManager.HasComponent(uid)); - }); -#pragma warning restore NUnit2045 - mapLoader.Save(grid1, "save load save 2.yml"); + Assert.That(mapLoader.TryLoadGrid(mapId1, rp1, out var grid1)); + Assert.That(mapLoader.TrySaveGrid(grid1!.Value, rp2)); }); await server.WaitIdleAsync(); @@ -54,14 +53,12 @@ namespace Content.IntegrationTests.Tests string one; string two; - var rp1 = new ResPath("/save load save 1.yml"); await using (var stream = userData.Open(rp1, FileMode.Open)) using (var reader = new StreamReader(stream)) { one = await reader.ReadToEndAsync(); } - var rp2 = new ResPath("/save load save 2.yml"); await using (var stream = userData.Open(rp2, FileMode.Open)) using (var reader = new StreamReader(stream)) { @@ -87,6 +84,7 @@ namespace Content.IntegrationTests.Tests TestContext.Error.WriteLine(twoTmp); } }); + testSystem.Enabled = false; await pair.CleanReturnAsync(); } @@ -101,8 +99,12 @@ namespace Content.IntegrationTests.Tests await using var pair = await PoolManager.GetServerClient(); var server = pair.Server; var mapLoader = server.ResolveDependency().GetEntitySystem(); - var mapManager = server.ResolveDependency(); - var mapSystem = server.System(); + var mapSys = server.System(); + var testSystem = server.System(); + testSystem.Enabled = true; + + var rp1 = new ResPath("/load save ticks save 1.yml"); + var rp2 = new ResPath("/load save ticks save 2.yml"); MapId mapId = default; var cfg = server.ResolveDependency(); @@ -111,10 +113,10 @@ namespace Content.IntegrationTests.Tests // Load bagel.yml as uninitialized map, and save it to ensure it's up to date. server.Post(() => { - mapSystem.CreateMap(out mapId, runMapInit: false); - mapManager.SetMapPaused(mapId, true); - Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); - mapLoader.SaveMap(mapId, "load save ticks save 1.yml"); + var path = new ResPath(TestMap); + Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); + mapId = map!.Value.Comp.MapId; + Assert.That(mapLoader.TrySaveMap(mapId, rp1)); }); // Run 5 ticks. @@ -122,7 +124,7 @@ namespace Content.IntegrationTests.Tests await server.WaitPost(() => { - mapLoader.SaveMap(mapId, "/load save ticks save 2.yml"); + Assert.That(mapLoader.TrySaveMap(mapId, rp2)); }); await server.WaitIdleAsync(); @@ -131,13 +133,13 @@ namespace Content.IntegrationTests.Tests string one; string two; - await using (var stream = userData.Open(new ResPath("/load save ticks save 1.yml"), FileMode.Open)) + await using (var stream = userData.Open(rp1, FileMode.Open)) using (var reader = new StreamReader(stream)) { one = await reader.ReadToEndAsync(); } - await using (var stream = userData.Open(new ResPath("/load save ticks save 2.yml"), FileMode.Open)) + await using (var stream = userData.Open(rp2, FileMode.Open)) using (var reader = new StreamReader(stream)) { two = await reader.ReadToEndAsync(); @@ -163,7 +165,8 @@ namespace Content.IntegrationTests.Tests } }); - await server.WaitPost(() => mapManager.DeleteMap(mapId)); + testSystem.Enabled = false; + await server.WaitPost(() => mapSys.DeleteMap(mapId)); await pair.CleanReturnAsync(); } @@ -184,29 +187,31 @@ namespace Content.IntegrationTests.Tests var server = pair.Server; var mapLoader = server.System(); - var mapSystem = server.System(); - var mapManager = server.ResolveDependency(); + var mapSys = server.System(); var userData = server.ResolveDependency().UserData; var cfg = server.ResolveDependency(); Assert.That(cfg.GetCVar(CCVars.GridFill), Is.False); + var testSystem = server.System(); + testSystem.Enabled = true; - MapId mapId = default; - const string fileA = "/load tick load a.yml"; - const string fileB = "/load tick load b.yml"; + MapId mapId1 = default; + MapId mapId2 = default; + var fileA = new ResPath("/load tick load a.yml"); + var fileB = new ResPath("/load tick load b.yml"); string yamlA; string yamlB; // Load & save the first map server.Post(() => { - mapSystem.CreateMap(out mapId, runMapInit: false); - mapManager.SetMapPaused(mapId, true); - Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); - mapLoader.SaveMap(mapId, fileA); + var path = new ResPath(TestMap); + Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); + mapId1 = map!.Value.Comp.MapId; + Assert.That(mapLoader.TrySaveMap(mapId1, fileA)); }); await server.WaitIdleAsync(); - await using (var stream = userData.Open(new ResPath(fileA), FileMode.Open)) + await using (var stream = userData.Open(fileA, FileMode.Open)) using (var reader = new StreamReader(stream)) { yamlA = await reader.ReadToEndAsync(); @@ -217,16 +222,15 @@ namespace Content.IntegrationTests.Tests // Load & save the second map server.Post(() => { - mapManager.DeleteMap(mapId); - mapSystem.CreateMap(out mapId, runMapInit: false); - mapManager.SetMapPaused(mapId, true); - Assert.That(mapLoader.TryLoad(mapId, TestMap, out _), $"Failed to load test map {TestMap}"); - mapLoader.SaveMap(mapId, fileB); + var path = new ResPath(TestMap); + Assert.That(mapLoader.TryLoadMap(path, out var map, out _), $"Failed to load test map {TestMap}"); + mapId2 = map!.Value.Comp.MapId; + Assert.That(mapLoader.TrySaveMap(mapId2, fileB)); }); await server.WaitIdleAsync(); - await using (var stream = userData.Open(new ResPath(fileB), FileMode.Open)) + await using (var stream = userData.Open(fileB, FileMode.Open)) using (var reader = new StreamReader(stream)) { yamlB = await reader.ReadToEndAsync(); @@ -234,8 +238,32 @@ namespace Content.IntegrationTests.Tests Assert.That(yamlA, Is.EqualTo(yamlB)); - await server.WaitPost(() => mapManager.DeleteMap(mapId)); + testSystem.Enabled = false; + await server.WaitPost(() => mapSys.DeleteMap(mapId1)); + await server.WaitPost(() => mapSys.DeleteMap(mapId2)); await pair.CleanReturnAsync(); } + + /// + /// Simple system that modifies the data saved to a yaml file by removing the timestamp. + /// Required by some tests that validate that re-saving a map does not modify it. + /// + private sealed class SaveLoadSaveTestSystem : EntitySystem + { + public bool Enabled; + public override void Initialize() + { + SubscribeLocalEvent(OnAfterSave); + } + + private void OnAfterSave(AfterSerializationEvent ev) + { + if (!Enabled) + return; + + // Remove timestamp. + ((MappingDataNode)ev.Node["meta"]).Remove("time"); + } + } } } diff --git a/Content.IntegrationTests/Tests/Shuttle/DockTest.cs b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs index f6e99596e9..ab82a3d2f9 100644 --- a/Content.IntegrationTests/Tests/Shuttle/DockTest.cs +++ b/Content.IntegrationTests/Tests/Shuttle/DockTest.cs @@ -4,10 +4,12 @@ using System.Numerics; using Content.Server.Shuttles.Systems; using Content.Tests; using Robust.Server.GameObjects; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Maths; +using Robust.Shared.Utility; namespace Content.IntegrationTests.Tests.Shuttle; @@ -106,8 +108,9 @@ public sealed class DockTest : ContentUnitTest { mapGrid = entManager.AddComponent(map.MapUid); entManager.DeleteEntity(map.Grid); - Assert.That(entManager.System().TryLoad(otherMap.MapId, "/Maps/Shuttles/emergency.yml", out var rootUids)); - shuttle = rootUids[0]; + var path = new ResPath("/Maps/Shuttles/emergency.yml"); + Assert.That(entManager.System().TryLoadGrid(otherMap.MapId, path, out var grid)); + shuttle = grid!.Value.Owner; var dockingConfig = dockingSystem.GetDockingConfig(shuttle, map.MapUid); Assert.That(dockingConfig, Is.EqualTo(null)); diff --git a/Content.Server/Administration/Commands/LoadGameMapCommand.cs b/Content.Server/Administration/Commands/LoadGameMapCommand.cs index ea31c1ecb1..034b50a9c5 100644 --- a/Content.Server/Administration/Commands/LoadGameMapCommand.cs +++ b/Content.Server/Administration/Commands/LoadGameMapCommand.cs @@ -1,11 +1,9 @@ -using System.Linq; using System.Numerics; using Content.Server.GameTicking; using Content.Server.Maps; using Content.Shared.Administration; -using Robust.Server.Maps; using Robust.Shared.Console; -using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -25,6 +23,7 @@ namespace Content.Server.Administration.Commands var prototypeManager = IoCManager.Resolve(); var entityManager = IoCManager.Resolve(); var gameTicker = entityManager.EntitySysManager.GetEntitySystem(); + var mapSys = entityManager.EntitySysManager.GetEntitySystem(); if (args.Length is not (2 or 4 or 5)) { @@ -32,29 +31,28 @@ namespace Content.Server.Administration.Commands return; } - if (prototypeManager.TryIndex(args[1], out var gameMap)) - { - if (!int.TryParse(args[0], out var mapId)) - return; - - var loadOptions = new MapLoadOptions() - { - LoadMap = false, - }; - - var stationName = args.Length == 5 ? args[4] : null; - - if (args.Length >= 4 && int.TryParse(args[2], out var x) && int.TryParse(args[3], out var y)) - { - loadOptions.Offset = new Vector2(x, y); - } - var grids = gameTicker.LoadGameMap(gameMap, new MapId(mapId), loadOptions, stationName); - shell.WriteLine($"Loaded {grids.Count} grids."); - } - else + if (!prototypeManager.TryIndex(args[1], out var gameMap)) { shell.WriteError($"The given map prototype {args[0]} is invalid."); + return; } + + if (!int.TryParse(args[0], out var mapId)) + return; + + var stationName = args.Length == 5 ? args[4] : null; + + Vector2? offset = null; + if (args.Length >= 4) + offset = new Vector2(int.Parse(args[2]), int.Parse(args[3])); + + var id = new MapId(mapId); + + var grids = mapSys.MapExists(id) + ? gameTicker.MergeGameMap(gameMap, id, stationName: stationName, offset: offset) + : gameTicker.LoadGameMapWithId(gameMap, id, stationName: stationName, offset: offset); + + shell.WriteLine($"Loaded {grids.Count} grids."); } public CompletionResult GetCompletion(IConsoleShell shell, string[] args) diff --git a/Content.Server/Administration/Commands/PersistenceSaveCommand.cs b/Content.Server/Administration/Commands/PersistenceSaveCommand.cs index 7ef1932c56..cae507f6d8 100644 --- a/Content.Server/Administration/Commands/PersistenceSaveCommand.cs +++ b/Content.Server/Administration/Commands/PersistenceSaveCommand.cs @@ -1,10 +1,10 @@ using Content.Shared.Administration; using Content.Shared.CCVar; -using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Map; -using System.Linq; +using Robust.Shared.EntitySerialization.Systems; +using Robust.Shared.Utility; namespace Content.Server.Administration.Commands; @@ -48,7 +48,7 @@ public sealed class PersistenceSave : IConsoleCommand } var mapLoader = _system.GetEntitySystem(); - mapLoader.SaveMap(mapId, saveFilePath); + mapLoader.TrySaveMap(mapId, new ResPath(saveFilePath)); shell.WriteLine(Loc.GetString("cmd-savemap-success")); } } diff --git a/Content.Server/Administration/Systems/AdminTestArenaSystem.cs b/Content.Server/Administration/Systems/AdminTestArenaSystem.cs index e3671bcdfb..12bf0eba64 100644 --- a/Content.Server/Administration/Systems/AdminTestArenaSystem.cs +++ b/Content.Server/Administration/Systems/AdminTestArenaSystem.cs @@ -1,8 +1,7 @@ -using Robust.Server.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Network; using Robust.Shared.Player; +using Robust.Shared.Utility; namespace Content.Server.Administration.Systems; @@ -11,8 +10,7 @@ namespace Content.Server.Administration.Systems; /// public sealed class AdminTestArenaSystem : EntitySystem { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly MetaDataSystem _metaDataSystem = default!; public const string ArenaMapPath = "/Maps/Test/admin_test_arena.yml"; @@ -28,26 +26,24 @@ public sealed class AdminTestArenaSystem : EntitySystem { return (arenaMap, arenaGrid); } - else - { - ArenaGrid[admin.UserId] = null; - return (arenaMap, null); - } - } - ArenaMap[admin.UserId] = _mapManager.GetMapEntityId(_mapManager.CreateMap()); - _metaDataSystem.SetEntityName(ArenaMap[admin.UserId], $"ATAM-{admin.Name}"); - var grids = _map.LoadMap(Comp(ArenaMap[admin.UserId]).MapId, ArenaMapPath); - if (grids.Count != 0) - { - _metaDataSystem.SetEntityName(grids[0], $"ATAG-{admin.Name}"); - ArenaGrid[admin.UserId] = grids[0]; - } - else - { + ArenaGrid[admin.UserId] = null; + return (arenaMap, null); } - return (ArenaMap[admin.UserId], ArenaGrid[admin.UserId]); + var path = new ResPath(ArenaMapPath); + if (!_loader.TryLoadMap(path, out var map, out var grids)) + throw new Exception($"Failed to load admin arena"); + + ArenaMap[admin.UserId] = map.Value.Owner; + _metaDataSystem.SetEntityName(map.Value.Owner, $"ATAM-{admin.Name}"); + + var grid = grids.FirstOrNull(); + ArenaGrid[admin.UserId] = grid?.Owner; + if (grid != null) + _metaDataSystem.SetEntityName(grid.Value.Owner, $"ATAG-{admin.Name}"); + + return (map.Value.Owner, grid?.Owner); } } diff --git a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs index a8d40882c4..1988a07cea 100644 --- a/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/DeviceListSystem.cs @@ -20,7 +20,7 @@ public sealed class DeviceListSystem : SharedDeviceListSystem SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnBeforeBroadcast); SubscribeLocalEvent(OnBeforePacketSent); - SubscribeLocalEvent(OnMapSave); + SubscribeLocalEvent(OnMapSave); } private void OnShutdown(EntityUid uid, DeviceListComponent component, ComponentShutdown args) @@ -124,14 +124,14 @@ public sealed class DeviceListSystem : SharedDeviceListSystem Dirty(list); } - private void OnMapSave(BeforeSaveEvent ev) + private void OnMapSave(BeforeSerializationEvent ev) { List toRemove = new(); var query = GetEntityQuery(); var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var uid, out var device, out var xform)) { - if (xform.MapUid != ev.Map) + if (!ev.MapIds.Contains(xform.MapID)) continue; foreach (var ent in device.Devices) @@ -144,7 +144,10 @@ public sealed class DeviceListSystem : SharedDeviceListSystem continue; } - if (linkedXform.MapUid == ev.Map) + // This is assuming that **all** of the map is getting saved. + // Which is not necessarily true. + // AAAAAAAAAAAAAA + if (ev.MapIds.Contains(linkedXform.MapID)) continue; toRemove.Add(ent); diff --git a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs index efaf568094..645d28f6d2 100644 --- a/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs +++ b/Content.Server/DeviceNetwork/Systems/NetworkConfiguratorSystem.cs @@ -67,15 +67,18 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem SubscribeLocalEvent(OnComponentRemoved); - SubscribeLocalEvent(OnMapSave); + SubscribeLocalEvent(OnMapSave); } - private void OnMapSave(BeforeSaveEvent ev) + private void OnMapSave(BeforeSerializationEvent ev) { var enumerator = AllEntityQuery(); while (enumerator.MoveNext(out var uid, out var conf)) { - if (CompOrNull(conf.ActiveDeviceList)?.MapUid != ev.Map) + if (!TryComp(conf.ActiveDeviceList, out TransformComponent? listXform)) + continue; + + if (!ev.MapIds.Contains(listXform.MapID)) continue; // The linked device list is (probably) being saved. Make sure that the configurator is also being saved @@ -83,9 +86,10 @@ public sealed class NetworkConfiguratorSystem : SharedNetworkConfiguratorSystem // containing a set of all entities that are about to be saved, which would make checking this much easier. // This is a shitty bandaid, and will force close the UI during auto-saves. // TODO Map serialization refactor + // I'm refactoring it now and I still dont know what to do var xform = Transform(uid); - if (xform.MapUid == ev.Map && IsSaveable(uid)) + if (ev.MapIds.Contains(xform.MapID) && IsSaveable(uid)) continue; _uiSystem.CloseUi(uid, NetworkConfiguratorUiKey.Configure); diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index a7dd5d6ab6..40e3303c2d 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -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(); // the map might have been force-set by something @@ -132,52 +132,202 @@ 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; + } /// /// 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 . /// - /// Game map prototype to load in. - /// Map to load into. - /// Map loading options, includes offset. + /// + /// This is basically a wrapper around a method that auto generate + /// some using information in a prototype, and raise some events to allow content + /// to modify the options and react to the map creation. + /// + /// Game map prototype to load in. + /// The id of the map that was loaded. + /// Entity loading options, including whether the maps should be initialized. /// Name to assign to the loaded station. /// All loaded entities and grids. - public IReadOnlyList LoadGameMap(GameMapPrototype map, MapId targetMapId, MapLoadOptions? loadOptions, string? stationName = null) + public IReadOnlyList 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 {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); + /// + /// Variant of that attempts to assign the provided to the + /// loaded map. + /// + public IReadOnlyList 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 {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; + } + + /// + /// Variant of that loads and then merges a game map onto an existing map. + /// + public IReadOnlyList 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 {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 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 +424,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 +864,14 @@ namespace Content.Server.GameTicking /// You likely want to subscribe to this after StationSystem. /// [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; } - /// /// Event raised after the game loads a given map. /// diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index f8ba7e1d7d..d286fd641e 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -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!; diff --git a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs index 1f0505c60f..f09fff5eaf 100644 --- a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs @@ -20,11 +20,17 @@ public sealed partial class LoadMapRuleComponent : Component public ProtoId? GameMap; /// - /// A map path to load on a new map. + /// A map to load. /// [DataField] public ResPath? MapPath; + /// + /// A grid to load on a new map. + /// + [DataField] + public ResPath? GridPath; + /// /// A to move to a new map. /// If there are no instances left nothing is done. diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs index f18115d3cf..003a74ef4b 100644 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -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 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 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 {grid.Value.Owner}; } else if (comp.PreloadedGrid is {} preloaded) { @@ -56,11 +81,11 @@ public sealed class LoadMapRuleSystem : StationEventSystem 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() { loadedShuttle.Value }; _map.InitializeMap(mapUid); @@ -68,7 +93,6 @@ public sealed class LoadMapRuleSystem : StationEventSystem else { Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}"); - Del(mapUid); ForceEndSelf(uid, rule); return; } diff --git a/Content.Server/GridPreloader/GridPreloaderSystem.cs b/Content.Server/GridPreloader/GridPreloaderSystem.cs index e12ce41a31..d648acbb06 100644 --- a/Content.Server/GridPreloader/GridPreloaderSystem.cs +++ b/Content.Server/GridPreloader/GridPreloaderSystem.cs @@ -3,7 +3,6 @@ using Content.Shared.CCVar; using Content.Shared.GridPreloader.Prototypes; using Content.Shared.GridPreloader.Systems; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -13,6 +12,7 @@ using System.Numerics; using Content.Server.GameTicking; using Content.Shared.GameTicking; using JetBrains.Annotations; +using Robust.Shared.EntitySerialization.Systems; namespace Content.Server.GridPreloader; public sealed class GridPreloaderSystem : SharedGridPreloaderSystem @@ -72,23 +72,13 @@ public sealed class GridPreloaderSystem : SharedGridPreloaderSystem { for (var i = 0; i < proto.Copies; i++) { - var options = new MapLoadOptions + if (!_mapLoader.TryLoadGrid(mapId, proto.Path, out var grid)) { - LoadMap = false, - }; - - if (!_mapLoader.TryLoad(mapId, proto.Path.ToString(), out var roots, options)) + Log.Error($"Failed to preload grid prototype {proto.ID}"); continue; + } - // only supports loading maps with one grid. - if (roots.Count != 1) - continue; - - var gridUid = roots[0]; - - // gets grid + also confirms that the root we loaded is actually a grid - if (!TryComp(gridUid, out var mapGrid)) - continue; + var (gridUid, mapGrid) = grid.Value; if (!TryComp(gridUid, out var physics)) continue; diff --git a/Content.Server/Mapping/MappingCommand.cs b/Content.Server/Mapping/MappingCommand.cs index 70647d3281..b85b0953dd 100644 --- a/Content.Server/Mapping/MappingCommand.cs +++ b/Content.Server/Mapping/MappingCommand.cs @@ -3,12 +3,13 @@ using Content.Server.Administration; using Content.Server.GameTicking; using Content.Shared.Administration; using Content.Shared.CCVar; -using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; +using Robust.Shared.Utility; namespace Content.Server.Mapping { @@ -91,8 +92,9 @@ namespace Content.Server.Mapping } else { - var loadOptions = new MapLoadOptions {StoreMapUids = true}; - _entities.System().TryLoad(mapId, args[1], out _, loadOptions); + var path = new ResPath(args[1]); + var opts = new DeserializationOptions {StoreYamlUids = true}; + _entities.System().TryLoadMapWithId(mapId, path, out _, out _, opts); } // was the map actually created or did it fail somehow? diff --git a/Content.Server/Mapping/MappingManager.cs b/Content.Server/Mapping/MappingManager.cs index e8c6eca204..0097df2e55 100644 --- a/Content.Server/Mapping/MappingManager.cs +++ b/Content.Server/Mapping/MappingManager.cs @@ -4,6 +4,8 @@ using Content.Shared.Administration; using Content.Shared.Mapping; using Robust.Server.GameObjects; using Robust.Server.Player; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Serialization; @@ -21,6 +23,7 @@ public sealed class MappingManager : IPostInjectInit [Dependency] private readonly IServerNetManager _net = default!; [Dependency] private readonly IPlayerManager _players = default!; [Dependency] private readonly IEntitySystemManager _systems = default!; + [Dependency] private readonly IEntityManager _ent = default!; private ISawmill _sawmill = default!; private ZStdCompressionContext _zstd = default!; @@ -45,14 +48,14 @@ public sealed class MappingManager : IPostInjectInit if (!_players.TryGetSessionByChannel(message.MsgChannel, out var session) || !_admin.IsAdmin(session, true) || !_admin.HasAdminFlag(session, AdminFlags.Host) || - session.AttachedEntity is not { } player) + !_ent.TryGetComponent(session.AttachedEntity, out TransformComponent? xform) || + xform.MapUid is not {} mapUid) { return; } - var mapId = _systems.GetEntitySystem().GetMapCoordinates(player).MapId; - var mapEntity = _map.GetMapEntityIdOrThrow(mapId); - var data = _systems.GetEntitySystem().GetSaveData(mapEntity); + var sys = _systems.GetEntitySystem(); + var data = sys.SerializeEntitiesRecursive([mapUid]).Node; var document = new YamlDocument(data.ToYaml()); var stream = new YamlStream { document }; var writer = new StringWriter(); diff --git a/Content.Server/Mapping/MappingSystem.cs b/Content.Server/Mapping/MappingSystem.cs index 28bb3afbe1..1ef6944924 100644 --- a/Content.Server/Mapping/MappingSystem.cs +++ b/Content.Server/Mapping/MappingSystem.cs @@ -3,10 +3,10 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.CCVar; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -23,7 +23,7 @@ public sealed class MappingSystem : EntitySystem [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IResourceManager _resMan = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; // Not a comp because I don't want to deal with this getting saved onto maps ever /// @@ -78,7 +78,7 @@ public sealed class MappingSystem : EntitySystem var path = Path.Combine(saveDir, $"{DateTime.Now.ToString("yyyy-M-dd_HH.mm.ss")}-AUTO.yml"); _currentlyAutosaving[map] = (CalculateNextTime(), name); Log.Info($"Autosaving map {name} ({map}) to {path}. Next save in {ReadableTimeLeft(map)} seconds."); - _map.SaveMap(map, path); + _loader.TrySaveMap(map, new ResPath(path)); } } diff --git a/Content.Server/Maps/GameMapPrototype.cs b/Content.Server/Maps/GameMapPrototype.cs index 5942a9930e..c39ca348da 100644 --- a/Content.Server/Maps/GameMapPrototype.cs +++ b/Content.Server/Maps/GameMapPrototype.cs @@ -25,6 +25,11 @@ public sealed partial class GameMapPrototype : IPrototype [DataField] public float MaxRandomOffset = 1000f; + /// + /// Turns out some of the map files are actually secretly grids. Excellent. I love map loading code. + /// + [DataField] public bool IsGrid; + [DataField] public bool RandomRotation = true; diff --git a/Content.Server/Maps/MapMigrationSystem.cs b/Content.Server/Maps/MapMigrationSystem.cs index 83c2abc5e3..3341034a35 100644 --- a/Content.Server/Maps/MapMigrationSystem.cs +++ b/Content.Server/Maps/MapMigrationSystem.cs @@ -2,8 +2,8 @@ using System.IO; using System.Linq; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.ContentPack; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map.Events; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.Markdown; diff --git a/Content.Server/Maps/ResaveCommand.cs b/Content.Server/Maps/ResaveCommand.cs index cc4d13dded..c48c02c1f7 100644 --- a/Content.Server/Maps/ResaveCommand.cs +++ b/Content.Server/Maps/ResaveCommand.cs @@ -1,11 +1,11 @@ using System.Linq; using Content.Server.Administration; using Content.Shared.Administration; -using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Console; using Robust.Shared.ContentPack; -using Robust.Shared.Map; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Components; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Utility; namespace Content.Server.Maps; @@ -17,8 +17,8 @@ namespace Content.Server.Maps; public sealed class ResaveCommand : LocalizedCommands { [Dependency] private readonly IEntityManager _entManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IResourceManager _res = default!; + [Dependency] private readonly ILogManager _log = default!; public override string Command => "resave"; @@ -26,32 +26,56 @@ public sealed class ResaveCommand : LocalizedCommands { var loader = _entManager.System(); - foreach (var fn in _res.ContentFindFiles(new ResPath("/Maps/"))) + var opts = MapLoadOptions.Default with { - var mapId = _mapManager.CreateMap(); - _mapManager.AddUninitializedMap(mapId); - loader.Load(mapId, fn.ToString(), new MapLoadOptions() + + DeserializationOptions = DeserializationOptions.Default with { - StoreMapUids = true, - LoadMap = true, - }); + StoreYamlUids = true, + LogOrphanedGrids = false + } + }; + + var log = _log.GetSawmill(Command); + var files = _res.ContentFindFiles(new ResPath("/Maps/")).ToList(); + + for (var i = 0; i < files.Count; i++) + { + var fn = files[i]; + log.Info($"Re-saving file {i}/{files.Count} : {fn}"); + + if (!loader.TryLoadGeneric(fn, out var result, opts)) + continue; + + if (result.Maps.Count != 1) + { + shell.WriteError( + $"Multi-map or multi-grid files like {fn} are not yet supported by the {Command} command"); + loader.Delete(result); + continue; + } + + var map = result.Maps.First(); // Process deferred component removals. _entManager.CullRemovedComponents(); - var mapUid = _mapManager.GetMapEntityId(mapId); - var mapXform = _entManager.GetComponent(mapUid); - - if (_entManager.HasComponent(mapUid) || mapXform.ChildCount != 1) + if (_entManager.HasComponent(map)) { - loader.SaveMap(mapId, fn.ToString()); + loader.TrySaveMap(map.Comp.MapId, fn); } - else if (mapXform.ChildEnumerator.MoveNext(out var child)) + else if (result.Grids.Count == 1) { - loader.Save(child, fn.ToString()); + loader.TrySaveGrid(result.Grids.First(), fn); + } + else + { + shell.WriteError($"Failed to resave {fn}"); } - _mapManager.DeleteMap(mapId); + loader.Delete(result); } + + shell.WriteLine($"Resaved all maps"); } } diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index 706f63ffd7..521ebed7ec 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -15,10 +15,13 @@ using Robust.Server.GameObjects; using Robust.Shared.Collections; using Robust.Shared.Configuration; using Robust.Shared.Console; +using Robust.Shared.EntitySerialization; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Prototypes; using Robust.Shared.Random; +using Robust.Shared.Utility; namespace Content.Server.Procedural; @@ -173,14 +176,18 @@ public sealed partial class DungeonSystem : SharedDungeonSystem return Transform(uid).MapID; } - var mapId = _mapManager.CreateMap(); - _mapManager.AddUninitializedMap(mapId); - _loader.Load(mapId, proto.AtlasPath.ToString()); - var mapUid = _mapManager.GetMapEntityId(mapId); - _mapManager.SetMapPaused(mapId, true); - comp = AddComp(mapUid); + var opts = new MapLoadOptions + { + DeserializationOptions = DeserializationOptions.Default with {PauseMaps = true}, + ExpectedCategory = FileCategory.Map + }; + + if (!_loader.TryLoadGeneric(proto.AtlasPath, out var res, opts) || !res.Maps.TryFirstOrNull(out var map)) + throw new Exception($"Failed to load dungeon template."); + + comp = AddComp(map.Value.Owner); comp.Path = proto.AtlasPath; - return mapId; + return map.Value.Comp.MapId; } /// diff --git a/Content.Server/Salvage/SalvageSystem.Magnet.cs b/Content.Server/Salvage/SalvageSystem.Magnet.cs index dd61aad274..523ac06e0b 100644 --- a/Content.Server/Salvage/SalvageSystem.Magnet.cs +++ b/Content.Server/Salvage/SalvageSystem.Magnet.cs @@ -6,7 +6,6 @@ using Content.Shared.Mobs.Components; using Content.Shared.Procedural; using Content.Shared.Radio; using Content.Shared.Salvage.Magnet; -using Robust.Server.Maps; using Robust.Shared.Exceptions; using Robust.Shared.Map; @@ -291,15 +290,10 @@ public sealed partial class SalvageSystem case SalvageOffering wreck: var salvageProto = wreck.SalvageMap; - var opts = new MapLoadOptions - { - Offset = new Vector2(0, 0) - }; - - if (!_map.TryLoad(salvMapXform.MapID, salvageProto.MapPath.ToString(), out _, opts)) + if (!_loader.TryLoadGrid(salvMapXform.MapID, salvageProto.MapPath, out _)) { Report(magnet, MagnetChannel, "salvage-system-announcement-spawn-debris-disintegrated"); - _mapManager.DeleteMap(salvMapXform.MapID); + _mapSystem.DeleteMap(salvMapXform.MapID); return; } diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index eb5719c892..9115c60536 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -1,38 +1,23 @@ -using System.Linq; -using System.Numerics; -using Content.Server.Cargo.Systems; -using Content.Server.Construction; -using Content.Server.GameTicking; using Content.Server.Radio.EntitySystems; -using Content.Shared.Examine; -using Content.Shared.Interaction; -using Content.Shared.Popups; using Content.Shared.Radio; using Content.Shared.Salvage; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Map; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Utility; using Content.Server.Chat.Managers; using Content.Server.Gravity; using Content.Server.Parallax; using Content.Server.Procedural; using Content.Server.Shuttles.Systems; using Content.Server.Station.Systems; -using Content.Shared.CCVar; using Content.Shared.Construction.EntitySystems; -using Content.Shared.Random; -using Content.Shared.Random.Helpers; -using Content.Shared.Tools.Components; -using Robust.Server.Maps; -using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map.Components; using Robust.Shared.Timing; using Content.Server.Labels; +using Robust.Shared.EntitySerialization.Systems; namespace Content.Server.Salvage { @@ -50,7 +35,7 @@ namespace Content.Server.Salvage [Dependency] private readonly DungeonSystem _dungeon = default!; [Dependency] private readonly GravitySystem _gravity = default!; [Dependency] private readonly LabelSystem _labelSystem = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly RadioSystem _radioSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index 1f972d9675..708f5a7a1c 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -30,11 +30,13 @@ using Robust.Server.GameObjects; using Robust.Shared.Collections; using Robust.Shared.Configuration; using Robust.Shared.Console; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Utility; using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent; namespace Content.Server.Shuttles.Systems; @@ -512,15 +514,13 @@ public sealed class ArrivalsSystem : EntitySystem private void SetupArrivalsStation() { - var mapUid = _mapSystem.CreateMap(out var mapId, false); - _metaData.SetEntityName(mapUid, Loc.GetString("map-name-terminal")); - - if (!_loader.TryLoad(mapId, _cfgManager.GetCVar(CCVars.ArrivalsMap), out var uids)) - { + var path = new ResPath(_cfgManager.GetCVar(CCVars.ArrivalsMap)); + if (!_loader.TryLoadMap(path, out var map, out var grids)) return; - } - foreach (var id in uids) + _metaData.SetEntityName(map.Value, Loc.GetString("map-name-terminal")); + + foreach (var id in grids) { EnsureComp(id); EnsureComp(id); @@ -531,15 +531,15 @@ public sealed class ArrivalsSystem : EntitySystem if (_cfgManager.GetCVar(CCVars.ArrivalsPlanet)) { var template = _random.Pick(_arrivalsBiomeOptions); - _biomes.EnsurePlanet(mapUid, _protoManager.Index(template)); + _biomes.EnsurePlanet(map.Value, _protoManager.Index(template)); var restricted = new RestrictedRangeComponent { Range = 32f }; - AddComp(mapUid, restricted); + AddComp(map.Value, restricted); } - _mapSystem.InitializeMap(mapId); + _mapSystem.InitializeMap(map.Value.Comp.MapId); // Handle roundstart stations. var query = AllEntityQuery(); @@ -600,9 +600,9 @@ public sealed class ArrivalsSystem : EntitySystem var dummpMapEntity = _mapSystem.CreateMap(out var dummyMapId); if (TryGetArrivals(out var arrivals) && - _loader.TryLoad(dummyMapId, component.ShuttlePath.ToString(), out var shuttleUids)) + _loader.TryLoadGrid(dummyMapId, component.ShuttlePath, out var shuttle)) { - component.Shuttle = shuttleUids[0]; + component.Shuttle = shuttle.Value; var shuttleComp = Comp(component.Shuttle); var arrivalsComp = EnsureComp(component.Shuttle); arrivalsComp.Station = uid; diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 6c4bdc0814..afa77421bd 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -29,10 +29,9 @@ using Content.Shared.Shuttles.Events; using Content.Shared.Tag; using Content.Shared.Tiles; using Robust.Server.GameObjects; -using Robust.Server.Maps; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; -using Robust.Shared.Map; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map.Components; using Robust.Shared.Player; using Robust.Shared.Random; @@ -60,7 +59,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem [Dependency] private readonly DockingSystem _dock = default!; [Dependency] private readonly IdCardSystem _idSystem = default!; [Dependency] private readonly NavMapSystem _navMap = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; @@ -531,10 +530,11 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem } var map = _mapSystem.CreateMap(out var mapId); - var grid = _map.LoadGrid(mapId, component.Map.ToString(), new MapLoadOptions() + if (!_loader.TryLoadGrid(mapId, component.Map, out var grid)) { - LoadMap = false, - }); + Log.Error($"Failed to set up centcomm grid!"); + return; + } if (!Exists(map)) { @@ -608,15 +608,11 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem // Load escape shuttle var shuttlePath = ent.Comp1.EmergencyShuttlePath; - var shuttle = _map.LoadGrid(map.MapId, shuttlePath.ToString(), new MapLoadOptions() - { + if (!_loader.TryLoadGrid(map.MapId, + shuttlePath, + out var shuttle, // Should be far enough... right? I'm too lazy to bounds check CentCom rn. - Offset = new Vector2(500f + ent.Comp2.ShuttleIndex, 0f), - // fun fact: if you just fucking yeet centcomm into nullspace anytime you try to spawn the shuttle, then any distance is far enough. so lets not do that - LoadMap = false, - }); - - if (shuttle == null) + offset: new Vector2(500f + ent.Comp2.ShuttleIndex, 0f))) { Log.Error($"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(ent)}"); return; diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs index de0593b26f..6123f348ec 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -72,17 +72,15 @@ public sealed partial class ShuttleSystem _mapSystem.CreateMap(out var mapId); - if (_loader.TryLoad(mapId, component.Path.ToString(), out var ent) && ent.Count > 0) + if (_loader.TryLoadGrid(mapId, component.Path, out var ent)) { - if (HasComp(ent[0])) - { - TryFTLProximity(ent[0], targetGrid.Value); - } + if (HasComp(ent)) + TryFTLProximity(ent.Value, targetGrid.Value); - _station.AddGridToStation(uid, ent[0]); + _station.AddGridToStation(uid, ent.Value); } - _mapManager.DeleteMap(mapId); + _mapSystem.DeleteMap(mapId); } private bool TryDungeonSpawn(Entity targetGrid, DungeonSpawnGroup group, out EntityUid spawned) @@ -143,20 +141,18 @@ public sealed partial class ShuttleSystem var path = paths[^1]; paths.RemoveAt(paths.Count - 1); - if (_loader.TryLoad(mapId, path.ToString(), out var ent) && ent.Count == 1) + if (_loader.TryLoadGrid(mapId, path, out var grid)) { - if (HasComp(ent[0])) - { - TryFTLProximity(ent[0], targetGrid); - } + if (HasComp(grid)) + TryFTLProximity(grid.Value, targetGrid); if (group.NameGrid) { var name = path.FilenameWithoutExtension; - _metadata.SetEntityName(ent[0], name); + _metadata.SetEntityName(grid.Value, name); } - spawned = ent[0]; + spawned = grid.Value; return true; } @@ -227,7 +223,7 @@ public sealed partial class ShuttleSystem } } - _mapManager.DeleteMap(mapId); + _mapSystem.DeleteMap(mapId); } private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapInitEvent args) @@ -246,23 +242,22 @@ public sealed partial class ShuttleSystem _mapSystem.CreateMap(out var mapId); var valid = false; - if (_loader.TryLoad(mapId, component.Path.ToString(), out var ent) && - ent.Count == 1 && - TryComp(ent[0], out TransformComponent? shuttleXform)) + if (_loader.TryLoadGrid(mapId, component.Path, out var grid)) { - var escape = GetSingleDock(ent[0]); + var escape = GetSingleDock(grid.Value); if (escape != null) { - var config = _dockSystem.GetDockingConfig(ent[0], xform.GridUid.Value, escape.Value.Entity, escape.Value.Component, uid, dock); + var config = _dockSystem.GetDockingConfig(grid.Value, xform.GridUid.Value, escape.Value.Entity, escape.Value.Component, uid, dock); if (config != null) { - FTLDock((ent[0], shuttleXform), config); + var shuttleXform = Transform(grid.Value); + FTLDock((grid.Value, shuttleXform), config); if (TryComp(xform.GridUid, out var stationMember)) { - _station.AddGridToStation(stationMember.Station, ent[0]); + _station.AddGridToStation(stationMember.Station, grid.Value); } valid = true; @@ -273,11 +268,11 @@ public sealed partial class ShuttleSystem { var compType = compReg.Component.GetType(); - if (HasComp(ent[0], compType)) + if (HasComp(grid.Value, compType)) continue; var comp = _factory.GetComponent(compType); - AddComp(ent[0], comp, true); + AddComp(grid.Value, comp, true); } } @@ -286,7 +281,7 @@ public sealed partial class ShuttleSystem Log.Error($"Error loading gridfill dock for {ToPrettyString(uid)} / {component.Path}"); } - _mapManager.DeleteMap(mapId); + _mapSystem.DeleteMap(mapId); } private (EntityUid Entity, DockingComponent Component)? GetSingleDock(EntityUid uid) diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 6e8c1a9e20..f2c58d103c 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -17,6 +17,7 @@ using Robust.Server.GameStates; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; +using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Physics; diff --git a/Content.Shared/Follower/FollowerSystem.cs b/Content.Shared/Follower/FollowerSystem.cs index d1adaa014a..243886dbb7 100644 --- a/Content.Shared/Follower/FollowerSystem.cs +++ b/Content.Shared/Follower/FollowerSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Numerics; using Content.Shared.Administration.Managers; using Content.Shared.Database; @@ -44,8 +45,8 @@ public sealed class FollowerSystem : EntitySystem SubscribeLocalEvent(OnFollowedAttempt); SubscribeLocalEvent(OnGotEquippedHand); SubscribeLocalEvent(OnFollowedTerminating); + SubscribeLocalEvent(OnBeforeSave); SubscribeLocalEvent(OnFollowedPolymorphed); - SubscribeLocalEvent(OnBeforeSave); } private void OnFollowedAttempt(Entity ent, ref ComponentGetStateAttemptEvent args) @@ -63,10 +64,16 @@ public sealed class FollowerSystem : EntitySystem } } - private void OnBeforeSave(BeforeSaveEvent ev) + private void OnBeforeSave(BeforeSerializationEvent ev) { - // Some followers will not be map savable. This ensures that maps don't get saved with empty/invalid - // followers, but just stopping any following on the map being saved. + // Some followers will not be map savable. This ensures that maps don't get saved with some entities that have + // empty/invalid followers, by just stopping any following happening on the map being saved. + // I hate this so much. + // TODO WeakEntityReference + // We need some way to store entity references in a way that doesn't imply that the entity still exists. + // Then we wouldn't have to deal with this shit. + + var maps = ev.Entities.Select(x => Transform(x).MapUid).ToHashSet(); var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var follower, out var xform, out var meta)) @@ -74,7 +81,7 @@ public sealed class FollowerSystem : EntitySystem if (meta.EntityPrototype == null || meta.EntityPrototype.MapSavable) continue; - if (xform.MapUid != ev.Map) + if (!maps.Contains(xform.MapUid)) continue; StopFollowingEntity(uid, follower.Following); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 6f508d9038..14c8036287 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -8,6 +8,7 @@ using Content.Shared.Movement.Events; using Robust.Shared.GameStates; using Robust.Shared.Input; using Robust.Shared.Input.Binding; +using Robust.Shared.Map.Components; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -207,7 +208,7 @@ namespace Content.Shared.Movement.Systems } // If we went from grid -> map we'll preserve our worldrotation - if (relative != null && _mapManager.IsMap(relative.Value)) + if (relative != null && HasComp(relative.Value)) { targetRotation = currentRotation.FlipPositive().Reduced(); } diff --git a/Resources/Maps/Shuttles/emergency_relic.yml b/Resources/Maps/Shuttles/emergency_relic.yml index c724cbc514..288c6ccf5f 100644 --- a/Resources/Maps/Shuttles/emergency_relic.yml +++ b/Resources/Maps/Shuttles/emergency_relic.yml @@ -16,26 +16,11 @@ tilemap: entities: - proto: "" entities: - - uid: 1 - components: - - type: MetaData - name: Map Entity - - type: Transform - - type: Map - mapPaused: True - - type: PhysicsMap - - type: GridTree - - type: MovedGrids - - type: Broadphase - - type: OccluderTree - - type: LoadedMap - uid: 2 components: - type: MetaData name: grid - type: Transform - pos: -7.6484375,2.8046875 - parent: 1 - type: MapGrid chunks: 0,0: diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 2051c24be8..9a4ca97210 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -454,7 +454,7 @@ duration: 1 - type: RuleGrids - type: LoadMapRule - mapPath: /Maps/Shuttles/ShuttleEvent/striker.yml + gridPath: /Maps/Shuttles/ShuttleEvent/striker.yml - type: NukeopsRule roundEndBehavior: Nothing - type: AntagSelection diff --git a/Resources/Prototypes/Maps/centcomm.yml b/Resources/Prototypes/Maps/centcomm.yml index 47dc5ca1f3..007da851d0 100644 --- a/Resources/Prototypes/Maps/centcomm.yml +++ b/Resources/Prototypes/Maps/centcomm.yml @@ -1,5 +1,6 @@ - type: gameMap id: CentComm + isGrid: true # Did you know that centcomm is the only "game map" that isn't actually a map? Send help. mapName: 'Central Command' mapPath: /Maps/centcomm.yml minPlayers: 10