--- /dev/null
+using Content.Server.GameTicking;
+using Content.Server.Ghost.Components;
+using Content.Server.Players;
+using Content.Shared.Administration;
+using Content.Shared.CCVar;
+using Content.Shared.Ghost;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.Configuration;
+using Robust.Shared.Console;
+using Robust.Shared.Map;
+using System.Linq;
+
+namespace Content.Server.Administration.Commands;
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class PersistenceSave : IConsoleCommand
+{
+ [Dependency] private readonly IConfigurationManager _config = default!;
+ [Dependency] private readonly IEntityManager _entities = default!;
+ [Dependency] private readonly IEntitySystemManager _system = default!;
+ [Dependency] private readonly IMapManager _map = default!;
+
+ public string Command => "persistencesave";
+ public string Description => "Saves server data to a persistence file to be loaded later.";
+ public string Help => "persistencesave [mapId] [filePath - default: game.map (CCVar) ]";
+
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length < 1 || args.Length > 2)
+ {
+ shell.WriteError(Loc.GetString("shell-wrong-arguments-number"));
+ return;
+ }
+
+ if (!int.TryParse(args[0], out var intMapId))
+ {
+ shell.WriteError(Loc.GetString("cmd-parse-failure-integer", ("arg", args[0])));
+ return;
+ }
+
+ var mapId = new MapId(intMapId);
+ if (!_map.MapExists(mapId))
+ {
+ shell.WriteError(Loc.GetString("cmd-savemap-not-exist"));
+ return;
+ }
+
+ var saveFilePath = (args.Length > 1 ? args[1] : null) ?? _config.GetCVar(CCVars.GameMap);
+ if (string.IsNullOrWhiteSpace(saveFilePath))
+ {
+ shell.WriteError(Loc.GetString("cmd-persistencesave-no-path", ("cvar", nameof(CCVars.GameMap))));
+ return;
+ }
+
+ var mapLoader = _system.GetEntitySystem<MapLoaderSystem>();
+ mapLoader.SaveMap(mapId, saveFilePath);
+ shell.WriteLine(Loc.GetString("cmd-savemap-success"));
+ }
+}
using Content.Shared.CCVar;
using Robust.Server.Player;
using Robust.Shared.Configuration;
+using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
+using Robust.Shared.Utility;
namespace Content.Server.Maps;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IResourceManager _resMan = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[ViewVariables(VVAccess.ReadOnly)]
if (TryLookupMap(value, out GameMapPrototype? map))
{
_configSelectedMap = map;
+ return;
}
- else
+
+ if (string.IsNullOrEmpty(value))
{
- if (string.IsNullOrEmpty(value))
- {
- _configSelectedMap = default!;
- }
- else
+ _configSelectedMap = default!;
+ return;
+ }
+
+ if (_configurationManager.GetCVar<bool>(CCVars.UsePersistence))
+ {
+ var startMap = _configurationManager.GetCVar<string>(CCVars.PersistenceMap);
+ _configSelectedMap = _prototypeManager.Index<GameMapPrototype>(startMap);
+
+ var mapPath = new ResPath(value);
+ if (_resMan.UserData.Exists(mapPath))
{
- _log.Error($"Unknown map prototype {value} was selected!");
+ _configSelectedMap = _configSelectedMap.Persistence(mapPath);
+ _log.Info($"Using persistence map from {value}");
+ return;
}
+
+ // persistence save path doesn't exist so we just use the start map
+ _log.Warning($"Using persistence start map {startMap} as {value} doesn't exist");
+ return;
}
+
+ _log.Error($"Unknown map prototype {value} was selected!");
}, true);
_configurationManager.OnValueChanged(CCVars.GameMapRotation, value => _mapRotationEnabled = value, true);
_configurationManager.OnValueChanged(CCVars.GameMapMemoryDepth, value =>
/// The stations this map contains. The names should match with the BecomesStation components.
/// </summary>
public IReadOnlyDictionary<string, StationConfig> Stations => _stations;
+
+ /// <summary>
+ /// Performs a shallow clone of this map prototype, replacing <c>MapPath</c> with the argument.
+ /// </summary>
+ public GameMapPrototype Persistence(ResPath mapPath)
+ {
+ return new()
+ {
+ ID = ID,
+ MapName = MapName,
+ MapPath = mapPath,
+ _stations = _stations
+ };
+ }
}
/// <summary>
/// Station that this grid is a part of.
/// </summary>
- [ViewVariables]
+ [DataField]
public EntityUid Station = EntityUid.Invalid;
}
if (!string.IsNullOrEmpty(name))
_metaData.SetEntityName(mapGrid, name);
- var stationMember = AddComp<StationMemberComponent>(mapGrid);
+ var stationMember = EnsureComp<StationMemberComponent>(mapGrid);
stationMember.Station = station;
stationData.Grids.Add(mapGrid);
public static readonly CVarDef<string>
GameMap = CVarDef.Create("game.map", string.Empty, CVar.SERVERONLY);
+ /// <summary>
+ /// Controls whether to use world persistence or not.
+ /// </summary>
+ public static readonly CVarDef<bool>
+ UsePersistence = CVarDef.Create("game.usepersistence", false, CVar.ARCHIVE);
+
+ /// <summary>
+ /// If world persistence is used, what map prototype should be initially loaded.
+ /// If the save file exists, it replaces MapPath but everything else stays the same (station name and such).
+ /// </summary>
+ public static readonly CVarDef<string>
+ PersistenceMap = CVarDef.Create("game.persistencemap", "Empty", CVar.ARCHIVE);
+
/// <summary>
/// Prototype to use for map pool.
/// </summary>
--- /dev/null
+cmd-persistencesave-no-path = filePath was not specified and CCVar {$cvar} is not set. Manually set the filePath param in order to save the map.