diff --git a/Content.Server/Mapping/MappingCommand.cs b/Content.Server/Mapping/MappingCommand.cs index 85c561b9db..12a7af4484 100644 --- a/Content.Server/Mapping/MappingCommand.cs +++ b/Content.Server/Mapping/MappingCommand.cs @@ -9,6 +9,7 @@ using Robust.Shared.ContentPack; using Robust.Shared.EntitySerialization; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Utility; namespace Content.Server.Mapping @@ -35,6 +36,8 @@ namespace Content.Server.Mapping var opts = CompletionHelper.UserFilePath(args[1], res.UserData) .Concat(CompletionHelper.ContentFilePath(args[1], res)); return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-mapping-path")); + case 3: + return CompletionResult.FromHintOptions(["false", "true"], Loc.GetString("cmd-mapping-hint-grid")); } return CompletionResult.Empty; } @@ -47,7 +50,7 @@ namespace Content.Server.Mapping return; } - if (args.Length > 2) + if (args.Length > 3) { shell.WriteLine(Help); return; @@ -57,12 +60,20 @@ namespace Content.Server.Mapping shell.WriteLine(Loc.GetString("cmd-mapping-warning")); #endif + // For backwards compatibility, isGrid is optional and we allow mappers to try load grids without explicitly + // specifying that they are loading a grid. Currently content is not allowed to override a map's MapId, so + // without engine changes this needs to be done by brute force by just trying to load it as a map first. + // This can result in errors being logged if the file is actually a grid, but the command should still work. + // yipeeee + bool? isGrid = args.Length < 3 ? null : bool.Parse(args[2]); + MapId mapId; string? toLoad = null; var mapSys = _entities.System(); + Entity? grid = null; // Get the map ID to use - if (args.Length is 1 or 2) + if (args.Length > 0) { if (!int.TryParse(args[0], out var intMapId)) { @@ -79,7 +90,7 @@ namespace Content.Server.Mapping return; } - if (_map.MapExists(mapId)) + if (mapSys.MapExists(mapId)) { shell.WriteError(Loc.GetString("cmd-mapping-exists", ("mapId", mapId))); return; @@ -93,12 +104,43 @@ namespace Content.Server.Mapping else { var path = new ResPath(args[1]); + toLoad = path.FilenameWithoutExtension; var opts = new DeserializationOptions {StoreYamlUids = true}; - _entities.System().TryLoadMapWithId(mapId, path, out _, out _, opts); + var loader = _entities.System(); + + if (isGrid == true) + { + mapSys.CreateMap(mapId, runMapInit: false); + if (!loader.TryLoadGrid(mapId, path, out grid, opts)) + { + shell.WriteError(Loc.GetString("cmd-mapping-error")); + mapSys.DeleteMap(mapId); + return; + } + } + else if (!loader.TryLoadMapWithId(mapId, path, out _, out _, opts)) + { + if (isGrid == false) + { + shell.WriteError(Loc.GetString("cmd-mapping-error")); + return; + } + + // isGrid was not specified and loading it as a map failed, so we fall back to trying to load + // the file as a grid + shell.WriteLine(Loc.GetString("cmd-mapping-try-grid")); + mapSys.CreateMap(mapId, runMapInit: false); + if (!loader.TryLoadGrid(mapId, path, out grid, opts)) + { + shell.WriteError(Loc.GetString("cmd-mapping-error")); + mapSys.DeleteMap(mapId); + return; + } + } } // was the map actually created or did it fail somehow? - if (!_map.MapExists(mapId)) + if (!mapSys.MapExists(mapId)) { shell.WriteError(Loc.GetString("cmd-mapping-error")); return; @@ -120,16 +162,22 @@ namespace Content.Server.Mapping shell.ExecuteCommand("changecvar events.enabled false"); shell.ExecuteCommand("changecvar shuttle.auto_call_time 0"); - if (_cfg.GetCVar(CCVars.AutosaveEnabled)) - shell.ExecuteCommand($"toggleautosave {mapId} {toLoad ?? "NEWMAP"}"); + var auto = _entities.System(); + if (grid != null) + auto.ToggleAutosave(grid.Value.Owner, toLoad ?? "NEWGRID"); + else + auto.ToggleAutosave(mapId, toLoad ?? "NEWMAP"); + shell.ExecuteCommand($"tp 0 0 {mapId}"); shell.RemoteExecuteCommand("mappingclientsidesetup"); - _map.SetMapPaused(mapId, true); + DebugTools.Assert(mapSys.IsPaused(mapId)); - if (args.Length == 2) - shell.WriteLine(Loc.GetString("cmd-mapping-success-load",("mapId",mapId),("path", args[1]))); - else + if (args.Length != 2) shell.WriteLine(Loc.GetString("cmd-mapping-success", ("mapId", mapId))); + else if (grid == null) + shell.WriteLine(Loc.GetString("cmd-mapping-success-load", ("mapId", mapId), ("path", args[1]))); + else + shell.WriteLine(Loc.GetString("cmd-mapping-success-load-grid", ("mapId", mapId), ("path", args[1]))); } } } diff --git a/Content.Server/Mapping/MappingSystem.cs b/Content.Server/Mapping/MappingSystem.cs index 1ef6944924..b79387b3e5 100644 --- a/Content.Server/Mapping/MappingSystem.cs +++ b/Content.Server/Mapping/MappingSystem.cs @@ -2,12 +2,12 @@ using System.IO; using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.CCVar; -using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.ContentPack; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map; +using Robust.Shared.Map.Components; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -21,7 +21,7 @@ public sealed class MappingSystem : EntitySystem [Dependency] private readonly IConsoleHost _conHost = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly SharedMapSystem _map = default!; [Dependency] private readonly IResourceManager _resMan = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; @@ -30,7 +30,7 @@ public sealed class MappingSystem : EntitySystem /// map id -> next autosave timespan & original filename. /// /// - private Dictionary _currentlyAutosaving = new(); + private Dictionary _currentlyAutosaving = new(); private bool _autosaveEnabled; @@ -60,25 +60,29 @@ public sealed class MappingSystem : EntitySystem if (!_autosaveEnabled) return; - foreach (var (map, (time, name))in _currentlyAutosaving.ToArray()) + foreach (var (uid, (time, name))in _currentlyAutosaving) { if (_timing.RealTime <= time) continue; - if (!_mapManager.MapExists(map) || _mapManager.IsMapInitialized(map)) + if (LifeStage(uid) >= EntityLifeStage.MapInitialized) { - Log.Warning($"Can't autosave map {map}; it doesn't exist, or is initialized. Removing from autosave."); - _currentlyAutosaving.Remove(map); - return; + Log.Warning($"Can't autosave entity {uid}; it doesn't exist, or is initialized. Removing from autosave."); + _currentlyAutosaving.Remove(uid); + continue; } + _currentlyAutosaving[uid] = (CalculateNextTime(), name); var saveDir = Path.Combine(_cfg.GetCVar(CCVars.AutosaveDirectory), name); _resMan.UserData.CreateDir(new ResPath(saveDir).ToRootedPath()); - 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."); - _loader.TrySaveMap(map, new ResPath(path)); + var path = new ResPath(Path.Combine(saveDir, $"{DateTime.Now:yyyy-M-dd_HH.mm.ss}-AUTO.yml")); + Log.Info($"Autosaving map {name} ({uid}) to {path}. Next save in {ReadableTimeLeft(uid)} seconds."); + + if (HasComp(uid)) + _loader.TrySaveMap(uid, path); + else + _loader.TrySaveGrid(uid, path); } } @@ -87,34 +91,41 @@ public sealed class MappingSystem : EntitySystem return _timing.RealTime + TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.AutosaveInterval)); } - private double ReadableTimeLeft(MapId map) + private double ReadableTimeLeft(EntityUid uid) { - return Math.Round(_currentlyAutosaving[map].next.TotalSeconds - _timing.RealTime.TotalSeconds); + return Math.Round(_currentlyAutosaving[uid].next.TotalSeconds - _timing.RealTime.TotalSeconds); } #region Public API - public void ToggleAutosave(MapId map, string? path=null) + public void ToggleAutosave(MapId map, string? path = null) + { + if (_map.TryGetMap(map, out var uid)) + ToggleAutosave(uid.Value, path); + } + + public void ToggleAutosave(EntityUid uid, string? path=null) { if (!_autosaveEnabled) return; - if (path != null && _currentlyAutosaving.TryAdd(map, (CalculateNextTime(), Path.GetFileName(path)))) - { - if (!_mapManager.MapExists(map) || _mapManager.IsMapInitialized(map)) - { - Log.Warning("Tried to enable autosaving on non-existant or already initialized map!"); - _currentlyAutosaving.Remove(map); - return; - } + if (_currentlyAutosaving.Remove(uid) || path == null) + return; - Log.Info($"Started autosaving map {path} ({map}). Next save in {ReadableTimeLeft(map)} seconds."); - } - else + if (LifeStage(uid) >= EntityLifeStage.MapInitialized) { - _currentlyAutosaving.Remove(map); - Log.Info($"Stopped autosaving on map {map}"); + Log.Error("Tried to enable autosaving on a post map-init entity."); + return; } + + if (!HasComp(uid) && !HasComp(uid)) + { + Log.Error($"{ToPrettyString(uid)} is neither a grid or map"); + return; + } + + _currentlyAutosaving[uid] = (CalculateNextTime(), Path.GetFileName(path)); + Log.Info($"Started autosaving map {path} ({uid}). Next save in {ReadableTimeLeft(uid)} seconds."); } #endregion diff --git a/Resources/Locale/en-US/mapping/mapping-command.ftl b/Resources/Locale/en-US/mapping/mapping-command.ftl index e6d4f0e393..12ab144e25 100644 --- a/Resources/Locale/en-US/mapping/mapping-command.ftl +++ b/Resources/Locale/en-US/mapping/mapping-command.ftl @@ -1,8 +1,10 @@ cmd-mapping-desc = Create or load a map and teleports you to it. -cmd-mapping-help = Usage: mapping [MapID] [Path] +cmd-mapping-help = Usage: mapping [MapID] [Path] [Grid] cmd-mapping-server = Only players can use this command. cmd-mapping-error = An error occurred when creating the new map. +cmd-mapping-try-grid = Failed to load the file as a map. Attempting to load the file as a grid... cmd-mapping-success-load = Created uninitialized map from file {$path} with id {$mapId}. +cmd-mapping-success-load-grid = Loaded uninitialized grid from file {$path} onto a new map with id {$mapId}. cmd-mapping-success = Created uninitialized map with id {$mapId}. cmd-mapping-warning = WARNING: The server is using a debug build. You are risking losing your changes. @@ -14,5 +16,6 @@ cmd-mapping-failure-float = {$arg} is not a valid float. cmd-mapping-failure-bool = {$arg} is not a valid bool. cmd-mapping-nullspace = You cannot load into map 0. cmd-hint-mapping-id = [MapID] +cmd-mapping-hint-grid = [Grid] cmd-hint-mapping-path = [Path] cmd-mapping-exists = Map {$mapId} already exists.