mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Refactor map loading & saving (#5572)
* Refactor map loading & saving * test fixes * ISerializationManager tweaks * Fix component composition * Try fix entity deserialization component composition * comments * CL * error preinit * a * cleanup * error if version is too new * Add AlwaysPushSerializationTest * Add auto-inclusion test * Better categorization * Combine test components * Save -> TrySave Also better handling for saving multiple entities individually * Create new partial class for map loading * Add OrphanSerializationTest * Include MapIds in BeforeSerializationEvent * Addd LifetimeSerializationTest * Add TestMixedLifetimeSerialization * Add CategorizationTest * explicitly serialize list of nullspace entities * Add backwards compatibility test * Version comments also fixes wrong v4 format * add MapMergeTest * Add NetEntity support * Optimize EntityDeserializer Avoid unnecessary component deserialization * fix assert & other bugs * fucking containers strike again * Fix deletion of pre-init entities * fix release note merge conflict * Update Robust.Shared/Map/MapManager.GridCollection.cs Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> * VV --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
@@ -35,19 +35,30 @@ END TEMPLATE-->
|
||||
|
||||
### Breaking changes
|
||||
|
||||
*None yet*
|
||||
* `ITileDefinitionManager.AssignAlias` and general tile alias functionality has been removed. `TileAliasPrototype` still exist, but are only used during entity deserialization.
|
||||
* `IMapManager.AddUninitializedMap` has been removed. Use the map-init options on `CreateMap()` instead.
|
||||
* Re-using a MapId will now log a warning. This may cause some integration tests to fail if they are configured to fail
|
||||
when warnings are logged.
|
||||
* The minimum supported map format / version has been increased from 2 to 3.
|
||||
* The server-side `MapLoaderSystem` and associated classes & structs has been moved to `Robust.Shared`, and has been significantly modified.
|
||||
* The`TryLoad` and `Save` methods have been replaced with grid, map, generic entity variants. I.e, `SaveGrid`, `SaveMap`, and `SaveEntities`.
|
||||
* Most of the serialization logic and methods have been moved out of `MapLoaderSystem` and into new `EntitySerializer`
|
||||
and `EntityDeserializer` classes, which also replace the old `MapSerializationContext`.
|
||||
* The `MapLoadOptions` class has been split into `MapLoadOptions`, `SerializationOptions`, and `DeserializationOptions`
|
||||
structs.
|
||||
|
||||
### New features
|
||||
|
||||
*None yet*
|
||||
* The current map format/version has increased from 6 to 7 and now contains more information to try support serialization of maps with null-space entities and full game saves.
|
||||
* `IEntitySystemManager` now provides access to the system `IDependencyCollection`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
*None yet*
|
||||
* Fixed entity deserialization for components with a data fields that have a AlwaysPushInheritance Attribute
|
||||
|
||||
### Other
|
||||
|
||||
*None yet*
|
||||
* `MapChangedEvent` has been marked as obsolete, and should be replaced with `MapCreatedEvent` and `MapRemovedEvent.
|
||||
|
||||
### Internal
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
var id = new MapId(--LastMapId);
|
||||
while (MapManager.MapExists(id))
|
||||
while (MapExists(id) || UsedIds.Contains(id))
|
||||
{
|
||||
id = new MapId(--LastMapId);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Console.Commands
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
_ent.System<MapLoaderSystem>().Save(uid, args[1]);
|
||||
_ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
}
|
||||
|
||||
@@ -63,7 +64,6 @@ namespace Robust.Server.Console.Commands
|
||||
public sealed class LoadGridCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
public override string Command => "loadgrid";
|
||||
@@ -91,13 +91,14 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
var sys = _system.GetEntitySystem<SharedMapSystem>();
|
||||
if (!sys.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError("Target map does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
var loadOptions = new MapLoadOptions();
|
||||
Vector2 offset = default;
|
||||
if (args.Length >= 4)
|
||||
{
|
||||
if (!float.TryParse(args[2], out var x))
|
||||
@@ -112,9 +113,10 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.Offset = new Vector2(x, y);
|
||||
offset = new Vector2(x, y);
|
||||
}
|
||||
|
||||
Angle rot = default;
|
||||
if (args.Length >= 5)
|
||||
{
|
||||
if (!float.TryParse(args[4], out var rotation))
|
||||
@@ -123,9 +125,10 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.Rotation = Angle.FromDegrees(rotation);
|
||||
rot = Angle.FromDegrees(rotation);
|
||||
}
|
||||
|
||||
var opts = DeserializationOptions.Default;
|
||||
if (args.Length >= 6)
|
||||
{
|
||||
if (!bool.TryParse(args[5], out var storeUids))
|
||||
@@ -134,10 +137,11 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.StoreMapUids = storeUids;
|
||||
opts.StoreYamlUids = storeUids;
|
||||
}
|
||||
|
||||
_system.GetEntitySystem<MapLoaderSystem>().Load(mapId, args[1], loadOptions);
|
||||
var path = new ResPath(args[1]);
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TryLoadGrid(mapId, path, out _, opts, offset, rot);
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
@@ -149,7 +153,6 @@ namespace Robust.Server.Console.Commands
|
||||
public sealed class SaveMap : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
public override string Command => "savemap";
|
||||
@@ -189,13 +192,14 @@ namespace Robust.Server.Console.Commands
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
var sys = _system.GetEntitySystem<SharedMapSystem>();
|
||||
if (!sys.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-savemap-not-exist"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map.IsMapInitialized(mapId) &&
|
||||
if (sys.IsInitialized(mapId) &&
|
||||
( args.Length < 3 || !bool.TryParse(args[2], out var force) || !force))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-savemap-init-warning"));
|
||||
@@ -203,7 +207,7 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-attempt", ("mapId", mapId), ("path", args[1])));
|
||||
_system.GetEntitySystem<MapLoaderSystem>().SaveMap(mapId, args[1]);
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TrySaveMap(mapId, new ResPath(args[1]));
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||
}
|
||||
}
|
||||
@@ -211,7 +215,6 @@ namespace Robust.Server.Console.Commands
|
||||
public sealed class LoadMap : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
public override string Command => "loadmap";
|
||||
@@ -267,61 +270,49 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map.MapExists(mapId))
|
||||
var sys = _system.GetEntitySystem<SharedMapSystem>();
|
||||
if (sys.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-loadmap-exists", ("mapId", mapId)));
|
||||
return;
|
||||
}
|
||||
|
||||
var loadOptions = new MapLoadOptions();
|
||||
|
||||
float x = 0, y = 0;
|
||||
if (args.Length >= 3)
|
||||
float x = 0;
|
||||
if (args.Length >= 3 && !float.TryParse(args[2], out x))
|
||||
{
|
||||
if (!float.TryParse(args[2], out x))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
|
||||
return;
|
||||
}
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length >= 4)
|
||||
float y = 0;
|
||||
if (args.Length >= 4 && !float.TryParse(args[3], out y))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3])));
|
||||
return;
|
||||
}
|
||||
var offset = new Vector2(x, y);
|
||||
|
||||
if (!float.TryParse(args[3], out y))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3])));
|
||||
return;
|
||||
}
|
||||
float rotation = 0;
|
||||
if (args.Length >= 5 && !float.TryParse(args[4], out rotation))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[4])));
|
||||
return;
|
||||
}
|
||||
var rot = new Angle(rotation);
|
||||
|
||||
bool storeUids = false;
|
||||
if (args.Length >= 6 && !bool.TryParse(args[5], out storeUids))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[5])));
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.Offset = new Vector2(x, y);
|
||||
var opts = new DeserializationOptions {StoreYamlUids = storeUids};
|
||||
|
||||
if (args.Length >= 5)
|
||||
{
|
||||
if (!float.TryParse(args[4], out var rotation))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[4])));
|
||||
return;
|
||||
}
|
||||
var path = new ResPath(args[1]);
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TryLoadMapWithId(mapId, path, out _, out _, opts, offset, rot);
|
||||
|
||||
loadOptions.Rotation = new Angle(rotation);
|
||||
}
|
||||
|
||||
if (args.Length >= 6)
|
||||
{
|
||||
if (!bool.TryParse(args[5], out var storeUids))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[5])));
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.StoreMapUids = storeUids;
|
||||
}
|
||||
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TryLoad(mapId, args[1], out _, loadOptions);
|
||||
|
||||
if (_map.MapExists(mapId))
|
||||
if (sys.MapExists(mapId))
|
||||
shell.WriteLine(Loc.GetString("cmd-loadmap-success", ("mapId", mapId), ("path", args[1])));
|
||||
else
|
||||
shell.WriteLine(Loc.GetString("cmd-loadmap-error", ("path", args[1])));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ namespace Robust.Server.GameObjects
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
var id = new MapId(++LastMapId);
|
||||
while (MapManager.MapExists(id))
|
||||
while (MapExists(id) || UsedIds.Contains(id))
|
||||
{
|
||||
id = new MapId(++LastMapId);
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
internal interface IServerEntityManagerInternal : IServerEntityManager
|
||||
{
|
||||
// These methods are used by the map loader to do multi-stage entity construction during map load.
|
||||
// I would recommend you refer to the MapLoader for usage.
|
||||
|
||||
EntityUid AllocEntity(EntityPrototype? prototype);
|
||||
|
||||
void FinishEntityLoad(EntityUid entity, IEntityLoadContext? context = null);
|
||||
|
||||
void FinishEntityLoad(EntityUid entity, EntityPrototype? prototype, IEntityLoadContext? context = null);
|
||||
|
||||
void FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta = null);
|
||||
|
||||
void FinishEntityStartup(EntityUid entity);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata component used to keep consistent UIDs inside map files cross saving.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This component stores the previous map UID of entities from map load.
|
||||
/// This can then be used to re-serialize the entity with the same UID for the merge driver to recognize.
|
||||
/// </remarks>
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
public sealed partial class MapSaveIdComponent : Component
|
||||
{
|
||||
public int Uid { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Server.GameObjects
|
||||
/// Manager for entities -- controls things like template loading and instantiation
|
||||
/// </summary>
|
||||
[UsedImplicitly] // DI Container
|
||||
public sealed class ServerEntityManager : EntityManager, IServerEntityManagerInternal
|
||||
public sealed class ServerEntityManager : EntityManager, IServerEntityManager
|
||||
{
|
||||
private static readonly Gauge EntitiesCount = Metrics.CreateGauge(
|
||||
"robust_entities_count",
|
||||
@@ -61,32 +61,6 @@ namespace Robust.Server.GameObjects
|
||||
_pvs = System<PvsSystem>();
|
||||
}
|
||||
|
||||
EntityUid IServerEntityManagerInternal.AllocEntity(EntityPrototype? prototype)
|
||||
{
|
||||
return AllocEntity(prototype, out _);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, IEntityLoadContext? context)
|
||||
{
|
||||
LoadEntity(entity, context);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityLoad(EntityUid entity, EntityPrototype? prototype, IEntityLoadContext? context)
|
||||
{
|
||||
LoadEntity(entity, context, prototype);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta)
|
||||
{
|
||||
InitializeEntity(entity, meta);
|
||||
}
|
||||
|
||||
[Obsolete("Use StartEntity")]
|
||||
void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity)
|
||||
{
|
||||
StartEntity(entity);
|
||||
}
|
||||
|
||||
internal override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
{
|
||||
if (prototypeName == null)
|
||||
|
||||
@@ -29,7 +29,8 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
base.Initialize();
|
||||
EntityManager.EntityDeleted += OnDeleted;
|
||||
_player.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<MapRemovedEvent>(OnMapRemoved);
|
||||
SubscribeLocalEvent<MapCreatedEvent>(OnMapCreated);
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridCreated);
|
||||
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
|
||||
|
||||
@@ -270,14 +271,6 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
|
||||
#region Map/Grid Events
|
||||
|
||||
private void OnMapChanged(MapChangedEvent ev)
|
||||
{
|
||||
if (ev.Created)
|
||||
OnMapCreated(ev);
|
||||
else
|
||||
OnMapDestroyed(ev);
|
||||
}
|
||||
|
||||
private void OnGridRemoved(GridRemovalEvent ev)
|
||||
{
|
||||
RemoveForceSend(ev.EntityUid);
|
||||
@@ -290,12 +283,12 @@ public sealed class PvsOverrideSystem : SharedPvsOverrideSystem
|
||||
AddForceSend(ev.EntityUid);
|
||||
}
|
||||
|
||||
private void OnMapDestroyed(MapChangedEvent ev)
|
||||
private void OnMapRemoved(MapRemovedEvent ev)
|
||||
{
|
||||
RemoveForceSend(ev.Uid);
|
||||
}
|
||||
|
||||
private void OnMapCreated(MapChangedEvent ev)
|
||||
private void OnMapCreated(MapCreatedEvent ev)
|
||||
{
|
||||
// TODO PVS remove this requirement.
|
||||
// I think this just required refactoring client game state logic so it doesn't sending maps/grids to nullspace.
|
||||
|
||||
@@ -303,11 +303,8 @@ internal sealed partial class PvsSystem
|
||||
RemoveRoot(ev.EntityUid);
|
||||
}
|
||||
|
||||
private void OnMapChanged(MapChangedEvent ev)
|
||||
private void OnMapChanged(MapRemovedEvent ev)
|
||||
{
|
||||
if (!ev.Destroyed)
|
||||
return;
|
||||
|
||||
RemoveRoot(ev.Uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
_metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<MapRemovedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
|
||||
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.Maps;
|
||||
|
||||
/// <summary>
|
||||
/// Added to Maps that were loaded by MapLoaderSystem. If not present then this map was created externally.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class LoadedMapComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Server.Maps
|
||||
{
|
||||
[PublicAPI]
|
||||
public sealed class MapLoadOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, UID components will be created for loaded entities
|
||||
/// to maintain consistency upon subsequent savings.
|
||||
/// </summary>
|
||||
public bool StoreMapUids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset to apply to the loaded objects.
|
||||
/// </summary>
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => _offset;
|
||||
set
|
||||
{
|
||||
TransformMatrix = Matrix3Helpers.CreateTransform(value, Rotation);
|
||||
_offset = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation to apply to the loaded objects as a collective, around 0, 0.
|
||||
/// </summary>
|
||||
/// <remarks>Setting this overrides </remarks>
|
||||
public Angle Rotation
|
||||
{
|
||||
get => _rotation;
|
||||
set
|
||||
{
|
||||
TransformMatrix = Matrix3Helpers.CreateTransform(Offset, value);
|
||||
_rotation = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Angle _rotation = Angle.Zero;
|
||||
|
||||
public Matrix3x2 TransformMatrix { get; set; } = Matrix3x2.Identity;
|
||||
|
||||
/// <summary>
|
||||
/// If there is a map entity serialized should we also load it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be set to false if you want to load a map file onto an existing map and do not wish to overwrite the existing entity.
|
||||
/// </remarks>
|
||||
public bool LoadMap { get; set; } = true;
|
||||
|
||||
public bool DoMapInit = false;
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,6 @@ namespace Robust.Server
|
||||
deps.Register<IResourceManagerInternal, ResourceManager>();
|
||||
deps.Register<EntityManager, ServerEntityManager>();
|
||||
deps.Register<IServerEntityManager, ServerEntityManager>();
|
||||
deps.Register<IServerEntityManagerInternal, ServerEntityManager>();
|
||||
deps.Register<IServerGameStateManager, ServerGameStateManager>();
|
||||
deps.Register<IReplayRecordingManager, ReplayRecordingManager>();
|
||||
deps.Register<IReplayRecordingManagerInternal, ReplayRecordingManager>();
|
||||
|
||||
@@ -59,7 +59,7 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
|
||||
UpdatesAfter.Add(typeof(SharedTransformSystem));
|
||||
UpdatesAfter.Add(typeof(SharedPhysicsSystem));
|
||||
|
||||
SubscribeLocalEvent<MapChangedEvent>(MapManagerOnMapCreated);
|
||||
SubscribeLocalEvent<MapCreatedEvent>(MapManagerOnMapCreated);
|
||||
SubscribeLocalEvent<GridInitializeEvent>(MapManagerOnGridCreated);
|
||||
|
||||
SubscribeLocalEvent<TComp, ComponentStartup>(OnCompStartup);
|
||||
@@ -143,11 +143,8 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
|
||||
RemComp(uid, component);
|
||||
}
|
||||
|
||||
private void MapManagerOnMapCreated(MapChangedEvent e)
|
||||
private void MapManagerOnMapCreated(MapCreatedEvent e)
|
||||
{
|
||||
if (e.Destroyed || e.Map == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
EnsureComp<TTreeComp>(e.Uid);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ internal sealed class ListMapsCommand : LocalizedEntityCommands
|
||||
{
|
||||
var msg = new StringBuilder();
|
||||
|
||||
foreach (var mapId in _map.GetAllMapIds().OrderBy(id => id.Value))
|
||||
foreach (var mapId in _mapSystem.GetAllMapIds().OrderBy(id => id.Value))
|
||||
{
|
||||
if (!_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Added to Maps that were loaded by <see cref="MapLoaderSystem"/>. If not present then this map was created externally.
|
||||
/// </summary>
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
public sealed partial class LoadedMapComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
namespace Robust.Shared.EntitySerialization.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="MapLoaderSystem"/> to track the original tile map from when a map was loaded.
|
||||
@@ -0,0 +1,20 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component is optionally added to entities that get loaded from yaml files. It stores the UID that the entity
|
||||
/// had within the yaml file. This is used when saving the entity back to a yaml file so that it re-uses the same UID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is primarily intended to reduce the diff sizes when modifying yaml maps. Note that there is no guarantee that
|
||||
/// the given uid will be used when writing the entity. E.g., if more than one entity have this component with the
|
||||
/// same uid, only one of those entities will be saved with the requested id.
|
||||
/// </remarks>
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
public sealed partial class YamlUidComponent : Component
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int Uid;
|
||||
}
|
||||
1221
Robust.Shared/EntitySerialization/EntityDeserializer.cs
Normal file
1221
Robust.Shared/EntitySerialization/EntityDeserializer.cs
Normal file
File diff suppressed because it is too large
Load Diff
985
Robust.Shared/EntitySerialization/EntitySerializer.cs
Normal file
985
Robust.Shared/EntitySerialization/EntitySerializer.cs
Normal file
@@ -0,0 +1,985 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides methods for serializing entities into yaml. It provides some more control over
|
||||
/// serialization than the methods provided by <see cref="MapLoaderSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are several methods (e.g., <see cref="SerializeEntityRecursive"/> that serialize entities into a
|
||||
/// per-entity <see cref="MappingDataNode"/> stored in the <see cref="EntityData"/> dictionary, which is indexed by the
|
||||
/// entity's assigned yaml id (see <see cref="GetYamlUid"/>. The generated data can then be written to a larger yaml
|
||||
/// document using the various "Write" methods. (e.g., <see cref="WriteEntitySection"/>). After a one has finished using
|
||||
/// the generated data, the serializer needs to be reset (<see cref="Reset"/>) using it again to serialize other entities.
|
||||
/// </remarks>
|
||||
public sealed class EntitySerializer : ISerializationContext,
|
||||
ITypeSerializer<EntityUid, ValueDataNode>,
|
||||
ITypeSerializer<NetEntity, ValueDataNode>
|
||||
{
|
||||
public const int MapFormatVersion = 7;
|
||||
// v6->v7: PR #5572 - Added more metadata, List maps/grids/orphans, include some life-stage information
|
||||
// v5->v6: PR #4307 - Converted Tile.TypeId from ushort to int
|
||||
// v4->v5: PR #3992 - Removed name & author fields
|
||||
// v3->v4: PR #3913 - Grouped entities by prototype
|
||||
// v2->v3: PR #3468
|
||||
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
|
||||
[Dependency] public readonly EntityManager EntMan = default!;
|
||||
[Dependency] public readonly IGameTiming Timing = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDef = default!;
|
||||
[Dependency] private readonly IConfigurationManager _conf = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
|
||||
private readonly ISawmill _log;
|
||||
public readonly Dictionary<EntityUid, int> YamlUidMap = new();
|
||||
public readonly HashSet<int> YamlIds = new();
|
||||
|
||||
|
||||
public string? CurrentComponent { get; private set; }
|
||||
public Entity<MetaDataComponent>? CurrentEntity { get; private set; }
|
||||
public int CurrentEntityYamlUid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tile ID -> yaml tile ID mapping.
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, int> _tileMap = new();
|
||||
private readonly HashSet<int> _yamlTileIds = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WritingReadingPrototypes { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, the serializer will refuse to serialize the given entity and will orphan any entity that is parented to
|
||||
/// it. This is useful for serializing things like a grid (or multiple grids & entities) that are parented to a map
|
||||
/// without actually serializing the map itself.
|
||||
/// </summary>
|
||||
public EntityUid Truncate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of all entities that have previously been ignored via <see cref="Truncate"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is tracked in case somebody does something weird, like trying to save a grid w/o its map, and then later on
|
||||
/// including the map in the file. AFAIK, that should work in principle, though it would lead to a weird file where
|
||||
/// the grid is orphaned and not on the map where it should be.
|
||||
/// </remarks>
|
||||
public readonly HashSet<EntityUid> Truncated = new();
|
||||
|
||||
public readonly SerializationOptions Options;
|
||||
|
||||
/// <summary>
|
||||
/// Cached prototype data. This is used to avoid writing redundant data that is already specified in an entity's
|
||||
/// prototype.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, Dictionary<string, MappingDataNode>> PrototypeCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// The serialized entity data.
|
||||
/// </summary>
|
||||
public readonly Dictionary<int, (EntityUid Uid, MappingDataNode Node)> EntityData = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="EntityData"/> indices grouped by their entity prototype ids.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, List<int>> Prototypes = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized map entities.
|
||||
/// </summary>
|
||||
public readonly List<int> Maps = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized null-space entities.
|
||||
/// This only includes entities that were initially in null-space, it does not include entities that were
|
||||
/// serialized without their parents. Those are in <see cref="Orphans"/>.
|
||||
/// </summary>
|
||||
public readonly List<int> Nullspace = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized grid entities.
|
||||
/// </summary>
|
||||
public readonly List<int> Grids = new();
|
||||
|
||||
/// <summary>
|
||||
/// Yaml ids of all serialized entities in the file whose parents were not serialized. This does not include
|
||||
/// entities that did not have a parent (e.g., maps or null-space entities). I.e., these are the entities that
|
||||
/// need to be attached to a new parent when loading the file, unless you want to load them into null-space.
|
||||
/// </summary>
|
||||
public readonly List<int> Orphans = new();
|
||||
|
||||
private readonly string _metaName;
|
||||
private readonly string _xformName;
|
||||
private readonly MappingDataNode _emptyMetaNode;
|
||||
private readonly MappingDataNode _emptyXformNode;
|
||||
private int _nextYamlUid = 1;
|
||||
private int _nextYamlTileId;
|
||||
|
||||
private readonly List<EntityUid> _autoInclude = new();
|
||||
private readonly EntityQuery<YamlUidComponent> _yamlQuery;
|
||||
private readonly EntityQuery<MapGridComponent> _gridQuery;
|
||||
private readonly EntityQuery<MapComponent> _mapQuery;
|
||||
private readonly EntityQuery<MetaDataComponent> _metaQuery;
|
||||
private readonly EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
/// <summary>
|
||||
/// C# event for checking whether an entity is serializable. Can be used by content to prevent specific entities
|
||||
/// from getting serialized.
|
||||
/// </summary>
|
||||
public event IsSerializableDelegate? OnIsSerializeable;
|
||||
public delegate void IsSerializableDelegate(Entity<MetaDataComponent> ent, ref bool serializable);
|
||||
|
||||
public EntitySerializer(IDependencyCollection _dependency, SerializationOptions options)
|
||||
{
|
||||
_dependency.InjectDependencies(this);
|
||||
|
||||
_log = _logMan.GetSawmill("entity_serializer");
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
|
||||
_metaName = _factory.GetComponentName(typeof(MetaDataComponent));
|
||||
_xformName = _factory.GetComponentName(typeof(TransformComponent));
|
||||
_emptyMetaNode = _serialization.WriteValueAs<MappingDataNode>(typeof(MetaDataComponent), new MetaDataComponent(), alwaysWrite: true, context: this);
|
||||
|
||||
CurrentComponent = _xformName;
|
||||
_emptyXformNode = _serialization.WriteValueAs<MappingDataNode>(typeof(TransformComponent), new TransformComponent(), alwaysWrite: true, context: this);
|
||||
CurrentComponent = null;
|
||||
|
||||
_yamlQuery = EntMan.GetEntityQuery<YamlUidComponent>();
|
||||
_gridQuery = EntMan.GetEntityQuery<MapGridComponent>();
|
||||
_mapQuery = EntMan.GetEntityQuery<MapComponent>();
|
||||
_metaQuery = EntMan.GetEntityQuery<MetaDataComponent>();
|
||||
_xformQuery = EntMan.GetEntityQuery<TransformComponent>();
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public bool IsSerializable(Entity<MetaDataComponent?> ent)
|
||||
{
|
||||
if (ent.Comp == null && !EntMan.TryGetComponent(ent.Owner, out ent.Comp))
|
||||
return false;
|
||||
|
||||
if (ent.Comp.EntityPrototype?.MapSavable == false)
|
||||
return false;
|
||||
|
||||
bool serializable = true;
|
||||
OnIsSerializeable?.Invoke(ent!, ref serializable);
|
||||
return serializable;
|
||||
}
|
||||
|
||||
#region Serialize API
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a single entity. This does not automatically include
|
||||
/// children, though depending on the setting of <see cref="SerializationOptions.MissingEntityBehaviour"/> it may
|
||||
/// auto-include additional entities aside from the one provided.
|
||||
/// </summary>
|
||||
public void SerializeEntity(EntityUid uid)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
throw new Exception($"{EntMan.ToPrettyString(uid)} is not serializable");
|
||||
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
ReserveYamlId(uid);
|
||||
SerializeEntityInternal(uid);
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
if (_autoInclude.Count != 0)
|
||||
ProcessAutoInclude();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a set of entities. This does not automatically include children or parents, though depending on the
|
||||
/// setting of <see cref="SerializationOptions.MissingEntityBehaviour"/> it may auto-include additional entities
|
||||
/// aside from the one provided.
|
||||
/// </summary>
|
||||
public void SerializeEntities(HashSet<EntityUid> entities)
|
||||
{
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
throw new Exception($"{EntMan.ToPrettyString(uid)} is not serializable");
|
||||
}
|
||||
|
||||
ReserveYamlIds(entities);
|
||||
SerializeEntitiesInternal(entities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes an entity and all of its serializable children. Note that this will not automatically serialize the
|
||||
/// entity's parents.
|
||||
/// </summary>
|
||||
public void SerializeEntityRecursive(EntityUid root)
|
||||
{
|
||||
if (!IsSerializable(root))
|
||||
throw new Exception($"{EntMan.ToPrettyString(root)} is not serializable");
|
||||
|
||||
Truncate = _xformQuery.GetComponent(root).ParentUid;
|
||||
Truncated.Add(Truncate);
|
||||
InitializeTileMap(root);
|
||||
HashSet<EntityUid> entities = new();
|
||||
RecursivelyIncludeChildren(root, entities);
|
||||
ReserveYamlIds(entities);
|
||||
SerializeEntitiesInternal(entities);
|
||||
Truncate = EntityUid.Invalid;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the <see cref="_tileMap"/> that is used to serialize grid chunks using
|
||||
/// <see cref="MapChunkSerializer"/>. This initialization just involves checking to see if any of the entities being
|
||||
/// serialized were previously deserialized. If they were, it will re-use the old tile map. This is not actually required,
|
||||
/// and is just meant to prevent large map file diffs when the internal tile ids change. I.e., you can serialize entities
|
||||
/// without initializing the tile map.
|
||||
/// </summary>
|
||||
private void InitializeTileMap(EntityUid root)
|
||||
{
|
||||
if (!FindSavedTileMap(root, out var savedMap))
|
||||
return;
|
||||
|
||||
// Note: some old maps were saved with duplicate id strings.
|
||||
// I.e, multiple integers that correspond to the same prototype id.
|
||||
// Hence the TryAdd()
|
||||
//
|
||||
// Though now we also need to use TryAdd in case InitializeTileMap() is called multiple times.
|
||||
// E.g., if different grids get added separately to a single save file, in which case the
|
||||
// tile map may already be partially populated.
|
||||
foreach (var (origId, prototypeId) in savedMap)
|
||||
{
|
||||
if (_tileDef.TryGetDefinition(prototypeId, out var definition))
|
||||
_tileMap.TryAdd(definition.TileId, origId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FindSavedTileMap(EntityUid root, [NotNullWhen(true)] out Dictionary<int, string>? map)
|
||||
{
|
||||
// Try and fetch the mapping directly
|
||||
if (EntMan.TryGetComponent(root, out MapSaveTileMapComponent? comp))
|
||||
{
|
||||
map = comp.TileMap;
|
||||
return true;
|
||||
}
|
||||
|
||||
// iterate over all of its children and grab the first grid with a mapping
|
||||
var xform = _xformQuery.GetComponent(root);
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (!EntMan.TryGetComponent(child, out MapSaveTileMapComponent? cComp))
|
||||
continue;
|
||||
map = cComp.TileMap;
|
||||
return true;
|
||||
}
|
||||
|
||||
map = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
#region AutoInclude
|
||||
|
||||
private void ProcessAutoInclude()
|
||||
{
|
||||
DebugTools.AssertEqual(_autoInclude.ToHashSet().Count, _autoInclude.Count);
|
||||
|
||||
var ents = new HashSet<EntityUid>();
|
||||
|
||||
switch (Options.MissingEntityBehaviour)
|
||||
{
|
||||
case MissingEntityBehaviour.PartialInclude:
|
||||
// Include the entity and any of its direct parents
|
||||
foreach (var uid in _autoInclude)
|
||||
{
|
||||
RecursivelyIncludeParents(uid, ents);
|
||||
}
|
||||
break;
|
||||
case MissingEntityBehaviour.IncludeNullspace:
|
||||
case MissingEntityBehaviour.AutoInclude:
|
||||
// Find the root transform of all the included entities
|
||||
var roots = new HashSet<EntityUid>();
|
||||
foreach (var uid in _autoInclude)
|
||||
{
|
||||
GetRootNode(uid, roots);
|
||||
}
|
||||
|
||||
// Recursively include all children of these root nodes.
|
||||
foreach (var root in roots)
|
||||
{
|
||||
RecursivelyIncludeChildren(root, ents);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_autoInclude.Clear();
|
||||
SerializeEntitiesInternal(ents);
|
||||
}
|
||||
|
||||
private void RecursivelyIncludeChildren(EntityUid uid, HashSet<EntityUid> ents)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
return;
|
||||
|
||||
ents.Add(uid);
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
RecursivelyIncludeChildren(child, ents);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetRootNode(EntityUid uid, HashSet<EntityUid> ents)
|
||||
{
|
||||
if (!IsSerializable(uid))
|
||||
throw new NotSupportedException($"Attempted to auto-include an unserializable entity: {EntMan.ToPrettyString(uid)}");
|
||||
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
while (xform.ParentUid.IsValid() && xform.ParentUid != Truncate)
|
||||
{
|
||||
uid = xform.ParentUid;
|
||||
xform = _xformQuery.GetComponent(uid);
|
||||
|
||||
if (!IsSerializable(uid))
|
||||
throw new NotSupportedException($"Encountered an un-serializable parent entity: {EntMan.ToPrettyString(uid)}");
|
||||
}
|
||||
|
||||
ents.Add(uid);
|
||||
}
|
||||
|
||||
private void RecursivelyIncludeParents(EntityUid uid, HashSet<EntityUid> ents)
|
||||
{
|
||||
while (uid.IsValid() && uid != Truncate)
|
||||
{
|
||||
if (!ents.Add(uid))
|
||||
break;
|
||||
|
||||
if (!IsSerializable(uid))
|
||||
throw new NotSupportedException($"Encountered an un-serializable parent entity: {EntMan.ToPrettyString(uid)}");
|
||||
|
||||
uid = _xformQuery.GetComponent(uid).ParentUid;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void SerializeEntitiesInternal(HashSet<EntityUid> entities)
|
||||
{
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
SerializeEntityInternal(uid);
|
||||
}
|
||||
|
||||
DebugTools.AssertNull(CurrentEntity);
|
||||
if (_autoInclude.Count != 0)
|
||||
ProcessAutoInclude();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a single entity, and store the results in <see cref="EntityData"/>.
|
||||
/// </summary>
|
||||
private void SerializeEntityInternal(EntityUid uid)
|
||||
{
|
||||
var saveId = GetYamlUid(uid);
|
||||
DebugTools.Assert(!EntityData.ContainsKey(saveId));
|
||||
|
||||
// It might be possible that something could cause an entity to be included twice.
|
||||
// E.g., if someone serializes a grid w/o its map, and then tries to separately include the map and all its children.
|
||||
// In that case, the grid would already have been serialized as a orphan.
|
||||
// uhhh.... I guess its fine?
|
||||
if (EntityData.ContainsKey(saveId))
|
||||
return;
|
||||
|
||||
var meta = _metaQuery.GetComponent(uid);
|
||||
var protoId = meta.EntityPrototype?.ID ?? string.Empty;
|
||||
|
||||
switch (meta.EntityLifeStage)
|
||||
{
|
||||
case <= EntityLifeStage.Initializing:
|
||||
_log.Error($"Encountered an uninitialized entity: {EntMan.ToPrettyString(uid)}");
|
||||
break;
|
||||
case >= EntityLifeStage.Terminating:
|
||||
_log.Error($"Encountered terminating or deleted entity: {EntMan.ToPrettyString(uid)}");
|
||||
break;
|
||||
}
|
||||
|
||||
CurrentEntityYamlUid = saveId;
|
||||
CurrentEntity = (uid, meta);
|
||||
|
||||
Prototypes.GetOrNew(protoId).Add(saveId);
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
|
||||
if (_mapQuery.HasComp(uid))
|
||||
Maps.Add(saveId);
|
||||
else if (xform.ParentUid == EntityUid.Invalid)
|
||||
Nullspace.Add(saveId);
|
||||
|
||||
if (_gridQuery.HasComp(uid))
|
||||
{
|
||||
// The current assumption is that grids cannot be in null-space, because the rest of the code
|
||||
// (broadphase, etc) don't support grids without maps.
|
||||
DebugTools.Assert(xform.ParentUid != EntityUid.Invalid || _mapQuery.HasComp(uid));
|
||||
Grids.Add(saveId);
|
||||
}
|
||||
|
||||
var entData = new MappingDataNode
|
||||
{
|
||||
{"uid", saveId.ToString(CultureInfo.InvariantCulture)}
|
||||
};
|
||||
|
||||
EntityData[saveId] = (uid, entData);
|
||||
var cache = GetProtoCache(meta.EntityPrototype);
|
||||
|
||||
// Store information about whether a given entity has been map-initialized.
|
||||
// In principle, if a map has been map-initialized, then all entities on that map should also be map-initialized.
|
||||
// But technically there is nothing that prevents someone from moving a post-init entity onto a pre-init map and vice-versa.
|
||||
// Also, we need to record this information even if the map is not being serialized.
|
||||
// In 99% of cases, this data is probably redundant and just bloats the file, but I can't think of a better way of handling it.
|
||||
// At least it should only bloat post-init maps, which aren't really getting used so far.
|
||||
if (meta.EntityLifeStage == EntityLifeStage.MapInitialized)
|
||||
{
|
||||
if (Options.ExpectPreInit)
|
||||
_log.Error($"Expected all entities to be pre-mapinit, but encountered post-init entity: {EntMan.ToPrettyString(uid)}");
|
||||
entData.Add("mapInit", "true");
|
||||
|
||||
// If an entity has been map-initialized, we assume it is un-paused.
|
||||
// If it is paused, we have to specify it.
|
||||
if (meta.EntityPaused)
|
||||
entData.Add("paused", "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
// If an entity has not yet been map-initialized, we assume it is paused.
|
||||
// I don't know in what situations it wouldn't be, but might as well future proof this.
|
||||
if (!meta.EntityPaused)
|
||||
entData.Add("paused", "false");
|
||||
}
|
||||
|
||||
var components = new SequenceDataNode();
|
||||
if (xform.NoLocalRotation && xform.LocalRotation != 0)
|
||||
{
|
||||
_log.Error($"Encountered a no-rotation entity with non-zero local rotation: {EntMan.ToPrettyString(uid)}");
|
||||
xform._localRotation = 0;
|
||||
}
|
||||
|
||||
foreach (var component in EntMan.GetComponentsInternal(uid))
|
||||
{
|
||||
var compType = component.GetType();
|
||||
|
||||
var reg = _factory.GetRegistration(compType);
|
||||
if (reg.Unsaved)
|
||||
continue;
|
||||
|
||||
CurrentComponent = reg.Name;
|
||||
MappingDataNode? compMapping;
|
||||
MappingDataNode? protoMapping = null;
|
||||
if (cache != null && cache.TryGetValue(reg.Name, out protoMapping))
|
||||
{
|
||||
// If this has a prototype, we need to use alwaysWrite: true.
|
||||
// E.g., an anchored prototype might have anchored: true. If we we are saving an un-anchored
|
||||
// instance of this entity, and if we have alwaysWrite: false, then compMapping would not include
|
||||
// the anchored data-field (as false is the default for this bool data field), so the entity would
|
||||
// implicitly be saved as anchored.
|
||||
compMapping = _serialization.WriteValueAs<MappingDataNode>(compType, component, alwaysWrite: true, context: this);
|
||||
|
||||
// This will not recursively call Except() on the values of the mapping. It will only remove
|
||||
// key-value pairs if both the keys and values are equal.
|
||||
compMapping = compMapping.Except(protoMapping);
|
||||
if(compMapping == null)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
compMapping = _serialization.WriteValueAs<MappingDataNode>(compType, component, alwaysWrite: false, context: this);
|
||||
}
|
||||
|
||||
// Don't need to write it if nothing was written! Note that if this entity has no associated
|
||||
// prototype, we ALWAYS want to write the component, because merely the fact that it exists is
|
||||
// information that needs to be written.
|
||||
if (compMapping.Children.Count != 0 || protoMapping == null)
|
||||
{
|
||||
compMapping.InsertAt(0, "type", new ValueDataNode(reg.Name));
|
||||
components.Add(compMapping);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentComponent = null;
|
||||
if (components.Count != 0)
|
||||
entData.Add("components", components);
|
||||
|
||||
// TODO ENTITY SERIALIZATION
|
||||
// Consider adding a Action<EntityUid, MappingDataNode>? OnEntitySerialized
|
||||
// I.e., allow content to modify the per-entity data? I don't know if that would actually be useful, as content
|
||||
// could just as easily append a separate entity dictionary to the output that has the extra per-entity data they
|
||||
// want to serialize.
|
||||
|
||||
if (meta.EntityPrototype == null)
|
||||
{
|
||||
CurrentEntityYamlUid = 0;
|
||||
CurrentEntity = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// an entity may have less components than the original prototype, so we need to check if any are missing.
|
||||
SequenceDataNode? missingComponents = null;
|
||||
foreach (var (name, comp) in meta.EntityPrototype.Components)
|
||||
{
|
||||
// try comp instead of has-comp as it checks whether the component is supposed to have been
|
||||
// deleted.
|
||||
if (EntMan.TryGetComponent(uid, comp.Component.GetType(), out _))
|
||||
continue;
|
||||
|
||||
missingComponents ??= new();
|
||||
missingComponents.Add(new ValueDataNode(name));
|
||||
}
|
||||
|
||||
if (missingComponents != null)
|
||||
entData.Add("missingComponents", missingComponents);
|
||||
|
||||
CurrentEntityYamlUid = 0;
|
||||
CurrentEntity = null;
|
||||
}
|
||||
|
||||
private Dictionary<string, MappingDataNode>? GetProtoCache(EntityPrototype? proto)
|
||||
{
|
||||
if (proto == null)
|
||||
return null;
|
||||
|
||||
if (PrototypeCache.TryGetValue(proto.ID, out var cache))
|
||||
return cache;
|
||||
|
||||
PrototypeCache[proto.ID] = cache = new(proto.Components.Count);
|
||||
WritingReadingPrototypes = true;
|
||||
|
||||
foreach (var (compName, comp) in proto.Components)
|
||||
{
|
||||
CurrentComponent = compName;
|
||||
cache.Add(compName, _serialization.WriteValueAs<MappingDataNode>(comp.Component.GetType(), comp.Component, alwaysWrite: true, context: this));
|
||||
}
|
||||
|
||||
CurrentComponent = null;
|
||||
WritingReadingPrototypes = false;
|
||||
cache.TryAdd(_metaName, _emptyMetaNode);
|
||||
cache.TryAdd(_xformName, _emptyXformNode);
|
||||
return cache;
|
||||
}
|
||||
|
||||
#region Write
|
||||
|
||||
public MappingDataNode Write()
|
||||
{
|
||||
DebugTools.AssertEqual(Maps.ToHashSet().Count, Maps.Count, "Duplicate maps?");
|
||||
DebugTools.AssertEqual(Grids.ToHashSet().Count, Grids.Count, "Duplicate frids?");
|
||||
DebugTools.AssertEqual(Orphans.ToHashSet().Count, Orphans.Count, "Duplicate orphans?");
|
||||
DebugTools.AssertEqual(Nullspace.ToHashSet().Count, Nullspace.Count, "Duplicate nullspace?");
|
||||
|
||||
return new MappingDataNode
|
||||
{
|
||||
{"meta", WriteMetadata()},
|
||||
{"maps", WriteIds(Maps)},
|
||||
{"grids", WriteIds(Grids)},
|
||||
{"orphans", WriteIds(Orphans)},
|
||||
{"nullspace", WriteIds(Nullspace)},
|
||||
{"tilemap", WriteTileMap()},
|
||||
{"entities", WriteEntitySection()},
|
||||
};
|
||||
}
|
||||
|
||||
public MappingDataNode WriteMetadata()
|
||||
{
|
||||
return new MappingDataNode
|
||||
{
|
||||
{"format", MapFormatVersion.ToString(CultureInfo.InvariantCulture)},
|
||||
{"category", GetCategory().ToString()},
|
||||
{"engineVersion", _conf.GetCVar(CVars.BuildEngineVersion) },
|
||||
{"forkId", _conf.GetCVar(CVars.BuildForkId)},
|
||||
{"forkVersion", _conf.GetCVar(CVars.BuildVersion)},
|
||||
{"time", DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)},
|
||||
{"entityCount", EntityData.Count.ToString(CultureInfo.InvariantCulture)}
|
||||
};
|
||||
}
|
||||
|
||||
public SequenceDataNode WriteIds(List<int> ids)
|
||||
{
|
||||
var result = new SequenceDataNode();
|
||||
foreach (var id in ids)
|
||||
{
|
||||
result.Add(new ValueDataNode(id.ToString(CultureInfo.InvariantCulture)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize the <see cref="_tileMap"/> to yaml. This data is required to deserialize any serialized grid chunks using <see cref="MapChunkSerializer"/>.
|
||||
/// </summary>
|
||||
public MappingDataNode WriteTileMap()
|
||||
{
|
||||
var map = new MappingDataNode();
|
||||
foreach (var (tileId, yamlTileId) in _tileMap.OrderBy(x => x.Key))
|
||||
{
|
||||
// This can come up if tests try to serialize test maps with custom / placeholder tile ids without registering them with the tile def manager..
|
||||
if (!_tileDef.TryGetDefinition(tileId, out var def))
|
||||
throw new Exception($"Attempting to serialize a tile {tileId} with no valid tile definition.");
|
||||
|
||||
var yamlId = yamlTileId.ToString(CultureInfo.InvariantCulture);
|
||||
map.Add(yamlId, def.ID);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public SequenceDataNode WriteEntitySection()
|
||||
{
|
||||
if (YamlIds.Count != YamlUidMap.Count || YamlIds.Count != EntityData.Count)
|
||||
{
|
||||
// Maybe someone reserved a yaml id with ReserveYamlId() or implicitly with GetId() without actually
|
||||
// ever serializing the entity, This can lead to references to non-existent entities.
|
||||
throw new Exception($"Entity count mismatch");
|
||||
}
|
||||
|
||||
var prototypes = new SequenceDataNode();
|
||||
var protos = Prototypes.Keys.ToList();
|
||||
protos.Sort(StringComparer.InvariantCulture);
|
||||
|
||||
foreach (var protoId in protos)
|
||||
{
|
||||
var entities = new SequenceDataNode();
|
||||
var node = new MappingDataNode
|
||||
{
|
||||
{ "proto", protoId },
|
||||
{ "entities", entities},
|
||||
};
|
||||
|
||||
prototypes.Add(node);
|
||||
|
||||
var saveIds = Prototypes[protoId];
|
||||
saveIds.Sort();
|
||||
foreach (var saveId in saveIds)
|
||||
{
|
||||
var entData = EntityData[saveId].Node;
|
||||
entities.Add(entData);
|
||||
}
|
||||
}
|
||||
|
||||
return prototypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the category that the serialized data belongs to. If one was specified in the
|
||||
/// <see cref="SerializationOptions"/> it will use that after validating it, otherwise it will attempt to infer a
|
||||
/// category.
|
||||
/// </summary>
|
||||
public FileCategory GetCategory()
|
||||
{
|
||||
switch (Options.Category)
|
||||
{
|
||||
case FileCategory.Save:
|
||||
return FileCategory.Save;
|
||||
|
||||
case FileCategory.Map:
|
||||
return Maps.Count == 1 ? FileCategory.Map : FileCategory.Unknown;
|
||||
|
||||
case FileCategory.Grid:
|
||||
if (Maps.Count > 0 || Grids.Count != 1)
|
||||
return FileCategory.Unknown;
|
||||
return FileCategory.Grid;
|
||||
|
||||
case FileCategory.Entity:
|
||||
if (Maps.Count > 0 || Grids.Count > 0 || Orphans.Count != 1)
|
||||
return FileCategory.Unknown;
|
||||
return FileCategory.Entity;
|
||||
|
||||
default:
|
||||
if (Maps.Count == 1)
|
||||
{
|
||||
// Contains a single map, and no orphaned entities that need reparenting.
|
||||
if (Orphans.Count == 0)
|
||||
return FileCategory.Map;
|
||||
}
|
||||
else if (Grids.Count == 1)
|
||||
{
|
||||
// Contains a single orphaned grid.
|
||||
if (Orphans.Count == 1 && Grids[0] == Orphans[0])
|
||||
return FileCategory.Grid;
|
||||
}
|
||||
else if (Orphans.Count == 1)
|
||||
{
|
||||
// A lone orphaned entity.
|
||||
return FileCategory.Entity;
|
||||
}
|
||||
|
||||
return FileCategory.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region YamlIds
|
||||
|
||||
/// <summary>
|
||||
/// Get (or allocate) the integer id that will be used in the serialized file to refer to the given entity.
|
||||
/// </summary>
|
||||
public int GetYamlUid(EntityUid uid)
|
||||
{
|
||||
return !YamlUidMap.TryGetValue(uid, out var id) ? AllocateYamlUid(uid) : id;
|
||||
}
|
||||
|
||||
private int AllocateYamlUid(EntityUid uid)
|
||||
{
|
||||
if (Truncated.Contains(uid))
|
||||
{
|
||||
_log.Error(
|
||||
"Including a previously truncated entity within the serialization process? Something probably wrong");
|
||||
}
|
||||
|
||||
DebugTools.Assert(!YamlUidMap.ContainsKey(uid));
|
||||
while (!YamlIds.Add(_nextYamlUid))
|
||||
{
|
||||
_nextYamlUid++;
|
||||
}
|
||||
|
||||
YamlUidMap.Add(uid, _nextYamlUid);
|
||||
return _nextYamlUid++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get (or allocate) the integer id that will be used in the serialized file to refer to the given grid tile id.
|
||||
/// </summary>
|
||||
public int GetYamlTileId(int tileId)
|
||||
{
|
||||
if (_tileMap.TryGetValue(tileId, out var yamlId))
|
||||
return yamlId;
|
||||
|
||||
return AllocateYamlTileId(tileId);
|
||||
}
|
||||
|
||||
private int AllocateYamlTileId(int tileId)
|
||||
{
|
||||
while (!_yamlTileIds.Add(_nextYamlTileId))
|
||||
{
|
||||
_nextYamlTileId++;
|
||||
}
|
||||
|
||||
_tileMap[tileId] = _nextYamlTileId;
|
||||
return _nextYamlTileId++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method ensures that the given entities have a yaml ids assigned. If the entities have a
|
||||
/// <see cref="YamlUidComponent"/>, they will attempt to use that id, which exists to prevent large map file diffs
|
||||
/// due to changing yaml ids.
|
||||
/// </summary>
|
||||
public void ReserveYamlIds(HashSet<EntityUid> entities)
|
||||
{
|
||||
List<EntityUid> needIds = new();
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (YamlUidMap.ContainsKey(uid))
|
||||
continue;
|
||||
|
||||
if (_yamlQuery.TryGetComponent(uid, out var comp) && comp.Uid > 0 && YamlIds.Add(comp.Uid))
|
||||
{
|
||||
if (Truncated.Contains(uid))
|
||||
{
|
||||
_log.Error(
|
||||
"Including a previously truncated entity within the serialization process? Something probably wrong");
|
||||
}
|
||||
|
||||
YamlUidMap.Add(uid, comp.Uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
needIds.Add(uid);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var uid in needIds)
|
||||
{
|
||||
AllocateYamlUid(uid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method ensures that the given entity has a yaml id assigned to it. If the entity has a
|
||||
/// <see cref="YamlUidComponent"/>, it will attempt to use that id, which exists to prevent large map file diffs due
|
||||
/// to changing yaml ids.
|
||||
/// </summary>
|
||||
public void ReserveYamlId(EntityUid uid)
|
||||
{
|
||||
if (YamlUidMap.ContainsKey(uid))
|
||||
return;
|
||||
|
||||
if (_yamlQuery.TryGetComponent(uid, out var comp) && comp.Uid > 0 && YamlIds.Add(comp.Uid))
|
||||
{
|
||||
if (Truncated.Contains(uid))
|
||||
{
|
||||
_log.Error(
|
||||
"Including a previously truncated entity within the serialization process? Something probably wrong");
|
||||
}
|
||||
|
||||
YamlUidMap.Add(uid, comp.Uid);
|
||||
}
|
||||
else
|
||||
AllocateYamlUid(uid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITypeSerializer
|
||||
|
||||
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context)
|
||||
{
|
||||
if (node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
if (!int.TryParse(node.Value, out _))
|
||||
return new ErrorNode(node, "Invalid EntityUid");
|
||||
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
EntityUid value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (YamlUidMap.TryGetValue(value, out var yamlId))
|
||||
return new ValueDataNode(yamlId.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
if (CurrentComponent == _xformName)
|
||||
{
|
||||
if (value == EntityUid.Invalid)
|
||||
return new ValueDataNode("invalid");
|
||||
|
||||
DebugTools.Assert(!Orphans.Contains(CurrentEntityYamlUid));
|
||||
Orphans.Add(CurrentEntityYamlUid);
|
||||
|
||||
if (Options.ErrorOnOrphan && CurrentEntity != null && value != Truncate)
|
||||
_log.Error($"Serializing entity {EntMan.ToPrettyString(CurrentEntity)} without including its parent {EntMan.ToPrettyString(value)}");
|
||||
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
if (value == EntityUid.Invalid)
|
||||
{
|
||||
if (Options.MissingEntityBehaviour != MissingEntityBehaviour.Ignore)
|
||||
_log.Error($"Encountered an invalid entityUid reference.");
|
||||
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
if (value == Truncate)
|
||||
{
|
||||
_log.Error(
|
||||
$"{EntMan.ToPrettyString(CurrentEntity)}:{CurrentComponent} is attempting to serialize references to a truncated entity {EntMan.ToPrettyString(Truncate)}.");
|
||||
}
|
||||
|
||||
switch (Options.MissingEntityBehaviour)
|
||||
{
|
||||
case MissingEntityBehaviour.Error:
|
||||
_log.Error(EntMan.Deleted(value)
|
||||
? $"Encountered a reference to a deleted entity {value} while serializing {EntMan.ToPrettyString(CurrentEntity)}."
|
||||
: $"Encountered a reference to a missing entity: {value} while serializing {EntMan.ToPrettyString(CurrentEntity)}.");
|
||||
return new ValueDataNode("invalid");
|
||||
case MissingEntityBehaviour.Ignore:
|
||||
return new ValueDataNode("invalid");
|
||||
case MissingEntityBehaviour.IncludeNullspace:
|
||||
if (!EntMan.TryGetComponent(value, out TransformComponent? xform)
|
||||
|| xform.ParentUid != EntityUid.Invalid
|
||||
|| _gridQuery.HasComp(value)
|
||||
|| _mapQuery.HasComp(value))
|
||||
{
|
||||
goto case MissingEntityBehaviour.Error;
|
||||
}
|
||||
goto case MissingEntityBehaviour.AutoInclude;
|
||||
case MissingEntityBehaviour.PartialInclude:
|
||||
case MissingEntityBehaviour.AutoInclude:
|
||||
if (Options.LogAutoInclude is {} level)
|
||||
_log.Log(level, $"Auto-including entity {EntMan.ToPrettyString(value)} referenced by {EntMan.ToPrettyString(CurrentEntity)}");
|
||||
_autoInclude.Add(value);
|
||||
var id = GetYamlUid(value);
|
||||
return new ValueDataNode(id.ToString(CultureInfo.InvariantCulture));
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
EntityUid ITypeReader<EntityUid, ValueDataNode>.Read(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context,
|
||||
ISerializationManager.InstantiationDelegate<EntityUid>? _)
|
||||
{
|
||||
return node.Value == "invalid" ? EntityUid.Invalid : EntityUid.Parse(node.Value);
|
||||
}
|
||||
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
if (!int.TryParse(node.Value, out _))
|
||||
return new ErrorNode(node, "Invalid NetEntity");
|
||||
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
|
||||
public NetEntity Read(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<NetEntity>? instanceProvider = null)
|
||||
{
|
||||
return node.Value == "invalid" ? NetEntity.Invalid : NetEntity.Parse(node.Value);
|
||||
}
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
NetEntity value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
var uid = EntMan.GetEntity(value);
|
||||
return serializationManager.WriteValue(uid, alwaysWrite, context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
71
Robust.Shared/EntitySerialization/LoadResult.cs
Normal file
71
Robust.Shared/EntitySerialization/LoadResult.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing information about entities that were loaded from a yaml file.
|
||||
/// </summary>
|
||||
public sealed class LoadResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The file format version.
|
||||
/// </summary>
|
||||
public int Version;
|
||||
|
||||
/// <summary>
|
||||
/// The category of the file that was loaded in.
|
||||
/// This might not match the actual final result. E.g., when loading in a grid file, a map may automatically gets
|
||||
/// generated for it via <see cref="EntityDeserializer.AdoptGrids"/>.
|
||||
/// </summary>
|
||||
public FileCategory Category = FileCategory.Unknown;
|
||||
|
||||
/// <summary>
|
||||
/// The engine version that was used to write the file. See <see cref="CVars.BuildEngineVersion"/>.
|
||||
/// </summary>
|
||||
public string? EngineVersion;
|
||||
|
||||
/// <summary>
|
||||
/// The fork that was used to write the file. See <see cref="CVars.BuildForkId"/>.
|
||||
/// </summary>
|
||||
public string? ForkId;
|
||||
|
||||
/// <summary>
|
||||
/// The fork version that was used to write the file. See <see cref="CVars.BuildVersion"/>.
|
||||
/// </summary>
|
||||
public string? ForkVersion;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DateTime.UtcNow"/> when the file was created.
|
||||
/// </summary>
|
||||
public DateTime? Time;
|
||||
|
||||
/// <summary>
|
||||
/// Set of all entities that were created while the file was being loaded.
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> Entities = new();
|
||||
|
||||
/// <summary>
|
||||
/// Set of entities that are not parented to other entities. This will be a combination of <see cref="Maps"/>,
|
||||
/// <see cref="Orphans"/>, and <see cref="NullspaceEntities"/>.
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> RootNodes = new();
|
||||
|
||||
public readonly HashSet<Entity<MapComponent>> Maps = new();
|
||||
|
||||
public readonly HashSet<Entity<MapGridComponent>> Grids = new();
|
||||
|
||||
/// <summary>
|
||||
/// Deserialized entities that need to be assigned a new parent. These differ from "true" null-space entities.
|
||||
/// E,g, saving a grid without saving the map would make the grid an "orphan".
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> Orphans = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of null-space entities. This contains all entities without a parent that don't have a
|
||||
/// <see cref="MapComponent"/>, and were not listed as orphans
|
||||
/// </summary>
|
||||
public readonly HashSet<EntityUid> NullspaceEntities = new();
|
||||
}
|
||||
@@ -14,19 +14,25 @@ using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Maps;
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
[TypeSerializer]
|
||||
internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingDataNode>, ITypeCopyCreator<MapChunk>
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public MapChunk Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, ISerializationManager.InstantiationDelegate<MapChunk>? instantiationDelegate = null)
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<MapChunk>? instantiationDelegate = null)
|
||||
{
|
||||
var ind = (Vector2i) serializationManager.Read(typeof(Vector2i), node["ind"], hookCtx, context)!;
|
||||
var tileNode = (ValueDataNode)node["tiles"];
|
||||
@@ -50,10 +56,8 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
|
||||
IReadOnlyDictionary<int, string>? tileMap = null;
|
||||
|
||||
if (context is MapSerializationContext serContext)
|
||||
{
|
||||
if (context is EntityDeserializer serContext)
|
||||
tileMap = serContext.TileMap;
|
||||
}
|
||||
|
||||
if (tileMap == null)
|
||||
{
|
||||
@@ -104,16 +108,12 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
|
||||
root.Add("version", new ValueDataNode("6"));
|
||||
|
||||
Dictionary<int, int>? tileWriteMap = null;
|
||||
if (context is MapSerializationContext mapContext)
|
||||
tileWriteMap = mapContext.TileWriteMap;
|
||||
|
||||
gridNode.Value = SerializeTiles(value, tileWriteMap);
|
||||
gridNode.Value = SerializeTiles(value, context as EntitySerializer);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string SerializeTiles(MapChunk chunk, Dictionary<int, int>? tileWriteMap)
|
||||
private static string SerializeTiles(MapChunk chunk, EntitySerializer? serializer)
|
||||
{
|
||||
// number of bytes written per tile, because sizeof(Tile) is useless.
|
||||
const int structSize = 6;
|
||||
@@ -124,17 +124,34 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
using (var stream = new MemoryStream(barr))
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
if (serializer == null)
|
||||
{
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
writer.Write(tile.TypeId);
|
||||
writer.Write((byte) tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
}
|
||||
}
|
||||
return Convert.ToBase64String(barr);
|
||||
}
|
||||
|
||||
var lastTile = -1;
|
||||
var yamlId = -1;
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
var typeId = tile.TypeId;
|
||||
if (tileWriteMap != null)
|
||||
typeId = tileWriteMap[typeId];
|
||||
if (tile.TypeId != lastTile)
|
||||
yamlId = serializer.GetYamlTileId(tile.TypeId);
|
||||
|
||||
writer.Write(typeId);
|
||||
writer.Write((byte)tile.Flags);
|
||||
lastTile = tile.TypeId;
|
||||
writer.Write(yamlId);
|
||||
writer.Write((byte) tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
}
|
||||
}
|
||||
@@ -143,8 +160,12 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
return Convert.ToBase64String(barr);
|
||||
}
|
||||
|
||||
public MapChunk CreateCopy(ISerializationManager serializationManager, MapChunk source,
|
||||
IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
public MapChunk CreateCopy(
|
||||
ISerializationManager serializationManager,
|
||||
MapChunk source,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
var mapManager = dependencies.Resolve<IMapManager>();
|
||||
mapManager.SuppressOnTileChanged = true;
|
||||
139
Robust.Shared/EntitySerialization/Options.cs
Normal file
139
Robust.Shared/EntitySerialization/Options.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
public record struct SerializationOptions
|
||||
{
|
||||
public static readonly SerializationOptions Default = new();
|
||||
|
||||
/// <summary>
|
||||
/// What to do when serializing the EntityUid of an entity that is not one of entities currently being serialized.
|
||||
/// I.e., What should happen when serializing a map that has entities with components that store references to a
|
||||
/// null-space entity? Note that this does not affect the treatment of <see cref="TransformComponent.ParentUid"/>,
|
||||
/// which will never auto-include parents.
|
||||
/// </summary>
|
||||
public MissingEntityBehaviour MissingEntityBehaviour = MissingEntityBehaviour.IncludeNullspace;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to log an error when serializing an entity without its parent.
|
||||
/// </summary>
|
||||
public bool ErrorOnOrphan = true;
|
||||
|
||||
/// <summary>
|
||||
/// Log level to use when auto-including entities while serializing. Null implies no logs.
|
||||
/// See <see cref="MissingEntityBehaviour"/>.
|
||||
/// </summary>
|
||||
public LogLevel? LogAutoInclude = LogLevel.Info;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the serializer will log an error if it encounters a post map-init entity.
|
||||
/// </summary>
|
||||
public bool ExpectPreInit;
|
||||
|
||||
public FileCategory Category;
|
||||
|
||||
public SerializationOptions()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public record struct DeserializationOptions()
|
||||
{
|
||||
public static readonly DeserializationOptions Default = new();
|
||||
|
||||
/// <summary>
|
||||
/// If true, each loaded entity will get a <see cref="YamlUidComponent"/> that stores the uid that the entity
|
||||
/// had in the yaml file. This is used to maintain consistent entity labelling on subsequent saves.
|
||||
/// </summary>
|
||||
public bool StoreYamlUids = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, all maps that get created while loading this file will get map-initialized.
|
||||
/// </summary>
|
||||
public bool InitializeMaps = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, all maps that get created while loading this file will get paused.
|
||||
/// Note that the converse is not true, paused maps will not get un-paused if this is false.
|
||||
/// Pre-mapinit maps are assumed to be paused.
|
||||
/// </summary>
|
||||
public bool PauseMaps = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to log an error when starting up a grid entity that has no map.
|
||||
/// This usually indicates that someone is attempting to load an incorrect file type (e.g. loading a grid as a map).
|
||||
/// </summary>
|
||||
public bool LogOrphanedGrids = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to log an error when encountering an yaml entity id.
|
||||
/// <see cref="TransformComponent.ParentUid"/> is exempt from this.
|
||||
/// </summary>
|
||||
public bool LogInvalidEntities = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically assign map ids to any deserialized map entities.
|
||||
/// If false, maps need to be manually given ids before entities are initialized.
|
||||
/// </summary>
|
||||
public bool AssignMapids = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Superset of <see cref="EntitySerialization.DeserializationOptions"/> that contain information relevant to loading
|
||||
/// maps & grids, potentially onto other existing maps.
|
||||
/// </summary>
|
||||
public struct MapLoadOptions()
|
||||
{
|
||||
public static readonly MapLoadOptions Default = new();
|
||||
|
||||
/// <summary>
|
||||
/// If specified, all orphaned entities and the children of all loaded maps will be re-parented onto this map.
|
||||
/// I.e., this will merge map contents onto an existing map. This will also cause any maps that get loaded to
|
||||
/// delete themselves after their children have been moved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this option effectively causes <see cref="DeserializationOptions.InitializeMaps"/> and
|
||||
/// <see cref="DeserializationOptions.PauseMaps"/> to have no effect, as the target map is not a map that was
|
||||
/// created by the deserialization.
|
||||
/// </remarks>
|
||||
public MapId? MergeMap = null;
|
||||
|
||||
/// <summary>
|
||||
/// Offset to apply to the position of any loaded entities that are directly parented to a map.
|
||||
/// </summary>
|
||||
public Vector2 Offset;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation to apply to the position & local rotation of any loaded entities that are directly parented to a map.
|
||||
/// </summary>
|
||||
public Angle Rotation;
|
||||
|
||||
/// <summary>
|
||||
/// Options to use when deserializing entities.
|
||||
/// </summary>
|
||||
public DeserializationOptions DeserializationOptions = DeserializationOptions.Default;
|
||||
|
||||
/// <summary>
|
||||
/// When loading a single map, this will attempt to force the map to use the given map id. Generally, it is better
|
||||
/// to allow the map system to auto-allocate a map id, to avoid accidentally re-using an old id.
|
||||
/// </summary>
|
||||
public MapId? ForceMapId;
|
||||
|
||||
/// <summary>
|
||||
/// The expected <see cref="LoadResult.Category"/> for the file currently being read in, at the end of the entity
|
||||
/// creation step. Will log errors if the category doesn't match the expected one (e.g., trying to load a "map" from a file
|
||||
/// that doesn't contain any map entities).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that the effective final category may change by the time the file has fully loaded. E.g., when loading a
|
||||
/// file containing an orphaned grid, a map may be automatically created for the grid, but the category will still
|
||||
/// be <see cref="FileCategory.Grid"/>
|
||||
/// </remarks>
|
||||
public FileCategory? ExpectedCategory;
|
||||
}
|
||||
88
Robust.Shared/EntitySerialization/SerializationEnums.cs
Normal file
88
Robust.Shared/EntitySerialization/SerializationEnums.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Upload;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// This enum is used to indicate the type of entity data that was written to a file. The actual format of the file does
|
||||
/// not change, but it helps avoid mistakes like accidentally using a map file when trying to load a single grid.
|
||||
/// </summary>
|
||||
public enum FileCategory : byte
|
||||
{
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// File should contain a single orphaned entity, its children, and maybe some null-space entities.
|
||||
/// </summary>
|
||||
Entity,
|
||||
|
||||
/// <summary>
|
||||
/// File should contain a single grid, its children, and maybe some null-space entities.
|
||||
/// </summary>
|
||||
Grid,
|
||||
|
||||
/// <summary>
|
||||
/// File should contain a single map, its children, and maybe some null-space entities.
|
||||
/// </summary>
|
||||
Map,
|
||||
|
||||
/// <summary>
|
||||
/// File is a full game save, and will likely contain at least one map and a few null-space entities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The file might also contain additional yaml entries for things like prototypes uploaded via
|
||||
/// <see cref="IGamePrototypeLoadManager"/>, and might contain references to additional resources that need to be
|
||||
/// loaded (e.g., files uploaded using <see cref="SharedNetworkResourceManager"/>).
|
||||
/// </remarks>
|
||||
Save,
|
||||
}
|
||||
|
||||
public enum MissingEntityBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Log an error and replace the reference with <see cref="EntityUid.Invalid"/>
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Ignore the reference, replace it with <see cref="EntityUid.Invalid"/>
|
||||
/// </summary>
|
||||
Ignore,
|
||||
|
||||
/// <summary>
|
||||
/// Automatically include & serialize any referenced null-space entities and their children.
|
||||
/// I.e., entities that are not attached to any parent and are not maps. Any non-nullspace entities will result in
|
||||
/// an error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is primarily intended to make it easy to auto-include information carrying null-space entities. E.g., the
|
||||
/// "minds" of players, or entities that represent power or gas networks on a grid. Note that a full game save
|
||||
/// should still try to explicitly include all relevant entities, as this could still easily fail to auto-include
|
||||
/// relevant entities if they are not explicitly referenced in a data-field by some other entity.
|
||||
/// </remarks>
|
||||
IncludeNullspace,
|
||||
|
||||
/// <summary>
|
||||
/// Automatically include & serialize any referenced entity. Note that this means that the missing entity's
|
||||
/// parents will (generally) also be included, however this will not include other children. E.g., if serializing a
|
||||
/// grid that references an entity on the map, this will also cause the map to get serialized, but will not necessarily
|
||||
/// serialize everything on the map.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If trying to serialize an entity without its parent (i.e., its parent is truncated via
|
||||
/// <see cref="EntitySerializer.Truncate"/>), this will try to respect that. E.g., if a referenced entity is on the
|
||||
/// same map as a grid that is getting serialized, it should include the entity without including the map.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Note that this might unexpectedly change the <see cref="FileCategory"/>. I.e., trying to serialize a grid might
|
||||
/// accidentally lead to serializing a (partial?) map file.
|
||||
/// </remarks>
|
||||
PartialInclude,
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="PartialInclude"/> that will also automatically include the children of any entities that
|
||||
/// that are automatically included. Note that because auto-inclusion generally needs to include an entity's
|
||||
/// parents, this will include more than just the missing entity's direct children.
|
||||
/// </summary>
|
||||
AutoInclude,
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
// This partial class file contains methods for loading generic entities and grids. Map specific methods are in another
|
||||
// file
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Tries to load entities from a yaml file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
public bool TryLoadGeneric(
|
||||
ResPath file,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapComponent>>? maps,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
MapLoadOptions? options = null)
|
||||
{
|
||||
grids = null;
|
||||
maps = null;
|
||||
if (!TryLoadGeneric(file, out var data, options))
|
||||
return false;
|
||||
|
||||
maps = data.Maps;
|
||||
grids = data.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load entities from a yaml file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
|
||||
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to load.</param>
|
||||
/// <param name="result">Data class containing information about the loaded entities</param>
|
||||
/// <param name="options">Optional Options for configuring loading behaviour.</param>
|
||||
public bool TryLoadGeneric(ResPath file, [NotNullWhen(true)] out LoadResult? result, MapLoadOptions? options = null)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (!TryReadFile(file, out var data))
|
||||
return false;
|
||||
|
||||
_stopwatch.Restart();
|
||||
var ev = new BeforeEntityReadEvent();
|
||||
RaiseLocalEvent(ev);
|
||||
|
||||
var opts = options ?? MapLoadOptions.Default;
|
||||
|
||||
// If we are forcing a map id, we cannot auto-assign ids.
|
||||
opts.DeserializationOptions.AssignMapids = opts.ForceMapId == null;
|
||||
|
||||
if (opts.MergeMap is { } targetId && !_mapSystem.MapExists(targetId))
|
||||
throw new Exception($"Target map {targetId} does not exist");
|
||||
|
||||
if (opts.MergeMap != null && opts.ForceMapId != null)
|
||||
throw new Exception($"Invalid combination of MapLoadOptions");
|
||||
|
||||
if (_mapSystem.MapExists(opts.ForceMapId))
|
||||
throw new Exception($"Target map already exists");
|
||||
|
||||
// Using a local deserializer instead of a cached value, both to ensure that we don't accidentally carry over
|
||||
// data from a previous serializations, and because some entities cause other maps/grids to be loaded during
|
||||
// during mapinit.
|
||||
var deserializer = new EntityDeserializer(
|
||||
_dependency,
|
||||
data,
|
||||
opts.DeserializationOptions,
|
||||
ev.RenamedPrototypes,
|
||||
ev.DeletedPrototypes);
|
||||
|
||||
if (!deserializer.TryProcessData())
|
||||
{
|
||||
Log.Debug($"Failed to process entity data in {file}");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
deserializer.CreateEntities();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while creating entities: {e}");
|
||||
Delete(deserializer.Result);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (opts.ExpectedCategory is { } exp && exp != deserializer.Result.Category)
|
||||
{
|
||||
// Did someone try to load a map file as a grid or vice versa?
|
||||
Log.Error($"File does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reparent entities if loading entities onto an existing map.
|
||||
var merged = new HashSet<EntityUid>();
|
||||
MergeMaps(deserializer, opts, merged);
|
||||
|
||||
if (!SetMapId(deserializer, opts))
|
||||
return false;
|
||||
|
||||
// Apply any offsets & rotations specified by the load options
|
||||
ApplyTransform(deserializer, opts);
|
||||
|
||||
try
|
||||
{
|
||||
deserializer.StartEntities();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while starting entities: {e}");
|
||||
Delete(deserializer.Result);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (opts.MergeMap is {} map)
|
||||
MapInitalizeMerged(merged, map);
|
||||
|
||||
result = deserializer.Result;
|
||||
Log.Debug($"Loaded map in {_stopwatch.Elapsed}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a regular (non-map, non-grid) entity from a file.
|
||||
/// The loaded entity will initially be in null-space.
|
||||
/// If the file does not contain exactly one orphaned entity, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadEntity(
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<TransformComponent>? entity,
|
||||
DeserializationOptions? options = null)
|
||||
{
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Entity
|
||||
};
|
||||
|
||||
entity = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Orphans.Count == 1)
|
||||
{
|
||||
var uid = result.Orphans.Single();
|
||||
entity = (uid, Transform(uid));
|
||||
return true;
|
||||
}
|
||||
|
||||
Delete(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a grid entity from a file and parent it to the given map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadGrid(
|
||||
MapId map,
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
MergeMap = map,
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Grid
|
||||
};
|
||||
|
||||
grid = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Grids.Count == 1)
|
||||
{
|
||||
grid = result.Grids.Single();
|
||||
return true;
|
||||
}
|
||||
|
||||
Delete(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ApplyTransform(EntityDeserializer deserializer, MapLoadOptions opts)
|
||||
{
|
||||
if (opts.Rotation == Angle.Zero && opts.Offset == Vector2.Zero)
|
||||
return;
|
||||
|
||||
// If merging onto a single map, the transformation was already applied by SwapRootNode()
|
||||
if (opts.MergeMap != null)
|
||||
return;
|
||||
|
||||
var matrix = Matrix3Helpers.CreateTransform(opts.Offset, opts.Rotation);
|
||||
|
||||
// We want to apply the transforms to all children of any loaded maps. However, we can't just iterate over the
|
||||
// children of loaded maps, as transform component has not yet been initialized. I.e. xform.Children is empty.
|
||||
// Hence we iterate over all entities and check which ones are attached to maps.
|
||||
foreach (var uid in deserializer.Result.Entities)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (!_mapQuery.HasComp(xform.ParentUid))
|
||||
continue;
|
||||
|
||||
// The original comment around this bit of logic was just:
|
||||
// > Smelly
|
||||
// I don't know what sloth meant by that, but I guess applying transforms to grid-maps is a no-no?
|
||||
// Or more generally, loading a mapgrid onto another (potentially non-mapgrid) map is just generally kind of weird.
|
||||
if (_gridQuery.HasComponent(xform.ParentUid))
|
||||
continue;
|
||||
|
||||
var rot = xform.LocalRotation + opts.Rotation;
|
||||
var pos = Vector2.Transform(xform.LocalPosition, matrix);
|
||||
_xform.SetLocalPositionRotation(uid, pos, rot, xform);
|
||||
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
// This partial class file contains methods specific to loading maps
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map.
|
||||
/// If the file does not contain exactly one map, this will return false and delete all loaded entities.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMap(
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Map
|
||||
};
|
||||
|
||||
map = null;
|
||||
grids = null;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (result.Maps.Count == 1)
|
||||
{
|
||||
map = result.Maps.First();
|
||||
grids = result.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
Delete(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map, assign it the given map id.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If possible, it is better to use <see cref="TryLoadMap"/> which automatically assigns a <see cref="MapId"/>.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Note that this will not automatically initialize the map, unless specified via the <see cref="DeserializationOptions"/>.
|
||||
/// </remarks>
|
||||
public bool TryLoadMapWithId(
|
||||
MapId mapId,
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
map = null;
|
||||
grids = null;
|
||||
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Map
|
||||
};
|
||||
|
||||
if (_mapSystem.MapExists(mapId))
|
||||
throw new Exception($"Target map already exists");
|
||||
|
||||
opts.ForceMapId = mapId;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))
|
||||
return false;
|
||||
|
||||
map = new(uid.Value, comp);
|
||||
grids = result.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load a file containing a single map, and merge its children onto another map. After which the
|
||||
/// loaded map gets deleted.
|
||||
/// </summary>
|
||||
public bool TryMergeMap(
|
||||
MapId mapId,
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
grids = null;
|
||||
|
||||
var opts = new MapLoadOptions
|
||||
{
|
||||
Offset = offset,
|
||||
Rotation = rot,
|
||||
DeserializationOptions = options ?? DeserializationOptions.Default,
|
||||
ExpectedCategory = FileCategory.Map
|
||||
};
|
||||
|
||||
if (!_mapSystem.MapExists(mapId))
|
||||
throw new Exception($"Target map {mapId} does not exist");
|
||||
|
||||
opts.MergeMap = mapId;
|
||||
if (!TryLoadGeneric(path, out var result, opts))
|
||||
return false;
|
||||
|
||||
if (!_mapSystem.TryGetMap(mapId, out var uid) || !TryComp(uid, out MapComponent? comp))
|
||||
return false;
|
||||
|
||||
grids = result.Grids;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void MergeMaps(EntityDeserializer deserializer, MapLoadOptions opts, HashSet<EntityUid> merged)
|
||||
{
|
||||
if (opts.MergeMap is not {} targetId)
|
||||
return;
|
||||
|
||||
if (!_mapSystem.TryGetMap(targetId, out var targetUid))
|
||||
throw new Exception($"Target map {targetId} does not exist");
|
||||
|
||||
deserializer.Result.Category = FileCategory.Unknown;
|
||||
var rotation = opts.Rotation;
|
||||
var matrix = Matrix3Helpers.CreateTransform(opts.Offset, rotation);
|
||||
var target = new Entity<TransformComponent>(targetUid.Value, Transform(targetUid.Value));
|
||||
|
||||
// We want to apply the transforms to all children of any loaded maps. However, we can't just iterate over the
|
||||
// children of loaded maps, as transform component has not yet been initialized. I.e. xform.Children is empty.
|
||||
// Hence we iterate over all entities and check which ones are attached to maps.
|
||||
HashSet<EntityUid> maps = new();
|
||||
HashSet<EntityUid> logged = new();
|
||||
foreach (var uid in deserializer.Result.Entities)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
if (!_mapQuery.HasComp(xform.ParentUid))
|
||||
continue;
|
||||
|
||||
if (_gridQuery.HasComponent(xform.ParentUid) && logged.Add(xform.ParentUid))
|
||||
{
|
||||
Log.Error($"Merging a grid-map onto another map is not supported.");
|
||||
continue;
|
||||
}
|
||||
|
||||
maps.Add(xform.ParentUid);
|
||||
Merge(merged, uid, target, matrix, rotation);
|
||||
}
|
||||
|
||||
deserializer.ToDelete.UnionWith(maps);
|
||||
deserializer.Result.Maps.RemoveWhere(x => maps.Contains(x.Owner));
|
||||
|
||||
foreach (var uid in deserializer.Result.Orphans)
|
||||
{
|
||||
Merge(merged, uid, target, matrix, rotation);
|
||||
}
|
||||
|
||||
deserializer.Result.Orphans.Clear();
|
||||
}
|
||||
|
||||
private void Merge(
|
||||
HashSet<EntityUid> merged,
|
||||
EntityUid uid,
|
||||
Entity<TransformComponent> target,
|
||||
in Matrix3x2 matrix,
|
||||
Angle rotation)
|
||||
{
|
||||
merged.Add(uid);
|
||||
var xform = Transform(uid);
|
||||
var angle = xform.LocalRotation + rotation;
|
||||
var pos = Vector2.Transform(xform.LocalPosition, matrix);
|
||||
var coords = new EntityCoordinates(target.Owner, pos);
|
||||
_xform.SetCoordinates((uid, xform, MetaData(uid)), coords, rotation: angle, newParent: target.Comp);
|
||||
}
|
||||
|
||||
private void MapInitalizeMerged(HashSet<EntityUid> merged, MapId targetId)
|
||||
{
|
||||
// fuck me I hate this map merging bullshit.
|
||||
// loading a map "onto" another map shouldn't need to be supported by the generic map loading methods.
|
||||
// If something needs to do that, it should implement it itself.
|
||||
// AFAIK this only exists for the loadgamemap command?
|
||||
|
||||
if (!_mapSystem.TryGetMap(targetId, out var targetUid))
|
||||
throw new Exception($"Target map {targetId} does not exist");
|
||||
|
||||
if (_mapSystem.IsInitialized(targetUid.Value))
|
||||
{
|
||||
foreach (var uid in merged)
|
||||
{
|
||||
_mapSystem.RecursiveMapInit(uid);
|
||||
}
|
||||
}
|
||||
|
||||
var paused = _mapSystem.IsPaused(targetUid.Value);
|
||||
foreach (var uid in merged)
|
||||
{
|
||||
_mapSystem.RecursiveSetPaused(uid, paused);
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetMapId(EntityDeserializer deserializer, MapLoadOptions opts)
|
||||
{
|
||||
if (opts.ForceMapId is not { } id)
|
||||
return true;
|
||||
|
||||
if (deserializer.Result.Maps.Count != 1)
|
||||
{
|
||||
Log.Error(
|
||||
$"The {nameof(MapLoadOptions.ForceMapId)} option is only supported when loading a file containing a single map.");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
|
||||
var map = deserializer.Result.Maps.Single();
|
||||
_mapSystem.AssignMapId(map, id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Events;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
// This partial class file contains methods for serializing and saving entities, grids, and maps.
|
||||
public sealed partial class MapLoaderSystem
|
||||
{
|
||||
/// <inheritdoc cref="EntitySerializer.OnIsSerializeable"/>
|
||||
public event EntitySerializer.IsSerializableDelegate? OnIsSerializable;
|
||||
|
||||
/// <summary>
|
||||
/// Recursively serialize the given entity and its children.
|
||||
/// </summary>
|
||||
public (MappingDataNode Node, FileCategory Category) SerializeEntitiesRecursive(
|
||||
HashSet<EntityUid> entities,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
if (!entities.All(Exists))
|
||||
throw new Exception($"Cannot serialize deleted entities");
|
||||
|
||||
Log.Info($"Serializing entities: {string.Join(", ", entities.Select(x => ToPrettyString(x).ToString()))}");
|
||||
|
||||
var maps = entities.Select(x => Transform(x).MapID).ToHashSet();
|
||||
var ev = new BeforeSerializationEvent(entities, maps);
|
||||
RaiseLocalEvent(ev);
|
||||
|
||||
// In case no options were provided, we assume that if all of the starting entities are pre-init, we should
|
||||
// expect that **all** entities that get serialized should be pre-init.
|
||||
var opts = options ?? SerializationOptions.Default with
|
||||
{
|
||||
ExpectPreInit = (entities.All(x => LifeStage(x) < EntityLifeStage.MapInitialized))
|
||||
};
|
||||
|
||||
var serializer = new EntitySerializer(_dependency, opts);
|
||||
serializer.OnIsSerializeable += OnIsSerializable;
|
||||
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
serializer.SerializeEntityRecursive(ent);
|
||||
}
|
||||
|
||||
var data = serializer.Write();
|
||||
var cat = serializer.GetCategory();
|
||||
|
||||
var ev2 = new AfterSerializationEvent(entities, data, cat);
|
||||
RaiseLocalEvent(ev2);
|
||||
|
||||
Log.Debug($"Serialized {serializer.EntityData.Count} entities in {_stopwatch.Elapsed}");
|
||||
return (data, cat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a standard (non-grid, non-map) entity and all of its children and write the result to a
|
||||
/// yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveEntity(EntityUid entity, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (_mapQuery.HasComp(entity))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(entity)} is a map. Use {nameof(TrySaveMap)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_gridQuery.HasComp(entity))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(entity)} is a grid. Use {nameof(TrySaveGrid)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
opts.Category = FileCategory.Entity;
|
||||
|
||||
MappingDataNode data;
|
||||
FileCategory cat;
|
||||
try
|
||||
{
|
||||
(data, cat) = SerializeEntitiesRecursive([entity], opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize entity {ToPrettyString(entity)}:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cat != FileCategory.Entity)
|
||||
{
|
||||
Log.Error($"Failed to save {ToPrettyString(entity)} as a singular entity. Output: {cat}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a map and all of its children and write the result to a yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveMap(MapId mapId, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapUid))
|
||||
return TrySaveMap(mapUid.Value, path, options);
|
||||
|
||||
Log.Error($"Unable to find map {mapId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a map and all of its children and write the result to a yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveMap(EntityUid map, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (!_mapQuery.HasComp(map))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(map)} is not a map.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
opts.Category = FileCategory.Map;
|
||||
|
||||
MappingDataNode data;
|
||||
FileCategory cat;
|
||||
try
|
||||
{
|
||||
(data, cat) = SerializeEntitiesRecursive([map], opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize map {ToPrettyString(map)}:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cat != FileCategory.Map)
|
||||
{
|
||||
Log.Error($"Failed to save {ToPrettyString(map)} as a map. Output: {cat}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize a grid and all of its children and write the result to a yaml file.
|
||||
/// </summary>
|
||||
public bool TrySaveGrid(EntityUid grid, ResPath path, SerializationOptions? options = null)
|
||||
{
|
||||
if (!_gridQuery.HasComp(grid))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(grid)} is not a grid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_mapQuery.HasComp(grid))
|
||||
{
|
||||
Log.Error($"{ToPrettyString(grid)} is a map, not (just) a grid. Use {nameof(TrySaveMap)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
opts.Category = FileCategory.Grid;
|
||||
|
||||
MappingDataNode data;
|
||||
FileCategory cat;
|
||||
try
|
||||
{
|
||||
(data, cat) = SerializeEntitiesRecursive([grid], opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize grid {ToPrettyString(grid)}:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cat != FileCategory.Grid)
|
||||
{
|
||||
Log.Error($"Failed to save {ToPrettyString(grid)} as a grid. Output: {cat}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize an entities and all of their children to a yaml file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
EntityUid uid,
|
||||
ResPath path,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
return TrySaveGeneric([uid], path, out category, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize one or more entities and all of their children to a yaml file.
|
||||
/// This makes no assumptions about the expected entity or resulting file category.
|
||||
/// If possible, use the map/grid specific variants instead.
|
||||
/// </summary>
|
||||
public bool TrySaveGeneric(
|
||||
HashSet<EntityUid> entities,
|
||||
ResPath path,
|
||||
out FileCategory category,
|
||||
SerializationOptions? options = null)
|
||||
{
|
||||
category = FileCategory.Unknown;
|
||||
if (entities.Count == 0)
|
||||
return false;
|
||||
|
||||
var opts = options ?? SerializationOptions.Default;
|
||||
|
||||
MappingDataNode data;
|
||||
try
|
||||
{
|
||||
(data, category) = SerializeEntitiesRecursive(entities, opts);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while trying to serialize entities:\n{e}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Write(path, data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
132
Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.cs
Normal file
132
Robust.Shared/EntitySerialization/Systems/MapLoaderSystem.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.EntitySerialization.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides methods for saving and loading maps and grids.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The save & load methods are basically wrappers around <see cref="EntitySerializer"/> and
|
||||
/// <see cref="EntityDeserializer"/>, which can be used for more control over serialization.
|
||||
/// </remarks>
|
||||
public sealed partial class MapLoaderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xform = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependency = default!;
|
||||
|
||||
private Stopwatch _stopwatch = new();
|
||||
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_mapQuery = GetEntityQuery<MapComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
}
|
||||
|
||||
private void Write(ResPath path, MappingDataNode data)
|
||||
{
|
||||
Log.Info($"Saving serialized results to {path}");
|
||||
path = path.ToRootedPath();
|
||||
var document = new YamlDocument(data.ToYaml());
|
||||
using var writer = _resourceManager.UserData.OpenWriteText(path);
|
||||
{
|
||||
var stream = new YamlStream {document};
|
||||
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryReadFile(ResPath file, [NotNullWhen(true)] out MappingDataNode? data)
|
||||
{
|
||||
var resPath = file.ToRootedPath();
|
||||
data = null;
|
||||
|
||||
if (!TryGetReader(resPath, out var reader))
|
||||
return false;
|
||||
|
||||
Log.Info($"Loading file: {resPath}");
|
||||
_stopwatch.Restart();
|
||||
|
||||
using var textReader = reader;
|
||||
var documents = DataNodeParser.ParseYamlStream(reader).ToArray();
|
||||
Log.Debug($"Loaded yml stream in {_stopwatch.Elapsed}");
|
||||
|
||||
// Yes, logging errors in a "try" method is kinda shit, but it was throwing exceptions when I found it and it does
|
||||
// make sense to at least provide some kind of feedback for why it failed.
|
||||
switch (documents.Length)
|
||||
{
|
||||
case < 1:
|
||||
Log.Error("Stream has no YAML documents.");
|
||||
return false;
|
||||
case > 1:
|
||||
Log.Error("Stream too many YAML documents. Map files store exactly one.");
|
||||
return false;
|
||||
default:
|
||||
data = (MappingDataNode) documents[0].Root;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetReader(ResPath resPath, [NotNullWhen(true)] out TextReader? reader)
|
||||
{
|
||||
if (_resourceManager.UserData.Exists(resPath))
|
||||
{
|
||||
// Log warning if file exists in both user and content data.
|
||||
if (_resourceManager.ContentFileExists(resPath))
|
||||
Log.Warning("Reading map user data instead of content");
|
||||
|
||||
reader = _resourceManager.UserData.OpenText(resPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_resourceManager.TryContentFileRead(resPath, out var contentReader))
|
||||
{
|
||||
reader = new StreamReader(contentReader);
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Error($"File not found: {resPath}");
|
||||
reader = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for deleting all loaded entities.
|
||||
/// </summary>
|
||||
public void Delete(LoadResult result)
|
||||
{
|
||||
foreach (var uid in result.Maps)
|
||||
{
|
||||
Del(uid);
|
||||
}
|
||||
|
||||
foreach (var uid in result.Orphans)
|
||||
{
|
||||
Del(uid);
|
||||
}
|
||||
|
||||
foreach (var uid in result.Entities)
|
||||
{
|
||||
Del(uid);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -311,11 +311,16 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
|
||||
public EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
|
||||
{
|
||||
return CreateEntity(prototypeName, out _, overrides);
|
||||
}
|
||||
|
||||
public EntityUid CreateEntityUninitialized(string? prototypeName, out MetaDataComponent meta, ComponentRegistry? overrides = null)
|
||||
{
|
||||
return CreateEntity(prototypeName, out meta, overrides);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
|
||||
{
|
||||
@@ -548,7 +553,28 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
TransformComponent? parentXform = null;
|
||||
if (xform.ParentUid.IsValid())
|
||||
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
|
||||
{
|
||||
if (xform.LifeStage < ComponentLifeStage.Initialized)
|
||||
{
|
||||
// Entity is being deleted before initialization ever finished.
|
||||
// The entity will not yet have been added to the parent's transform component.
|
||||
// This is seemingly pretty error prone ATM, and I'm not even sure if it should be supported?
|
||||
|
||||
// Just in case it HAS somehow been added, make sure we remove it.
|
||||
if (TransformQuery.TryComp(xform.ParentUid, out parentXform) && parentXform._children.Remove(e))
|
||||
DebugTools.Assert($"Child entity {ToPrettyString(e)} was added to the parent's child set prior to being initialized?");
|
||||
|
||||
parentXform = null;
|
||||
xform._parent = EntityUid.Invalid;
|
||||
xform._anchored = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use resolve for automatic error logging.
|
||||
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
|
||||
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
|
||||
}
|
||||
}
|
||||
|
||||
// Then actually delete them
|
||||
RecursiveDeleteEntity(e, meta, xform, parentXform);
|
||||
@@ -783,7 +809,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Allocates an entity and stores it but does not load components or do initialization.
|
||||
/// </summary>
|
||||
private protected EntityUid AllocEntity(
|
||||
protected internal EntityUid AllocEntity(
|
||||
EntityPrototype? prototype,
|
||||
out MetaDataComponent metadata)
|
||||
{
|
||||
@@ -793,6 +819,9 @@ namespace Robust.Shared.GameObjects
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="AllocEntity(Robust.Shared.Prototypes.EntityPrototype?,out Robust.Shared.GameObjects.MetaDataComponent)"/>
|
||||
internal EntityUid AllocEntity(EntityPrototype? prototype) => AllocEntity(prototype, out _);
|
||||
|
||||
/// <summary>
|
||||
/// Allocates an entity and stores it but does not load components or do initialization.
|
||||
/// </summary>
|
||||
@@ -872,21 +901,9 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
|
||||
{
|
||||
EntityPrototype.LoadEntity((entity, MetaQuery.GetComponent(entity)), ComponentFactory, this, _serManager, context);
|
||||
}
|
||||
|
||||
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
|
||||
{
|
||||
var meta = MetaQuery.GetComponent(entity);
|
||||
DebugTools.Assert(meta.EntityPrototype == prototype);
|
||||
EntityPrototype.LoadEntity((entity, meta), ComponentFactory, this, _serManager, context);
|
||||
}
|
||||
|
||||
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
|
||||
{
|
||||
var doMapInit = _mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
|
||||
var doMapInit = _mapSystem.IsInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
|
||||
InitializeAndStartEntity(entity, doMapInit);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public sealed class EntitySystemManager : IEntitySystemManager, IPostInjectInit
|
||||
{
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[IoC.Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[IoC.Dependency] private readonly ProfManager _profManager = default!;
|
||||
[IoC.Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[IoC.Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ProfManager _profManager = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
@@ -35,6 +35,18 @@ namespace Robust.Shared.GameObjects
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
internal DependencyCollection SystemDependencyCollection = default!;
|
||||
|
||||
public IDependencyCollection DependencyCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_initialized)
|
||||
return SystemDependencyCollection;
|
||||
|
||||
throw new InvalidOperationException($"{nameof(EntitySystemManager)} has not been initialized.");
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Type> _systemTypes = new();
|
||||
|
||||
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -131,5 +132,10 @@ namespace Robust.Shared.GameObjects
|
||||
IEnumerable<Type> GetEntitySystemTypes();
|
||||
bool TryGetEntitySystem(Type sysType, [NotNullWhen(true)] out object? system);
|
||||
object GetEntitySystem(Type sysType);
|
||||
|
||||
/// <summary>
|
||||
/// Dependency collection that contains all the loaded systems.
|
||||
/// </summary>
|
||||
public IDependencyCollection DependencyCollection { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -12,7 +13,7 @@ namespace Robust.Shared.GameObjects;
|
||||
/// <summary>
|
||||
/// Network identifier for entities; used by client and server to refer to the same entity where their local <see cref="EntityUid"/> may differ.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
[Serializable, NetSerializable, CopyByRef]
|
||||
public readonly struct NetEntity : IEquatable<NetEntity>, IComparable<NetEntity>, ISpanFormattable
|
||||
{
|
||||
public readonly int Id;
|
||||
|
||||
@@ -125,7 +125,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
SubscribeLocalEvent<BroadphaseComponent, ComponentAdd>(OnBroadphaseAdd);
|
||||
SubscribeLocalEvent<BroadphaseComponent, ComponentInit>(OnBroadphaseInit);
|
||||
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChange);
|
||||
SubscribeLocalEvent<MapCreatedEvent>(OnMapChange);
|
||||
|
||||
_transform.OnBeforeMoveEvent += OnMove;
|
||||
EntityManager.EntityInitialized += OnEntityInit;
|
||||
@@ -194,9 +194,9 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapChange(MapChangedEvent ev)
|
||||
private void OnMapChange(MapCreatedEvent ev)
|
||||
{
|
||||
if (ev.Created && ev.Map != MapId.Nullspace)
|
||||
if (ev.MapId != MapId.Nullspace)
|
||||
{
|
||||
EnsureComp<BroadphaseComponent>(ev.Uid);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
@@ -70,14 +71,15 @@ public abstract partial class SharedMapSystem
|
||||
if (component.MapId == MapId.Nullspace)
|
||||
{
|
||||
if (state.MapId == MapId.Nullspace)
|
||||
throw new Exception($"Received invalid map state? {ToPrettyString(uid)}");
|
||||
throw new Exception($"Received invalid map state for {ToPrettyString(uid)}");
|
||||
|
||||
component.MapId = state.MapId;
|
||||
Maps.Add(component.MapId, uid);
|
||||
AssignMapId((uid, component), state.MapId);
|
||||
RecursiveMapIdUpdate(uid, uid, component.MapId);
|
||||
}
|
||||
|
||||
DebugTools.AssertEqual(component.MapId, state.MapId);
|
||||
if (component.MapId != state.MapId)
|
||||
throw new Exception($"Received invalid map state for {ToPrettyString(uid)}");
|
||||
|
||||
component.LightingEnabled = state.LightingEnabled;
|
||||
component.MapInitialized = state.Initialized;
|
||||
|
||||
@@ -119,26 +121,75 @@ public abstract partial class SharedMapSystem
|
||||
EnsureComp<MovedGridsComponent>(uid);
|
||||
}
|
||||
|
||||
private void OnCompInit(EntityUid uid, MapComponent component, ComponentInit args)
|
||||
internal void AssignMapId(Entity<MapComponent> map, MapId? id = null)
|
||||
{
|
||||
if (component.MapId == MapId.Nullspace)
|
||||
component.MapId = GetNextMapId();
|
||||
|
||||
DebugTools.AssertEqual(component.MapId.IsClientSide, IsClientSide(uid));
|
||||
if (!Maps.TryAdd(component.MapId, uid))
|
||||
if (map.Comp.MapId != MapId.Nullspace)
|
||||
{
|
||||
if (Maps[component.MapId] != uid)
|
||||
throw new Exception($"Attempted to initialize a map {ToPrettyString(uid)} with a duplicate map id {component.MapId}");
|
||||
if (id != null && map.Comp.MapId != id)
|
||||
{
|
||||
QueueDel(map.Owner);
|
||||
throw new Exception($"Map entity {ToPrettyString(map.Owner)} has already been assigned an id");
|
||||
}
|
||||
|
||||
if (!Maps.TryGetValue(map.Comp.MapId, out var existing) || existing != map.Owner)
|
||||
{
|
||||
QueueDel(map.Owner);
|
||||
throw new Exception($"Map entity {ToPrettyString(map.Owner)} was improperly assigned a map id?");
|
||||
}
|
||||
|
||||
DebugTools.Assert(UsedIds.Contains(map.Comp.MapId));
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = new MapChangedEvent(uid, component.MapId, true);
|
||||
RaiseLocalEvent(uid, msg, true);
|
||||
map.Comp.MapId = id ?? GetNextMapId();
|
||||
|
||||
if (IsClientSide(map) != map.Comp.MapId.IsClientSide)
|
||||
throw new Exception($"Attempting to assign a client-side map id to a networked entity or vice-versa");
|
||||
|
||||
if (!UsedIds.Add(map.Comp.MapId))
|
||||
Log.Warning($"Re-using a previously used map id ({map.Comp.MapId}) for map entity {ToPrettyString(map)}");
|
||||
|
||||
if (Maps.TryAdd(map.Comp.MapId, map.Owner))
|
||||
return;
|
||||
|
||||
if (Maps[map.Comp.MapId] == map.Owner)
|
||||
return;
|
||||
|
||||
QueueDel(map);
|
||||
throw new Exception(
|
||||
$"Attempted to assign an existing mapId {map.Comp} to a map entity {ToPrettyString(map.Owner)}");
|
||||
}
|
||||
|
||||
private void OnCompInit(Entity<MapComponent> map, ref ComponentInit args)
|
||||
{
|
||||
AssignMapId(map);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var msg = new MapChangedEvent(map, map.Comp.MapId, true);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
RaiseLocalEvent(map, msg, true);
|
||||
var ev = new MapCreatedEvent(map, map.Comp.MapId);
|
||||
RaiseLocalEvent(map, ev, true);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, MapComponent component, MapInitEvent args)
|
||||
{
|
||||
DebugTools.Assert(!component.MapInitialized);
|
||||
component.MapInitialized = true;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void OnCompStartup(EntityUid uid, MapComponent component, ComponentStartup args)
|
||||
{
|
||||
if (component.MapPaused)
|
||||
RecursiveSetPaused(uid, true);
|
||||
// un-initialized maps are always paused.
|
||||
component.MapPaused |= !component.MapInitialized;
|
||||
|
||||
if (!component.MapPaused)
|
||||
return;
|
||||
|
||||
// Recursively pause all entities on the map
|
||||
component.MapPaused = false;
|
||||
SetPaused(uid, true);
|
||||
}
|
||||
|
||||
private void OnMapRemoved(EntityUid uid, MapComponent component, ComponentShutdown args)
|
||||
@@ -146,8 +197,13 @@ public abstract partial class SharedMapSystem
|
||||
DebugTools.Assert(component.MapId != MapId.Nullspace);
|
||||
Maps.Remove(component.MapId);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var msg = new MapChangedEvent(uid, component.MapId, false);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
RaiseLocalEvent(uid, msg, true);
|
||||
|
||||
var ev = new MapRemovedEvent(uid, component.MapId);
|
||||
RaiseLocalEvent(uid, ev, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -181,19 +237,17 @@ public abstract partial class SharedMapSystem
|
||||
if (_netManager.IsClient && _netManager.IsConnected && !mapId.IsClientSide)
|
||||
throw new ArgumentException($"Attempted to create a client-side map entity with a non client-side map ID?");
|
||||
|
||||
var uid = EntityManager.CreateEntityUninitialized(null);
|
||||
var map = _factory.GetComponent<MapComponent>();
|
||||
map.MapId = mapId;
|
||||
AddComp(uid, map);
|
||||
if (UsedIds.Contains(mapId))
|
||||
Log.Warning($"Re-using MapId: {mapId}");
|
||||
|
||||
// Give the entity a name, mainly for debugging. Content can always override this with a localized name.
|
||||
var meta = MetaData(uid);
|
||||
_meta.SetEntityName(uid, $"Map Entity", meta);
|
||||
var (uid, map, meta) = CreateUninitializedMap();
|
||||
DebugTools.AssertEqual(map.MapId, MapId.Nullspace);
|
||||
AssignMapId((uid, map), mapId);
|
||||
|
||||
// Initialize components. this should add the map id to the collections.
|
||||
EntityManager.InitializeComponents(uid, meta);
|
||||
EntityManager.StartComponents(uid);
|
||||
DebugTools.Assert(Maps[mapId] == uid);
|
||||
EntityManager.InitializeEntity(uid, meta);
|
||||
EntityManager.StartEntity(uid);
|
||||
DebugTools.AssertEqual(Maps[mapId], uid);
|
||||
|
||||
if (runMapInit)
|
||||
InitializeMap((uid, map));
|
||||
@@ -202,4 +256,22 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
public Entity<MapComponent, MetaDataComponent> CreateUninitializedMap()
|
||||
{
|
||||
var uid = EntityManager.CreateEntityUninitialized(null, out var meta);
|
||||
_meta.SetEntityName(uid, $"Map Entity", meta);
|
||||
return (uid, AddComp<MapComponent>(uid), meta);
|
||||
}
|
||||
|
||||
public void DeleteMap(MapId mapId)
|
||||
{
|
||||
if (TryGetMap(mapId, out var uid))
|
||||
Del(uid);
|
||||
}
|
||||
|
||||
public IEnumerable<MapId> GetAllMapIds()
|
||||
{
|
||||
return Maps.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,6 @@ public abstract partial class SharedMapSystem
|
||||
return map.Comp.MapInitialized;
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, MapComponent component, MapInitEvent args)
|
||||
{
|
||||
DebugTools.Assert(!component.MapInitialized);
|
||||
component.MapInitialized = true;
|
||||
EntityManager.Dirty(uid, component);
|
||||
}
|
||||
|
||||
public void InitializeMap(MapId mapId, bool unpause = true)
|
||||
{
|
||||
if(!Maps.TryGetValue(mapId, out var uid))
|
||||
@@ -63,7 +56,7 @@ public abstract partial class SharedMapSystem
|
||||
SetPaused(map, false);
|
||||
}
|
||||
|
||||
private void RecursiveMapInit(EntityUid entity)
|
||||
internal void RecursiveMapInit(EntityUid entity)
|
||||
{
|
||||
var toInitialize = new List<EntityUid> {entity};
|
||||
for (var i = 0; i < toInitialize.Count; i++)
|
||||
|
||||
@@ -22,7 +22,7 @@ public abstract partial class SharedMapSystem
|
||||
if (!_mapQuery.Resolve(map, ref map.Comp))
|
||||
return false;
|
||||
|
||||
return map.Comp.MapPaused;
|
||||
return map.Comp.MapPaused || !map.Comp.MapInitialized;
|
||||
}
|
||||
|
||||
public void SetPaused(MapId mapId, bool paused)
|
||||
@@ -49,7 +49,7 @@ public abstract partial class SharedMapSystem
|
||||
RecursiveSetPaused(map, paused);
|
||||
}
|
||||
|
||||
private void RecursiveSetPaused(EntityUid entity, bool paused)
|
||||
internal void RecursiveSetPaused(EntityUid entity, bool paused)
|
||||
{
|
||||
_meta.SetEntityPaused(entity, paused);
|
||||
foreach (var child in Transform(entity)._children)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -32,6 +33,13 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
internal Dictionary<MapId, EntityUid> Maps { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// This hashset is used to try prevent MapId re-use. This is mainly for auto-assigned map ids.
|
||||
/// Loading a map with a specific id (e.g., the various mapping commands) may still result in an id being
|
||||
/// reused.
|
||||
/// </summary>
|
||||
protected HashSet<MapId> UsedIds = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -53,6 +61,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Arguments for when a map is created or deleted.
|
||||
/// </summary>
|
||||
[Obsolete("Use map creation or deletion events")]
|
||||
public sealed class MapChangedEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Uid;
|
||||
@@ -83,6 +92,16 @@ namespace Robust.Shared.GameObjects
|
||||
public bool Destroyed => !Created;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised whenever a map is created.
|
||||
/// </summary>
|
||||
public readonly record struct MapCreatedEvent(EntityUid Uid, MapId MapId);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised whenever a map is removed.
|
||||
/// </summary>
|
||||
public readonly record struct MapRemovedEvent(EntityUid Uid, MapId MapId);
|
||||
|
||||
#pragma warning disable CS0618
|
||||
public sealed class GridStartupEvent : EntityEventArgs
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -214,7 +214,15 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
else if (_mapQuery.TryComp(uid, out var mapComp))
|
||||
{
|
||||
DebugTools.AssertNotEqual(mapComp.MapId, MapId.Nullspace);
|
||||
if (mapComp.MapId == MapId.Nullspace)
|
||||
{
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw new Exception("Transform is initialising before map ids have been assigned?");
|
||||
#endif
|
||||
Log.Error($"Transform is initialising before map ids have been assigned?");
|
||||
_map.AssignMapId((uid, mapComp));
|
||||
}
|
||||
|
||||
xform.MapUid = uid;
|
||||
xform.MapID = mapComp.MapId;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (xform.GridUid == xform.ParentUid)
|
||||
return (xform.Coordinates, GetWorldRotation(xform, XformQuery));
|
||||
|
||||
DebugTools.Assert(!_mapManager.IsGrid(uid) && !_mapManager.IsMap(uid));
|
||||
DebugTools.Assert(!HasComp<MapComponent>(uid) && !HasComp<MapComponent>(uid));
|
||||
|
||||
var (pos, worldRot) = GetWorldPositionRotation(xform, XformQuery);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Shared.Map.Components
|
||||
[DataField]
|
||||
public bool LightingEnabled { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
[ViewVariables(VVAccess.ReadOnly), Access(typeof(SharedMapSystem), Other = AccessPermissions.ReadExecute)]
|
||||
public MapId MapId { get; internal set; } = MapId.Nullspace;
|
||||
|
||||
[DataField, Access(typeof(SharedMapSystem), typeof(MapManager))]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
|
||||
namespace Robust.Shared.Map.Events;
|
||||
|
||||
@@ -28,17 +30,13 @@ public sealed class BeforeEntityReadEvent
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This event is broadcast just before an entity gets serialized.
|
||||
/// This event is broadcast just before the given entities (and their children) are serialized.
|
||||
/// For convenience, the event also contains a set with all the maps that the entities are on. This does not
|
||||
/// necessarily mean that the maps are themselves getting serialized.
|
||||
/// </summary>
|
||||
public sealed class BeforeSaveEvent(EntityUid entity, EntityUid? map)
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity that is going to be saved. usually a map or grid.
|
||||
/// </summary>
|
||||
public EntityUid Entity = entity;
|
||||
public readonly record struct BeforeSerializationEvent(HashSet<EntityUid> Entities, HashSet<MapId> MapIds);
|
||||
|
||||
/// <summary>
|
||||
/// The map that the <see cref="Entity"/> is on.
|
||||
/// </summary>
|
||||
public EntityUid? Map = map;
|
||||
}
|
||||
/// <summary>
|
||||
/// This event is broadcast just after entities (and their children) have been serialized, but before it gets written to a yaml file.
|
||||
/// </summary>
|
||||
public readonly record struct AfterSerializationEvent(HashSet<EntityUid> Entities, MappingDataNode Node, FileCategory Category);
|
||||
|
||||
@@ -47,21 +47,25 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="mapId">The map ID to check existence of.</param>
|
||||
/// <returns>True if the map exists, false otherwise.</returns>
|
||||
[Obsolete("Use MapSystem")]
|
||||
bool MapExists([NotNullWhen(true)] MapId? mapId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the map entity ID for a given map, or an invalid entity Id if the map does not exist.
|
||||
/// </summary>
|
||||
[Obsolete("Use TryGetMap")]
|
||||
[Obsolete("Use MapSystem")]
|
||||
EntityUid GetMapEntityId(MapId mapId);
|
||||
|
||||
/// <summary>
|
||||
/// Replaces GetMapEntity()'s throw-on-failure semantics.
|
||||
/// </summary>
|
||||
[Obsolete("Use MapSystem")]
|
||||
EntityUid GetMapEntityIdOrThrow(MapId mapId);
|
||||
|
||||
[Obsolete("Use MapSystem")]
|
||||
IEnumerable<MapId> GetAllMapIds();
|
||||
|
||||
[Obsolete("Use MapSystem")]
|
||||
void DeleteMap(MapId mapId);
|
||||
|
||||
// ReSharper disable once MethodOverloadWithOptionalParameter
|
||||
@@ -205,20 +209,22 @@ namespace Robust.Shared.Map
|
||||
[Obsolete("Just delete the grid entity")]
|
||||
void DeleteGrid(EntityUid euid);
|
||||
|
||||
[Obsolete("Use HasComp")]
|
||||
bool IsGrid(EntityUid uid);
|
||||
|
||||
[Obsolete("Use HasComp")]
|
||||
bool IsMap(EntityUid uid);
|
||||
|
||||
//
|
||||
// Pausing functions
|
||||
//
|
||||
|
||||
[Obsolete("Use MapSystem")]
|
||||
void SetMapPaused(MapId mapId, bool paused);
|
||||
|
||||
[Obsolete("Use MapSystem")]
|
||||
void DoMapInitialize(MapId mapId);
|
||||
|
||||
[Obsolete("Use CreateMap's runMapInit argument")]
|
||||
void AddUninitializedMap(MapId mapId);
|
||||
|
||||
[Obsolete("Use MapSystem")]
|
||||
bool IsMapPaused(MapId mapId);
|
||||
|
||||
|
||||
@@ -67,15 +67,5 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="tileDef">THe definition to register.</param>
|
||||
void Register(ITileDefinition tileDef);
|
||||
|
||||
/// <summary>
|
||||
/// Register a tile alias with this manager.
|
||||
/// The tile need not exist yet - the alias's creation will be deferred until it exists.
|
||||
/// Tile aliases do not have IDs of their own and do not show up in enumeration.
|
||||
/// Their main utility is for easier map migration.
|
||||
/// </summary>
|
||||
/// <param name="src">The source tile (i.e. name of the alias).</param>
|
||||
/// <param name="dst">The destination tile (i.e. the actual concrete tile).</param>
|
||||
void AssignAlias(string src, string dst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Robust.Shared.Map
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
return IsClientSide ? $"c{-Value}" : Value.ToString();
|
||||
}
|
||||
|
||||
public bool IsClientSide => Value < 0;
|
||||
|
||||
@@ -125,6 +125,9 @@ internal partial class MapManager
|
||||
EntityManager.System<MetaDataSystem>().SetEntityName(gridEnt, $"grid", meta);
|
||||
EntityManager.InitializeComponents(gridEnt, meta);
|
||||
EntityManager.StartComponents(gridEnt);
|
||||
// Note that this does not actually map-initialize the grid entity, even if the map its being spawn on has already been initialized.
|
||||
// I don't know whether that is intentional or not.
|
||||
|
||||
return (gridEnt, grid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
@@ -28,16 +27,10 @@ public sealed class MapEventArgs : EventArgs
|
||||
|
||||
internal partial class MapManager
|
||||
{
|
||||
private Dictionary<MapId, EntityUid> _mapEntities => _mapSystem.Maps;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void DeleteMap(MapId mapId)
|
||||
{
|
||||
if (!_mapEntities.TryGetValue(mapId, out var ent) || !ent.IsValid())
|
||||
throw new InvalidOperationException($"Attempted to delete nonexistent map '{mapId}'");
|
||||
|
||||
EntityManager.DeleteEntity(ent);
|
||||
DebugTools.Assert(!_mapEntities.ContainsKey(mapId));
|
||||
_mapSystem.DeleteMap(mapId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -81,7 +74,7 @@ internal partial class MapManager
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<MapId> GetAllMapIds()
|
||||
{
|
||||
return _mapEntities.Keys;
|
||||
return _mapSystem.GetAllMapIds();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
{
|
||||
@@ -27,14 +25,6 @@ namespace Robust.Shared.Map
|
||||
return _mapSystem.IsInitialized(mapId);
|
||||
}
|
||||
|
||||
public void AddUninitializedMap(MapId mapId)
|
||||
{
|
||||
var ent = GetMapEntityId(mapId);
|
||||
EntityManager.GetComponent<MapComponent>(ent).MapInitialized = false;
|
||||
var meta = EntityManager.GetComponent<MetaDataComponent>(ent);
|
||||
((EntityManager)EntityManager).SetLifeStage(meta, EntityLifeStage.Initialized);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMapPaused(MapId mapId)
|
||||
{
|
||||
@@ -87,7 +77,7 @@ namespace Robust.Shared.Map
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine(IsMapPaused(mapId).ToString());
|
||||
shell.WriteLine(_mapSystem.IsPaused(mapId).ToString());
|
||||
});
|
||||
|
||||
_conhost.RegisterCommand("unpausemap",
|
||||
|
||||
@@ -45,54 +45,54 @@ internal partial class MapManager
|
||||
public void FindGridsIntersecting<T>(MapId mapId, T shape, Transform transform,
|
||||
ref List<Entity<MapGridComponent>> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, shape, transform, ref grids, approx, includeMap);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt.Value, shape, transform, ref grids, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<T>(MapId mapId, T shape, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, shape, transform, callback, includeMap, approx);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt.Value, shape, transform, callback, includeMap, approx);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, Box2 worldAABB, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, worldAABB, callback, approx, includeMap);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt.Value, worldAABB, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(MapId mapId, Box2 worldAABB, ref TState state, GridCallback<TState> callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var map))
|
||||
FindGridsIntersecting(map, worldAABB, ref state, callback, approx, includeMap);
|
||||
if (_mapSystem.TryGetMap(mapId, out var map))
|
||||
FindGridsIntersecting(map.Value, worldAABB, ref state, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, Box2 worldAABB, ref List<Entity<MapGridComponent>> grids,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var map))
|
||||
FindGridsIntersecting(map, worldAABB, ref grids, approx, includeMap);
|
||||
if (_mapSystem.TryGetMap(mapId, out var map))
|
||||
FindGridsIntersecting(map.Value, worldAABB, ref grids, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, GridCallback callback, bool approx = IMapManager.Approximate,
|
||||
bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, worldBounds, callback, approx, includeMap);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt.Value, worldBounds, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(MapId mapId, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, worldBounds, ref state, callback, approx, includeMap);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt.Value, worldBounds, ref state, callback, approx, includeMap);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(MapId mapId, Box2Rotated worldBounds, ref List<Entity<MapGridComponent>> grids,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt, worldBounds, ref grids, approx, includeMap);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
FindGridsIntersecting(mapEnt.Value, worldBounds, ref grids, approx, includeMap);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -338,8 +338,8 @@ internal partial class MapManager
|
||||
/// </summary>
|
||||
public bool TryFindGridAt(MapId mapId, Vector2 worldPos, out EntityUid uid, [NotNullWhen(true)] out MapGridComponent? grid)
|
||||
{
|
||||
if (_mapEntities.TryGetValue(mapId, out var map))
|
||||
return TryFindGridAt(map, worldPos, out uid, out grid);
|
||||
if (_mapSystem.TryGetMap(mapId, out var map))
|
||||
return TryFindGridAt(map.Value, worldPos, out uid, out grid);
|
||||
|
||||
uid = default;
|
||||
grid = null;
|
||||
|
||||
@@ -11,10 +11,10 @@ namespace Robust.Shared.Map;
|
||||
|
||||
/// <inheritdoc cref="IMapManager" />
|
||||
[Virtual]
|
||||
internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber, IPostInjectInit
|
||||
internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
|
||||
{
|
||||
[field: Dependency] public IGameTiming GameTiming { get; } = default!;
|
||||
[field: Dependency] public IEntityManager EntityManager { get; } = default!;
|
||||
[Dependency] public readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] public readonly IEntityManager EntityManager = default!;
|
||||
[Dependency] private readonly IManifoldManager _manifolds = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _conhost = default!;
|
||||
@@ -34,6 +34,7 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber,
|
||||
_gridTreeQuery = EntityManager.GetEntityQuery<GridTreeComponent>();
|
||||
_gridQuery = EntityManager.GetEntityQuery<MapGridComponent>();
|
||||
InitializeMapPausing();
|
||||
_sawmill = _logManager.GetSawmill("system.map");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -74,9 +75,4 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber,
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("system.map");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
internal sealed class MapSerializationContext : ISerializationContext, IEntityLoadContext,
|
||||
ITypeSerializer<EntityUid, ValueDataNode>
|
||||
{
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
|
||||
// Run-specific data
|
||||
public Dictionary<int, string>? TileMap;
|
||||
public readonly Dictionary<string, IComponent> CurrentReadingEntityComponents = new();
|
||||
public HashSet<string> CurrentlyIgnoredComponents = new();
|
||||
public string? CurrentComponent;
|
||||
public EntityUid? CurrentWritingEntity;
|
||||
public IEntityManager EntityManager;
|
||||
public IGameTiming Timing;
|
||||
|
||||
private Dictionary<int, EntityUid> _uidEntityMap = new();
|
||||
private Dictionary<EntityUid, int> _entityUidMap = new();
|
||||
|
||||
// Native tile ID -> map tile ID map for writing maps.
|
||||
public Dictionary<int, int> TileWriteMap = [];
|
||||
|
||||
/// <summary>
|
||||
/// Are we currently iterating prototypes or entities for writing.
|
||||
/// </summary>
|
||||
public bool WritingReadingPrototypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the map has been MapInitialized or not.
|
||||
/// </summary>
|
||||
public bool MapInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// How long the target map has been paused. Used for time offsets.
|
||||
/// </summary>
|
||||
public TimeSpan PauseTime;
|
||||
|
||||
/// <summary>
|
||||
/// The parent of the entity being saved, This entity is not itself getting saved.
|
||||
/// </summary>
|
||||
private EntityUid? _parentUid;
|
||||
|
||||
public MapSerializationContext(IEntityManager entityManager, IGameTiming timing)
|
||||
{
|
||||
EntityManager = entityManager;
|
||||
Timing = timing;
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
}
|
||||
|
||||
public void Set(
|
||||
Dictionary<int, EntityUid> uidEntityMap,
|
||||
Dictionary<EntityUid, int> entityUidMap,
|
||||
bool mapPreInit,
|
||||
TimeSpan pauseTime,
|
||||
EntityUid? parentUid)
|
||||
{
|
||||
_uidEntityMap = uidEntityMap;
|
||||
_entityUidMap = entityUidMap;
|
||||
MapInitialized = mapPreInit;
|
||||
PauseTime = pauseTime;
|
||||
if (parentUid != null && parentUid.Value.IsValid())
|
||||
_parentUid = parentUid;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
CurrentReadingEntityComponents.Clear();
|
||||
CurrentlyIgnoredComponents.Clear();
|
||||
CurrentComponent = null;
|
||||
CurrentWritingEntity = null;
|
||||
PauseTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
// Create custom object serializers that will correctly allow data to be overriden by the map file.
|
||||
bool IEntityLoadContext.TryGetComponent(string componentName, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
return CurrentReadingEntityComponents.TryGetValue(componentName, out component);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetExtraComponentTypes()
|
||||
{
|
||||
return CurrentReadingEntityComponents!.Keys;
|
||||
}
|
||||
|
||||
public bool ShouldSkipComponent(string compName)
|
||||
{
|
||||
return CurrentlyIgnoredComponents.Contains(compName);
|
||||
}
|
||||
|
||||
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
|
||||
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
|
||||
{
|
||||
if (node.Value == "null")
|
||||
{
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
|
||||
if (!int.TryParse(node.Value, out var val) || !_uidEntityMap.ContainsKey(val))
|
||||
{
|
||||
return new ErrorNode(node, "Invalid EntityUid", true);
|
||||
}
|
||||
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, EntityUid value,
|
||||
IDependencyCollection dependencies, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (!_entityUidMap.TryGetValue(value, out var entityUidMapped))
|
||||
{
|
||||
if (CurrentComponent == "Transform")
|
||||
{
|
||||
if (!value.IsValid() || value == _parentUid)
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
dependencies
|
||||
.Resolve<ILogManager>()
|
||||
.GetSawmill("map")
|
||||
.Error("Encountered an invalid entityUid '{0}' while serializing a map.", value);
|
||||
|
||||
return new ValueDataNode("invalid");
|
||||
}
|
||||
|
||||
return new ValueDataNode(entityUidMapped.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
EntityUid ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context, ISerializationManager.InstantiationDelegate<EntityUid>? _)
|
||||
{
|
||||
if (node.Value == "invalid" && CurrentComponent == "Transform")
|
||||
return EntityUid.Invalid;
|
||||
|
||||
if (int.TryParse(node.Value, out var val) && _uidEntityMap.TryGetValue(val, out var entity))
|
||||
return entity;
|
||||
|
||||
dependencies
|
||||
.Resolve<ILogManager>()
|
||||
.GetSawmill("map")
|
||||
.Error("Error in map file: found local entity UID '{0}' which does not exist.", val);
|
||||
|
||||
return EntityUid.Invalid;
|
||||
|
||||
}
|
||||
|
||||
[MustUseReturnValue]
|
||||
public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target,
|
||||
bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return new((int)source);
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,6 @@ namespace Robust.Shared.Map
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<TileAliasPrototype>())
|
||||
{
|
||||
AssignAlias(prototype.ID, prototype.Target);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Register(ITileDefinition tileDef)
|
||||
@@ -45,46 +41,8 @@ namespace Robust.Shared.Map
|
||||
tileDef.AssignTileId(id);
|
||||
TileDefs.Add(tileDef);
|
||||
_tileNames[name] = tileDef;
|
||||
|
||||
AliasingHandleDeferred(name);
|
||||
}
|
||||
|
||||
private void AliasingHandleDeferred(string name)
|
||||
{
|
||||
// Aliases may have been held back due to tiles not being registered yet, handle this.
|
||||
if (_awaitingAliases.ContainsKey(name))
|
||||
{
|
||||
var list = _awaitingAliases[name];
|
||||
_awaitingAliases.Remove(name);
|
||||
foreach (var alias in list)
|
||||
{
|
||||
AssignAlias(alias, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void AssignAlias(string src, string dst)
|
||||
{
|
||||
if (_tileNames.ContainsKey(src))
|
||||
{
|
||||
throw new ArgumentException("Another tile definition or alias with the same name has already been registered.", nameof(src));
|
||||
}
|
||||
|
||||
if (_tileNames.ContainsKey(dst))
|
||||
{
|
||||
// Simple enough, source to destination.
|
||||
_tileNames[src] = _tileNames[dst];
|
||||
AliasingHandleDeferred(src);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Less simple - stash this alias for later so it appears when the target does.
|
||||
if (!_awaitingAliases.ContainsKey(dst))
|
||||
_awaitingAliases[dst] = new();
|
||||
_awaitingAliases[dst].Add(src);
|
||||
}
|
||||
}
|
||||
|
||||
public Tile GetVariantTile(string name, IRobustRandom random)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -78,13 +79,11 @@ public sealed class FixtureSerializer : ITypeSerializer<Dictionary<string, Fixtu
|
||||
if (value.Count == 0)
|
||||
return seq;
|
||||
|
||||
if (context is MapSerializationContext mapContext)
|
||||
if (context is EntitySerializer ctx)
|
||||
{
|
||||
// Don't serialize mapgrid fixtures because it's bloat and we'll just generate them at runtime.
|
||||
if (dependencies.Resolve<IEntityManager>().HasComponent<MapGridComponent>(mapContext.CurrentWritingEntity))
|
||||
{
|
||||
if (ctx.EntMan.HasComponent<MapGridComponent>(ctx.CurrentEntity))
|
||||
return seq;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (id, fixture) in value)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
@@ -268,7 +269,7 @@ namespace Robust.Shared.Prototypes
|
||||
component = newComponent;
|
||||
}
|
||||
|
||||
if (context is not MapSerializationContext map)
|
||||
if (context is not EntityDeserializer map)
|
||||
{
|
||||
serManager.CopyTo(data, ref component, context, notNullableOverride: true);
|
||||
return;
|
||||
|
||||
@@ -4,8 +4,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
/// <summary>
|
||||
/// Prototype that represents an alias from one tile ID to another.
|
||||
/// Tile alias prototypes, unlike tile prototypes, are implemented here, as they're really just fed to TileDefinitionManager.
|
||||
/// Prototype that represents an alias from one tile ID to another. These are used when deserializing entities from yaml.
|
||||
/// </summary>
|
||||
[Prototype("tileAlias")]
|
||||
public sealed partial class TileAliasPrototype : IPrototype
|
||||
@@ -13,13 +12,13 @@ public sealed partial class TileAliasPrototype : IPrototype
|
||||
/// <summary>
|
||||
/// The target tile ID to alias to.
|
||||
/// </summary>
|
||||
[DataField("target")]
|
||||
[DataField]
|
||||
public string Target { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The source tile ID (and the ID of this tile alias).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[IdDataFieldAttribute]
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
internal sealed class YamlValidationContext : ISerializationContext, ITypeSerializer<EntityUid, ValueDataNode>
|
||||
internal sealed class YamlValidationContext :
|
||||
ISerializationContext,
|
||||
ITypeSerializer<EntityUid, ValueDataNode>,
|
||||
ITypeSerializer<NetEntity, ValueDataNode>
|
||||
{
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
public bool WritingReadingPrototypes => true;
|
||||
@@ -24,7 +27,7 @@ internal sealed class YamlValidationContext : ISerializationContext, ITypeSerial
|
||||
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
|
||||
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
|
||||
{
|
||||
if (node.Value == "null" || node.Value == "invalid")
|
||||
if (node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
return new ErrorNode(node, "Prototypes should not contain EntityUids", true);
|
||||
@@ -52,11 +55,42 @@ internal sealed class YamlValidationContext : ISerializationContext, ITypeSerial
|
||||
return EntityUid.Parse(node.Value);
|
||||
}
|
||||
|
||||
[MustUseReturnValue]
|
||||
public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target,
|
||||
bool skipHook,
|
||||
public ValidationNode Validate(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return new((int)source);
|
||||
if (node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
return new ErrorNode(node, "Prototypes should not contain NetEntities");
|
||||
}
|
||||
|
||||
public NetEntity Read(
|
||||
ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<NetEntity>? instanceProvider = null)
|
||||
{
|
||||
if (node.Value == "invalid")
|
||||
return NetEntity.Invalid;
|
||||
|
||||
return NetEntity.Parse(node.Value);
|
||||
}
|
||||
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
NetEntity value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (!value.Valid)
|
||||
return new ValueDataNode("invalid");
|
||||
|
||||
return new ValueDataNode(value.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
@@ -440,6 +441,12 @@ namespace Robust.Shared.Serialization.Manager
|
||||
return (TNode) PushComposition(type, parents, child, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple <see cref="MappingDataNode"/> inheritance pusher clones data and overrides a parent's values with
|
||||
/// the child's.
|
||||
/// </summary>
|
||||
MappingDataNode CombineMappings(MappingDataNode child, MappingDataNode parent);
|
||||
|
||||
#endregion
|
||||
|
||||
public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType);
|
||||
|
||||
@@ -273,6 +273,26 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
|
||||
return newMapping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="Copy"/> that doesn't clone the keys or values.
|
||||
/// </summary>
|
||||
public MappingDataNode ShallowClone()
|
||||
{
|
||||
var newMapping = new MappingDataNode(_children.Count)
|
||||
{
|
||||
Tag = Tag,
|
||||
Start = Start,
|
||||
End = End
|
||||
};
|
||||
|
||||
foreach (var (key, val) in _list)
|
||||
{
|
||||
newMapping.Add(key, val);
|
||||
}
|
||||
|
||||
return newMapping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="Except(MappingDataNode)"/> that will recursively call except rather than only checking equality.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
@@ -13,8 +13,8 @@ using Robust.Shared.Utility;
|
||||
namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
/// <summary>
|
||||
/// This serializer offsets a timespan by the game's current time. If the entity is currently paused, the pause time
|
||||
/// will also be accounted for,
|
||||
/// This serializer offsets a timespan by the game's current time. If the entity is currently paused, the the offset will
|
||||
/// instead be the time at which the entity was paused.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Prototypes and pre map-init entities will always serialize this as zero. This is done mainly as a brute force fix
|
||||
@@ -29,14 +29,13 @@ public sealed class TimeOffsetSerializer : ITypeSerializer<TimeSpan, ValueDataNo
|
||||
ISerializationContext? context = null,
|
||||
ISerializationManager.InstantiationDelegate<TimeSpan>? instanceProvider = null)
|
||||
{
|
||||
if (context is not MapSerializationContext mapContext
|
||||
|| mapContext.WritingReadingPrototypes
|
||||
|| !mapContext.MapInitialized)
|
||||
{
|
||||
if (context is {WritingReadingPrototypes: true})
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
var timing = mapContext.Timing;
|
||||
if (context is not EntityDeserializer {CurrentReadingEntity.PostInit: true} ctx)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
var timing = ctx.Timing;
|
||||
var seconds = double.Parse(node.Value, CultureInfo.InvariantCulture);
|
||||
return TimeSpan.FromSeconds(seconds) + timing.CurTime;
|
||||
}
|
||||
@@ -50,34 +49,29 @@ public sealed class TimeOffsetSerializer : ITypeSerializer<TimeSpan, ValueDataNo
|
||||
: new ErrorNode(node, "Failed parsing TimeSpan");
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, TimeSpan value, IDependencyCollection dependencies, bool alwaysWrite = false,
|
||||
public DataNode Write(
|
||||
ISerializationManager serializationManager,
|
||||
TimeSpan value,
|
||||
IDependencyCollection dependencies,
|
||||
bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (context is not MapSerializationContext mapContext
|
||||
|| mapContext.WritingReadingPrototypes
|
||||
|| !mapContext.MapInitialized)
|
||||
if (context is not EntitySerializer serializer
|
||||
|| serializer.WritingReadingPrototypes
|
||||
|| !serializer.EntMan.TryGetComponent(serializer.CurrentEntity, out MetaDataComponent? meta)
|
||||
|| meta.EntityLifeStage < EntityLifeStage.MapInitialized)
|
||||
{
|
||||
DebugTools.Assert(value == TimeSpan.Zero || context?.WritingReadingPrototypes != true,
|
||||
"non-zero time offsets in prototypes are not supported. If required, initialize offsets on map-init");
|
||||
|
||||
return new ValueDataNode("0");
|
||||
}
|
||||
|
||||
if (!mapContext.MapInitialized)
|
||||
return new ValueDataNode("0");
|
||||
|
||||
if (mapContext.EntityManager.TryGetComponent(mapContext.CurrentWritingEntity, out MetaDataComponent? meta))
|
||||
{
|
||||
// Here, PauseTime is a time -- not a duration.
|
||||
if (meta.PauseTime != null)
|
||||
value -= meta.PauseTime.Value;
|
||||
}
|
||||
// We subtract the current time, unless the entity is paused, in which case we subtract the time at which
|
||||
// it was paused.
|
||||
if (meta.PauseTime != null)
|
||||
value -= meta.PauseTime.Value;
|
||||
else
|
||||
{
|
||||
// But here, PauseTime is a duration instead of a time
|
||||
// What jolly fun.
|
||||
value = value - mapContext.Timing.CurTime + mapContext.PauseTime;
|
||||
}
|
||||
value -= serializer.Timing.CurTime;
|
||||
|
||||
return new ValueDataNode(value.TotalSeconds.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -157,7 +159,6 @@ namespace Robust.UnitTesting
|
||||
systems.LoadExtraSystemType<DebugRayDrawingSystem>();
|
||||
systems.LoadExtraSystemType<PrototypeReloadSystem>();
|
||||
systems.LoadExtraSystemType<DebugPhysicsSystem>();
|
||||
systems.LoadExtraSystemType<MapLoaderSystem>();
|
||||
systems.LoadExtraSystemType<InputSystem>();
|
||||
systems.LoadExtraSystemType<PvsOverrideSystem>();
|
||||
systems.LoadExtraSystemType<MapSystem>();
|
||||
@@ -179,12 +180,10 @@ namespace Robust.UnitTesting
|
||||
if (ExtraComponents != null)
|
||||
compFactory.RegisterTypes(ExtraComponents);
|
||||
|
||||
if (Project == UnitTestProject.Server)
|
||||
{
|
||||
compFactory.RegisterClass<MapSaveTileMapComponent>();
|
||||
compFactory.RegisterClass<MapSaveIdComponent>();
|
||||
}
|
||||
else
|
||||
compFactory.RegisterClass<MapSaveTileMapComponent>();
|
||||
compFactory.RegisterClass<YamlUidComponent>();
|
||||
|
||||
if (Project != UnitTestProject.Server)
|
||||
{
|
||||
compFactory.RegisterClass<PointLightComponent>();
|
||||
compFactory.RegisterClass<SpriteComponent>();
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
public override UnitTestProject Project => UnitTestProject.Server;
|
||||
|
||||
private IServerEntityManagerInternal EntityManager = default!;
|
||||
private IEntityManager EntityManager = default!;
|
||||
private IMapManager MapManager = default!;
|
||||
private SharedTransformSystem XformSystem => EntityManager.System<SharedTransformSystem>();
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
|
||||
{
|
||||
IoCManager.Resolve<IComponentFactory>().GenerateNetIds();
|
||||
|
||||
EntityManager = IoCManager.Resolve<IServerEntityManagerInternal>();
|
||||
EntityManager = IoCManager.Resolve<IEntityManager>();
|
||||
MapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
|
||||
@@ -1,51 +1,59 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using IgnoreUIRangeComponent = Robust.Shared.GameObjects.IgnoreUIRangeComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Server.Maps
|
||||
{
|
||||
[TestFixture]
|
||||
public sealed partial class MapLoaderTest : RobustUnitTest
|
||||
public sealed partial class MapLoaderTest : RobustIntegrationTest
|
||||
{
|
||||
private const string MapData = @"
|
||||
meta:
|
||||
format: 2
|
||||
name: DemoStation
|
||||
author: Space-Wizards
|
||||
postmapinit: false
|
||||
format: 7
|
||||
category: Grid
|
||||
engineVersion: 238.0.0
|
||||
forkId: """"
|
||||
forkVersion: """"
|
||||
time: 12/22/2024 04:08:12
|
||||
entityCount: 3
|
||||
maps: []
|
||||
grids:
|
||||
- settings:
|
||||
chunksize: 16
|
||||
tilesize: 1
|
||||
snapsize: 1
|
||||
chunks: []
|
||||
- 1
|
||||
orphans:
|
||||
- 1
|
||||
nullspace: []
|
||||
tilemap: {}
|
||||
entities:
|
||||
- uid: 0
|
||||
components:
|
||||
- parent: null
|
||||
type: Transform
|
||||
- index: 0
|
||||
type: MapGrid
|
||||
- uid: 1
|
||||
type: MapDeserializeTest
|
||||
components:
|
||||
- type: MapDeserializeTest
|
||||
foo: 3
|
||||
- parent: 0
|
||||
type: Transform
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 1
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: MapGrid
|
||||
chunks: {}
|
||||
- type: Broadphase
|
||||
- type: Physics
|
||||
canCollide: False
|
||||
- type: Fixtures
|
||||
fixtures: {}
|
||||
- type: MapSaveTileMap
|
||||
- proto: MapDeserializeTest
|
||||
entities:
|
||||
- uid: 2
|
||||
mapInit: true
|
||||
components:
|
||||
- type: Transform
|
||||
parent: 1
|
||||
- type: MapDeserializeTest
|
||||
foo: 3
|
||||
";
|
||||
|
||||
private const string Prototype = @"
|
||||
@@ -57,46 +65,35 @@ entities:
|
||||
bar: 2
|
||||
";
|
||||
|
||||
protected override Type[]? ExtraComponents => new[] { typeof(MapDeserializeTestComponent), typeof(VisibilityComponent), typeof(IgnoreUIRangeComponent)};
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
var resourceManager = IoCManager.Resolve<IResourceManagerInternal>();
|
||||
resourceManager.Initialize(null);
|
||||
resourceManager.MountString("/TestMap.yml", MapData);
|
||||
resourceManager.MountString("/EnginePrototypes/TestMapEntity.yml", Prototype);
|
||||
|
||||
var protoMan = IoCManager.Resolve<IPrototypeManager>();
|
||||
protoMan.RegisterKind(typeof(EntityPrototype), typeof(EntityCategoryPrototype));
|
||||
|
||||
protoMan.LoadDirectory(new ("/EnginePrototypes"));
|
||||
protoMan.LoadDirectory(new ("/Prototypes"));
|
||||
protoMan.ResolveResults();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDataLoadPriority()
|
||||
public async Task TestDataLoadPriority()
|
||||
{
|
||||
// TODO: Fix after serv3
|
||||
// fix what?
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
entMan.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
|
||||
var traversal = entMan.System<SharedGridTraversalSystem>();
|
||||
traversal.Enabled = false;
|
||||
var mapLoad = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
|
||||
if (!mapLoad.TryLoad(mapId, "/TestMap.yml", out var root)
|
||||
|| root.FirstOrDefault() is not { Valid:true } geid)
|
||||
var opts = new ServerIntegrationOptions()
|
||||
{
|
||||
Assert.Fail();
|
||||
return;
|
||||
}
|
||||
ExtraPrototypes = Prototype
|
||||
};
|
||||
|
||||
var entity = entMan.GetComponent<TransformComponent>(geid)._children.Single();
|
||||
var c = entMan.GetComponent<MapDeserializeTestComponent>(entity);
|
||||
var server = StartServer(opts);
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var resourceManager = server.ResolveDependency<IResourceManagerInternal>();
|
||||
resourceManager.MountString("/TestMap.yml", MapData);
|
||||
|
||||
var traversal = server.System<SharedGridTraversalSystem>();
|
||||
traversal.Enabled = false;
|
||||
var mapLoad = server.System<MapLoaderSystem>();
|
||||
|
||||
Entity<MapGridComponent>? grid = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
server.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
Assert.That(mapLoad.TryLoadGrid(mapId, new ResPath("/TestMap.yml"), out grid));
|
||||
});
|
||||
|
||||
var geid = grid!.Value.Owner;
|
||||
|
||||
var entity = server.EntMan.GetComponent<TransformComponent>(geid)._children.Single();
|
||||
var c = server.EntMan.GetComponent<MapDeserializeTestComponent>(entity);
|
||||
traversal.Enabled = true;
|
||||
|
||||
Assert.That(c.Bar, Is.EqualTo(2));
|
||||
@@ -104,7 +101,7 @@ entities:
|
||||
Assert.That(c.Baz, Is.EqualTo(-1));
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
[RegisterComponent]
|
||||
private sealed partial class MapDeserializeTestComponent : Component
|
||||
{
|
||||
[DataField("foo")] public int Foo { get; set; } = -1;
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class AlwaysPushSerializationTest : RobustIntegrationTest
|
||||
{
|
||||
private const string Prototype = @"
|
||||
- type: entity
|
||||
id: TestEntityCompositionParent
|
||||
components:
|
||||
- type: EntitySaveTest
|
||||
list: [ 1, 2 ]
|
||||
|
||||
- type: entity
|
||||
id: TestEntityCompositionChild
|
||||
parent: TestEntityCompositionParent
|
||||
components:
|
||||
- type: EntitySaveTest
|
||||
list: [ 3 , 4 ]
|
||||
";
|
||||
|
||||
/// <summary>
|
||||
/// This test checks that deserializing an entity with some component that has the
|
||||
/// <see cref="AlwaysPushInheritanceAttribute"/> works as intended. Previously the attribute would cause the entity
|
||||
/// prototype to **always** append it's contents to the loaded entity, effectively causing
|
||||
/// the <see cref="TestAlwaysPushComponent.List"/> data-field to grow each time a map was loaded and saved.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[TestOf(typeof(AlwaysPushInheritanceAttribute))]
|
||||
public async Task TestAlwaysPushSerialization()
|
||||
{
|
||||
var opts = new ServerIntegrationOptions
|
||||
{
|
||||
ExtraPrototypes = Prototype
|
||||
};
|
||||
|
||||
var server = StartServer(opts);
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
|
||||
// Create a new map and spawn in some entities.
|
||||
MapId mapId = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> parent1 = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> parent2 = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> parent3 = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> child1 = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> child2 = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> child3 = default;
|
||||
|
||||
var path = new ResPath($"{nameof(TestAlwaysPushSerialization)}.yml");
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
server.System<SharedMapSystem>().CreateMap(out mapId);
|
||||
var coords = new MapCoordinates(0, 0, mapId);
|
||||
var parent1Uid = entMan.Spawn("TestEntityCompositionParent", coords);
|
||||
var parent2Uid = entMan.Spawn("TestEntityCompositionParent", coords);
|
||||
var parent3Uid = entMan.Spawn("TestEntityCompositionParent", coords);
|
||||
var child1Uid = entMan.Spawn("TestEntityCompositionChild", coords);
|
||||
var child2Uid = entMan.Spawn("TestEntityCompositionChild", coords);
|
||||
var child3Uid = entMan.Spawn("TestEntityCompositionChild", coords);
|
||||
|
||||
parent1 = Get(parent1Uid, entMan);
|
||||
parent2 = Get(parent2Uid, entMan);
|
||||
parent3 = Get(parent3Uid, entMan);
|
||||
child1 = Get(child1Uid, entMan);
|
||||
child2 = Get(child2Uid, entMan);
|
||||
child3 = Get(child3Uid, entMan);
|
||||
});
|
||||
|
||||
// Assign a unique id to each entity (so they can be identified after saving & loading a map)
|
||||
parent1.Comp2!.Id = nameof(parent1);
|
||||
parent2.Comp2!.Id = nameof(parent2);
|
||||
parent3.Comp2!.Id = nameof(parent3);
|
||||
child1.Comp2!.Id = nameof(child1);
|
||||
child2.Comp2!.Id = nameof(child2);
|
||||
child3.Comp2!.Id = nameof(child3);
|
||||
|
||||
// The inheritance pushing for the prototypes should ensure that the parent & child prototype's lists were merged.
|
||||
Assert.That(parent1.Comp2.List.SequenceEqual(new[] {1, 2}));
|
||||
Assert.That(parent2.Comp2.List.SequenceEqual(new[] {1, 2}));
|
||||
Assert.That(parent3.Comp2.List.SequenceEqual(new[] {1, 2}));
|
||||
Assert.That(child1.Comp2.List.SequenceEqual(new[] {3, 4, 1, 2}));
|
||||
Assert.That(child2.Comp2.List.SequenceEqual(new[] {3, 4, 1, 2}));
|
||||
Assert.That(child3.Comp2.List.SequenceEqual(new[] {3, 4, 1, 2}));
|
||||
|
||||
// Modify data on some components.
|
||||
parent2.Comp2.List.Add(-1);
|
||||
child2.Comp2.List.Add(-1);
|
||||
parent3.Comp2.List.RemoveAt(1);
|
||||
child3.Comp2.List.RemoveAt(1);
|
||||
|
||||
Assert.That(parent1.Comp2.List.SequenceEqual(new[] {1, 2}));
|
||||
Assert.That(parent2.Comp2.List.SequenceEqual(new[] {1, 2, -1}));
|
||||
Assert.That(parent3.Comp2.List.SequenceEqual(new[] {1}));
|
||||
Assert.That(child1.Comp2.List.SequenceEqual(new[] {3, 4, 1, 2}));
|
||||
Assert.That(child2.Comp2.List.SequenceEqual(new[] {3, 4, 1, 2, -1}));
|
||||
Assert.That(child3.Comp2.List.SequenceEqual(new[] {3, 1, 2}));
|
||||
|
||||
// Save map to yaml
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var map = server.System<SharedMapSystem>();
|
||||
Assert.That(loader.TrySaveMap(mapId, path));
|
||||
|
||||
// Delete the entities
|
||||
await server.WaitPost(() => map.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load the map
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
Assert.That(loader.TryLoadMap(path, out var ent, out _));
|
||||
mapId = ent!.Value.Comp.MapId;
|
||||
});
|
||||
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(6));
|
||||
|
||||
// Find the deserialized entities
|
||||
parent1 = Find(nameof(parent1), entMan);
|
||||
parent2 = Find(nameof(parent2), entMan);
|
||||
parent3 = Find(nameof(parent3), entMan);
|
||||
child1 = Find(nameof(child1), entMan);
|
||||
child2 = Find(nameof(child2), entMan);
|
||||
child3 = Find(nameof(child3), entMan);
|
||||
|
||||
// Verify that the entity data has not changed.
|
||||
Assert.That(parent1.Comp2.List.SequenceEqual(new[] {1, 2}));
|
||||
Assert.That(parent2.Comp2.List.SequenceEqual(new[] {1, 2, -1}));
|
||||
Assert.That(parent3.Comp2.List.SequenceEqual(new[] {1}));
|
||||
Assert.That(child1.Comp2.List.SequenceEqual(new[] {3, 4, 1, 2}));
|
||||
Assert.That(child2.Comp2.List.SequenceEqual(new[] {3, 4, 1, 2, -1}));
|
||||
Assert.That(child3.Comp2.List.SequenceEqual(new[] {3, 1, 2}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
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;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class AutoIncludeSerializationTest : RobustIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestAutoIncludeSerialization()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var mapPath = new ResPath($"{nameof(AutoIncludeSerializationTest)}_map.yml");
|
||||
var gridPath = new ResPath($"{nameof(AutoIncludeSerializationTest)}_grid.yml");
|
||||
|
||||
tileMan.Register(new TileDef("space"));
|
||||
var tDef = new TileDef("a");
|
||||
tileMan.Register(tDef);
|
||||
|
||||
// Create a map that contains an entity that references a nullspace entity.
|
||||
MapId mapId = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> grid = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> onGrid = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> offGrid = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullSpace = default;
|
||||
|
||||
void AssertCount(int expected) => Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(expected));
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapUid = mapSys.CreateMap(out mapId);
|
||||
var gridUid = mapMan.CreateGridEntity(mapId);
|
||||
mapSys.SetTile(gridUid, Vector2i.Zero, new Tile(tDef.TileId));
|
||||
|
||||
var onGridUid = entMan.SpawnEntity(null, new EntityCoordinates(gridUid, 0.5f, 0.5f));
|
||||
var offGridUid = entMan.SpawnEntity(null, new MapCoordinates(10f, 10f, mapId));
|
||||
var nullSpaceUid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
|
||||
map = Get(mapUid, entMan);
|
||||
grid = Get(gridUid, entMan);
|
||||
onGrid = Get(onGridUid, entMan);
|
||||
offGrid = Get(offGridUid, entMan);
|
||||
nullSpace = Get(nullSpaceUid, entMan);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
Assert.That(map.Comp1!.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(grid.Comp1!.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(onGrid.Comp1!.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(offGrid.Comp1!.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(nullSpace.Comp1!.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
// Assign unique ids.
|
||||
map.Comp2!.Id = nameof(map);
|
||||
grid.Comp2!.Id = nameof(grid);
|
||||
onGrid.Comp2!.Id = nameof(onGrid);
|
||||
offGrid.Comp2!.Id = nameof(offGrid);
|
||||
nullSpace.Comp2!.Id = nameof(nullSpace);
|
||||
|
||||
// First simple map loading without any references to other entities.
|
||||
// This will cause the null-space entity to be lost.
|
||||
// Save the map, then delete all the entities.
|
||||
AssertCount(5);
|
||||
Assert.That(loader.TrySaveMap(mapId, mapPath));
|
||||
Assert.That(loader.TrySaveGrid(grid, gridPath));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
AssertCount(1);
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
AssertCount(0);
|
||||
|
||||
// Load up the file that only saved the grid and check that the expected entities exist.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId));
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadGrid(mapId, gridPath, out _)));
|
||||
|
||||
AssertCount(2);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
AssertCount(0);
|
||||
|
||||
// Load up the map, and check that the expected entities exist.
|
||||
Entity<MapComponent>? loadedMap = default;
|
||||
HashSet<Entity<MapGridComponent>>? loadedGrids = default!;
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadMap(mapPath, out loadedMap, out loadedGrids)));
|
||||
mapId = loadedMap!.Value.Comp.MapId;
|
||||
Assert.That(loadedGrids, Has.Count.EqualTo(1));
|
||||
|
||||
AssertCount(4);
|
||||
map = Find(nameof(map), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
offGrid = Find(nameof(offGrid), entMan);
|
||||
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(offGrid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
|
||||
// Re-spawn the nullspace entity
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var nullSpaceUid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
nullSpace = Get(nullSpaceUid, entMan);
|
||||
nullSpace.Comp2.Id = nameof(nullSpace);
|
||||
});
|
||||
|
||||
// Repeat the previous saves, but with an entity that references the null-space entity.
|
||||
onGrid.Comp2.Entity = nullSpace.Owner;
|
||||
|
||||
AssertCount(5);
|
||||
Assert.That(loader.TrySaveMap(mapId, mapPath));
|
||||
Assert.That(loader.TrySaveGrid(grid, gridPath));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
AssertCount(1);
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
AssertCount(0);
|
||||
|
||||
// Load up the file that only saved the grid and check that the expected entities exist.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId));
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadGrid(mapId, gridPath, out _)));
|
||||
|
||||
AssertCount(3);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
nullSpace = Find(nameof(nullSpace), entMan);
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(onGrid.Comp2.Entity, Is.EqualTo(nullSpace.Owner));
|
||||
Assert.That(nullSpace.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
AssertCount(1);
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
AssertCount(0);
|
||||
|
||||
// Load up the map, and check that the expected entities exist.
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadMap(mapPath, out loadedMap, out loadedGrids)));
|
||||
mapId = loadedMap!.Value.Comp.MapId;
|
||||
Assert.That(loadedGrids, Has.Count.EqualTo(1));
|
||||
|
||||
AssertCount(5);
|
||||
map = Find(nameof(map), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
offGrid = Find(nameof(offGrid), entMan);
|
||||
nullSpace = Find(nameof(nullSpace), entMan);
|
||||
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(offGrid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(onGrid.Comp2.Entity, Is.EqualTo(nullSpace.Owner));
|
||||
Assert.That(nullSpace.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
// Check that attempting to save a reference to a non-null-space entity does not auto-include it.
|
||||
Entity<TransformComponent, EntitySaveTestComponent> otherMap = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var otherMapUid = mapSys.CreateMap();
|
||||
otherMap = Get(otherMapUid, entMan);
|
||||
otherMap.Comp2.Id = nameof(otherMap);
|
||||
});
|
||||
onGrid.Comp2.Entity = otherMap.Owner;
|
||||
|
||||
// By default it should log an error, but tests don't have a nice way to validate that an error was logged, so we'll just suppress it.
|
||||
var opts = SerializationOptions.Default with {MissingEntityBehaviour = MissingEntityBehaviour.Ignore};
|
||||
AssertCount(6);
|
||||
Assert.That(loader.TrySaveMap(mapId, mapPath, opts));
|
||||
Assert.That(loader.TrySaveGrid(grid, gridPath, opts));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(otherMap));
|
||||
AssertCount(0);
|
||||
|
||||
// Check the grid file
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId));
|
||||
var dOpts = DeserializationOptions.Default with {LogInvalidEntities = false};
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadGrid(mapId, gridPath, out _, dOpts)));
|
||||
AssertCount(2);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
AssertCount(0);
|
||||
|
||||
// Check the map file
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadMap(mapPath, out loadedMap, out loadedGrids, dOpts)));
|
||||
mapId = loadedMap!.Value.Comp.MapId;
|
||||
Assert.That(loadedGrids, Has.Count.EqualTo(1));
|
||||
AssertCount(4);
|
||||
map = Find(nameof(map), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
offGrid = Find(nameof(offGrid), entMan);
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(offGrid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
|
||||
// repeat the check, but this time with auto inclusion fully enabled.
|
||||
Entity<TransformComponent, EntitySaveTestComponent> otherEnt = default;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var otherMapUid = mapSys.CreateMap(out var otherMapId);
|
||||
otherMap = Get(otherMapUid, entMan);
|
||||
otherMap.Comp2.Id = nameof(otherMap);
|
||||
|
||||
var otherEntUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, otherMapId));
|
||||
otherEnt = Get(otherEntUid, entMan);
|
||||
otherEnt.Comp2.Id = nameof(otherEnt);
|
||||
|
||||
var nullSpaceUid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
nullSpace = Get(nullSpaceUid, entMan);
|
||||
nullSpace.Comp2.Id = nameof(nullSpace);
|
||||
});
|
||||
|
||||
onGrid.Comp2.Entity = otherMap.Owner;
|
||||
otherEnt.Comp2!.Entity = nullSpace;
|
||||
|
||||
AssertCount(7);
|
||||
opts = opts with {MissingEntityBehaviour = MissingEntityBehaviour.AutoInclude};
|
||||
Assert.That(loader.TrySaveGeneric(map.Owner, mapPath, out var cat, opts));
|
||||
Assert.That(cat, Is.EqualTo(FileCategory.Unknown));
|
||||
Assert.That(loader.TrySaveGeneric(grid.Owner, gridPath, out cat, opts));
|
||||
Assert.That(cat, Is.EqualTo(FileCategory.Unknown));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(otherMap));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
AssertCount(0);
|
||||
|
||||
// Check the grid file
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId));
|
||||
var mapLoadOpts = MapLoadOptions.Default with
|
||||
{
|
||||
DeserializationOptions = DeserializationOptions.Default with {LogOrphanedGrids = false}
|
||||
};
|
||||
LoadResult? result = default;
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadGeneric(gridPath, out result, mapLoadOpts)));
|
||||
Assert.That(result!.Grids, Has.Count.EqualTo(1));
|
||||
Assert.That(result.Orphans, Is.Empty); // Grid was orphaned, but was adopted after a new map was created
|
||||
Assert.That(result.Maps, Has.Count.EqualTo(2));
|
||||
Assert.That(result.NullspaceEntities, Has.Count.EqualTo(1));
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1)); // auto-generated map isn't marked as "loaded"
|
||||
AssertCount(5);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
otherMap = Find(nameof(otherMap), entMan);
|
||||
otherEnt = Find(nameof(otherEnt), entMan);
|
||||
nullSpace = Find(nameof(nullSpace), entMan);
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(otherEnt.Comp1.ParentUid, Is.EqualTo(otherMap.Owner));
|
||||
Assert.That(otherMap.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(nullSpace.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(otherMap));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(grid.Comp1.ParentUid));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
AssertCount(0);
|
||||
|
||||
// Check the map file
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadGeneric(mapPath, out result)));
|
||||
Assert.That(result.Orphans, Is.Empty);
|
||||
Assert.That(result.NullspaceEntities, Has.Count.EqualTo(1));
|
||||
Assert.That(result.Grids, Has.Count.EqualTo(1));
|
||||
Assert.That(result.Maps, Has.Count.EqualTo(2));
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(2));
|
||||
AssertCount(7);
|
||||
map = Find(nameof(map), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
offGrid = Find(nameof(offGrid), entMan);
|
||||
otherMap = Find(nameof(otherMap), entMan);
|
||||
otherEnt = Find(nameof(otherEnt), entMan);
|
||||
nullSpace = Find(nameof(nullSpace), entMan);
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(offGrid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(otherEnt.Comp1.ParentUid, Is.EqualTo(otherMap.Owner));
|
||||
Assert.That(otherMap.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(nullSpace.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(otherMap));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
AssertCount(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class BackwardsCompatibilityTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that v3 maps can be loaded. This simply tries to load a file and doesn't do a lot of extra validation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The file was pilfered from content integration tests ("floor3x3.yml") and modified slightly.
|
||||
/// See also the comments around <see cref="EntityDeserializer.OldestSupportedVersion"/> that point out that v3
|
||||
/// isn't even really loadable anymore.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task TestLoadV3()
|
||||
{
|
||||
var server = StartServer(new ServerIntegrationOptions {ExtraPrototypes = PrototypeV3});
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var meta = server.System<MetaDataSystem>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var resourceManager = server.ResolveDependency<IResourceManagerInternal>();
|
||||
|
||||
tileMan.Register(new TileDef("Space"));
|
||||
for (var i = 1; i <= 88; i++)
|
||||
{
|
||||
tileMan.Register(new TileDef(i.ToString()));
|
||||
}
|
||||
var gridPath = new ResPath($"{nameof(MapDataV3Grid)}.yml");
|
||||
resourceManager.MountString(gridPath.ToString(), MapDataV3Grid);
|
||||
|
||||
MapId mapId = default;
|
||||
EntityUid mapUid = default;
|
||||
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> ent;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> grid;
|
||||
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
await server.WaitPost(() => mapUid = mapSys.CreateMap(out mapId));
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGrid(mapId, gridPath, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(0));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(mapUid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(mapUid), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(mapUid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(mapUid));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
var mapPath = new ResPath($"{nameof(MapDataV3Map)}.yml");
|
||||
resourceManager.MountString(mapPath.ToString(), MapDataV3Map);
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath, out _, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.True);
|
||||
Assert.That(meta.EntityPaused(grid), Is.True);
|
||||
Assert.That(meta.EntityPaused(map), Is.True);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Repeat test, but with the initialize maps option enabled.
|
||||
// Apparently mounted strings can only be read a single time.
|
||||
// So have to re-mount them.
|
||||
var mapPath2 = new ResPath($"{nameof(MapDataV3Map)}2.yml");
|
||||
resourceManager.MountString(mapPath2.ToString(), MapDataV3Map);
|
||||
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath2, out _, out _, opts)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(map), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private const string MapDataV3Grid = @"
|
||||
meta:
|
||||
format: 3
|
||||
name: DemoStation
|
||||
author: Space-Wizards
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
1: 1
|
||||
2: 2
|
||||
3: 3
|
||||
4: 4
|
||||
5: 5
|
||||
6: 6
|
||||
7: 7
|
||||
8: 8
|
||||
9: 9
|
||||
10: 10
|
||||
11: 11
|
||||
12: 12
|
||||
13: 13
|
||||
14: 14
|
||||
15: 15
|
||||
16: 16
|
||||
17: 17
|
||||
18: 18
|
||||
19: 19
|
||||
20: 20
|
||||
21: 21
|
||||
22: 22
|
||||
23: 23
|
||||
24: 24
|
||||
25: 25
|
||||
26: 26
|
||||
27: 27
|
||||
28: 28
|
||||
29: 29
|
||||
30: 30
|
||||
31: 31
|
||||
32: 32
|
||||
33: 33
|
||||
34: 34
|
||||
35: 35
|
||||
36: 36
|
||||
37: 37
|
||||
38: 38
|
||||
39: 39
|
||||
40: 40
|
||||
41: 41
|
||||
42: 42
|
||||
43: 43
|
||||
44: 44
|
||||
45: 45
|
||||
46: 46
|
||||
47: 47
|
||||
48: 48
|
||||
49: 49
|
||||
50: 50
|
||||
51: 51
|
||||
52: 52
|
||||
53: 53
|
||||
54: 54
|
||||
55: 55
|
||||
56: 56
|
||||
57: 57
|
||||
58: 58
|
||||
59: 59
|
||||
60: 60
|
||||
61: 61
|
||||
62: 62
|
||||
63: 63
|
||||
64: 64
|
||||
65: 65
|
||||
66: 66
|
||||
67: 67
|
||||
68: 68
|
||||
69: 69
|
||||
70: 70
|
||||
71: 71
|
||||
72: 72
|
||||
73: 73
|
||||
74: 74
|
||||
75: 75
|
||||
76: 76
|
||||
77: 77
|
||||
78: 78
|
||||
79: 79
|
||||
80: 80
|
||||
81: 81
|
||||
82: 82
|
||||
83: 83
|
||||
84: 84
|
||||
85: 85
|
||||
86: 86
|
||||
87: 87
|
||||
88: 88
|
||||
entities:
|
||||
- uid: 0
|
||||
components:
|
||||
- type: MetaData
|
||||
- parent: null
|
||||
type: Transform
|
||||
- chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAPgAAAA==
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
type: MapGrid
|
||||
- type: Broadphase
|
||||
- angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
type: Physics
|
||||
- fixtures: {}
|
||||
type: Fixtures
|
||||
- type: OccluderTree
|
||||
- id: grid
|
||||
type: EntitySaveTest
|
||||
- uid: 1
|
||||
type: V3TestProto
|
||||
components:
|
||||
- pos: 0.5,0.5
|
||||
parent: 0
|
||||
type: Transform
|
||||
- id: ent
|
||||
type: EntitySaveTest
|
||||
...
|
||||
";
|
||||
|
||||
private const string MapDataV3Map = @"
|
||||
meta:
|
||||
format: 3
|
||||
name: DemoStation
|
||||
author: Space-Wizards
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
1: 1
|
||||
2: 2
|
||||
3: 3
|
||||
4: 4
|
||||
5: 5
|
||||
6: 6
|
||||
7: 7
|
||||
8: 8
|
||||
9: 9
|
||||
10: 10
|
||||
11: 11
|
||||
12: 12
|
||||
13: 13
|
||||
14: 14
|
||||
15: 15
|
||||
16: 16
|
||||
17: 17
|
||||
18: 18
|
||||
19: 19
|
||||
20: 20
|
||||
21: 21
|
||||
22: 22
|
||||
23: 23
|
||||
24: 24
|
||||
25: 25
|
||||
26: 26
|
||||
27: 27
|
||||
28: 28
|
||||
29: 29
|
||||
30: 30
|
||||
31: 31
|
||||
32: 32
|
||||
33: 33
|
||||
34: 34
|
||||
35: 35
|
||||
36: 36
|
||||
37: 37
|
||||
38: 38
|
||||
39: 39
|
||||
40: 40
|
||||
41: 41
|
||||
42: 42
|
||||
43: 43
|
||||
44: 44
|
||||
45: 45
|
||||
46: 46
|
||||
47: 47
|
||||
48: 48
|
||||
49: 49
|
||||
50: 50
|
||||
51: 51
|
||||
52: 52
|
||||
53: 53
|
||||
54: 54
|
||||
55: 55
|
||||
56: 56
|
||||
57: 57
|
||||
58: 58
|
||||
59: 59
|
||||
60: 60
|
||||
61: 61
|
||||
62: 62
|
||||
63: 63
|
||||
64: 64
|
||||
65: 65
|
||||
66: 66
|
||||
67: 67
|
||||
68: 68
|
||||
69: 69
|
||||
70: 70
|
||||
71: 71
|
||||
72: 72
|
||||
73: 73
|
||||
74: 74
|
||||
75: 75
|
||||
76: 76
|
||||
77: 77
|
||||
78: 78
|
||||
79: 79
|
||||
80: 80
|
||||
81: 81
|
||||
82: 82
|
||||
83: 83
|
||||
84: 84
|
||||
85: 85
|
||||
86: 86
|
||||
87: 87
|
||||
88: 88
|
||||
entities:
|
||||
- uid: 123
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: Map
|
||||
- type: EntitySaveTest
|
||||
id: map
|
||||
- uid: 0
|
||||
components:
|
||||
- type: MetaData
|
||||
- parent: 123
|
||||
type: Transform
|
||||
- chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAPgAAAA==
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
type: MapGrid
|
||||
- type: Broadphase
|
||||
- angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
type: Physics
|
||||
- fixtures: {}
|
||||
type: Fixtures
|
||||
- type: OccluderTree
|
||||
- id: grid
|
||||
type: EntitySaveTest
|
||||
- uid: 1
|
||||
type: V3TestProto
|
||||
components:
|
||||
- pos: 0.5,0.5
|
||||
parent: 0
|
||||
type: Transform
|
||||
- id: ent
|
||||
type: EntitySaveTest
|
||||
...
|
||||
";
|
||||
|
||||
private const string PrototypeV3 = @"
|
||||
- type: entity
|
||||
id: V3TestProto
|
||||
components:
|
||||
- type: EntitySaveTest
|
||||
list: [ 1, 2 ]
|
||||
";
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class BackwardsCompatibilityTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that v4 maps can be loaded. This simply tries to load a file and doesn't do a lot of extra validation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The file was pilfered from content integration tests ("floor3x3.yml") and modified slightly.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task TestLoadV4()
|
||||
{
|
||||
var server = StartServer(new ServerIntegrationOptions {ExtraPrototypes = PrototypeV4});
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var meta = server.System<MetaDataSystem>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var resourceManager = server.ResolveDependency<IResourceManagerInternal>();
|
||||
|
||||
tileMan.Register(new TileDef("Space"));
|
||||
tileMan.Register(new TileDef("A"));
|
||||
tileMan.Register(new TileDef("B"));
|
||||
var gridPath = new ResPath($"{nameof(MapDataV4Grid)}.yml");
|
||||
resourceManager.MountString(gridPath.ToString(), MapDataV4Grid);
|
||||
|
||||
MapId mapId = default;
|
||||
EntityUid mapUid = default;
|
||||
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> ent;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> grid;
|
||||
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
await server.WaitPost(() => mapUid = mapSys.CreateMap(out mapId));
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGrid(mapId, gridPath, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(0));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(mapUid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(mapUid), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(mapUid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(mapUid));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
var mapPath = new ResPath($"{nameof(MapDataV4Map)}.yml");
|
||||
resourceManager.MountString(mapPath.ToString(), MapDataV4Map);
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath, out _, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.True);
|
||||
Assert.That(meta.EntityPaused(grid), Is.True);
|
||||
Assert.That(meta.EntityPaused(map), Is.True);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Repeat test, but with the initialize maps option enabled.
|
||||
// Apparently mounted strings can only be read a single time.
|
||||
// So have to re-mount them.
|
||||
var mapPath2 = new ResPath($"{nameof(MapDataV4Map)}2.yml");
|
||||
resourceManager.MountString(mapPath2.ToString(), MapDataV4Map);
|
||||
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath2, out _, out _, opts)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(map), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private const string MapDataV4Grid = @"
|
||||
meta:
|
||||
format: 4
|
||||
name: DemoStation
|
||||
author: Space-Wizards
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
11: A
|
||||
68: B
|
||||
entities:
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 2
|
||||
components:
|
||||
- type: MetaData
|
||||
- parent: invalid
|
||||
type: Transform
|
||||
- chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAARAAAAA==
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
type: MapGrid
|
||||
- type: Broadphase
|
||||
- angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
type: Physics
|
||||
- type: OccluderTree
|
||||
- id: grid
|
||||
type: EntitySaveTest
|
||||
- proto: V4TestProto
|
||||
entities:
|
||||
- uid: 1
|
||||
components:
|
||||
- pos: 0.5,0.5
|
||||
parent: 2
|
||||
type: Transform
|
||||
- id: ent
|
||||
type: EntitySaveTest
|
||||
...
|
||||
";
|
||||
|
||||
private const string MapDataV4Map = @"
|
||||
meta:
|
||||
format: 4
|
||||
name: DemoStation
|
||||
author: Space-Wizards
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
11: A
|
||||
68: B
|
||||
entities:
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 123
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: Map
|
||||
- type: EntitySaveTest
|
||||
id: map
|
||||
- uid: 2
|
||||
components:
|
||||
- type: MetaData
|
||||
- parent: 123
|
||||
type: Transform
|
||||
- chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAARAAAAA==
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
type: MapGrid
|
||||
- type: Broadphase
|
||||
- angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
type: Physics
|
||||
- type: OccluderTree
|
||||
- id: grid
|
||||
type: EntitySaveTest
|
||||
- proto: V4TestProto
|
||||
entities:
|
||||
- uid: 1
|
||||
components:
|
||||
- pos: 0.5,0.5
|
||||
parent: 2
|
||||
type: Transform
|
||||
- id: ent
|
||||
type: EntitySaveTest
|
||||
";
|
||||
|
||||
private const string PrototypeV4 = @"
|
||||
- type: entity
|
||||
id: V4TestProto
|
||||
components:
|
||||
- type: EntitySaveTest
|
||||
list: [ 1, 2 ]
|
||||
";
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class BackwardsCompatibilityTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that v5 maps can be loaded. This simply tries to load a file and doesn't do a lot of extra validation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The file was pilfered from content integration tests ("floor3x3.yml") and modified slightly.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task TestLoadV5()
|
||||
{
|
||||
var server = StartServer(new ServerIntegrationOptions {ExtraPrototypes = PrototypeV5});
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var meta = server.System<MetaDataSystem>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var resourceManager = server.ResolveDependency<IResourceManagerInternal>();
|
||||
|
||||
tileMan.Register(new TileDef("Space"));
|
||||
tileMan.Register(new TileDef("A"));
|
||||
tileMan.Register(new TileDef("B"));
|
||||
var gridPath = new ResPath($"{nameof(MapDataV5Grid)}.yml");
|
||||
resourceManager.MountString(gridPath.ToString(), MapDataV5Grid);
|
||||
|
||||
MapId mapId = default;
|
||||
EntityUid mapUid = default;
|
||||
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> ent;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> grid;
|
||||
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
await server.WaitPost(() => mapUid = mapSys.CreateMap(out mapId));
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGrid(mapId, gridPath, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(0));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(mapUid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(mapUid), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(mapUid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(mapUid));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
var mapPath = new ResPath($"{nameof(MapDataV5Map)}.yml");
|
||||
resourceManager.MountString(mapPath.ToString(), MapDataV5Map);
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath, out _, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.True);
|
||||
Assert.That(meta.EntityPaused(grid), Is.True);
|
||||
Assert.That(meta.EntityPaused(map), Is.True);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Repeat test, but with the initialize maps option enabled.
|
||||
// Apparently mounted strings can only be read a single time.
|
||||
// So have to re-mount them.
|
||||
var mapPath2 = new ResPath($"{nameof(MapDataV5Map)}2.yml");
|
||||
resourceManager.MountString(mapPath2.ToString(), MapDataV5Map);
|
||||
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath2, out _, out _, opts)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(map), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private const string MapDataV5Grid = @"
|
||||
meta:
|
||||
format: 5
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
11: A
|
||||
69: B
|
||||
entities:
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 2
|
||||
components:
|
||||
- type: MetaData
|
||||
- parent: invalid
|
||||
type: Transform
|
||||
- chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAARQAAAA==
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
type: MapGrid
|
||||
- type: Broadphase
|
||||
- angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
type: Physics
|
||||
- fixtures: {}
|
||||
type: Fixtures
|
||||
- type: OccluderTree
|
||||
- id: grid
|
||||
type: EntitySaveTest
|
||||
- proto: V5TestProto
|
||||
entities:
|
||||
- uid: 1
|
||||
components:
|
||||
- pos: 0.5,0.5
|
||||
parent: 2
|
||||
type: Transform
|
||||
- id: ent
|
||||
type: EntitySaveTest
|
||||
";
|
||||
|
||||
private const string MapDataV5Map = @"
|
||||
meta:
|
||||
format: 5
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
11: A
|
||||
69: B
|
||||
entities:
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 123
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: Map
|
||||
- type: EntitySaveTest
|
||||
id: map
|
||||
- uid: 2
|
||||
components:
|
||||
- type: MetaData
|
||||
- parent: 123
|
||||
type: Transform
|
||||
- chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAARQAAAA==
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
|
||||
type: MapGrid
|
||||
- type: Broadphase
|
||||
- angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
type: Physics
|
||||
- fixtures: {}
|
||||
type: Fixtures
|
||||
- type: OccluderTree
|
||||
- id: grid
|
||||
type: EntitySaveTest
|
||||
- proto: V5TestProto
|
||||
entities:
|
||||
- uid: 1
|
||||
components:
|
||||
- pos: 0.5,0.5
|
||||
parent: 2
|
||||
type: Transform
|
||||
- id: ent
|
||||
type: EntitySaveTest
|
||||
";
|
||||
|
||||
private const string PrototypeV5 = @"
|
||||
- type: entity
|
||||
id: V5TestProto
|
||||
components:
|
||||
- type: EntitySaveTest
|
||||
list: [ 1, 2 ]
|
||||
";
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class BackwardsCompatibilityTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that v6 maps can be loaded. This simply tries to load a file and doesn't do a lot of extra validation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The file was pilfered from content integration tests ("floor3x3.yml") and modified slightly.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task TestLoadV6()
|
||||
{
|
||||
var server = StartServer(new ServerIntegrationOptions {ExtraPrototypes = PrototypeV6});
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var meta = server.System<MetaDataSystem>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var resourceManager = server.ResolveDependency<IResourceManagerInternal>();
|
||||
|
||||
tileMan.Register(new TileDef("Space"));
|
||||
tileMan.Register(new TileDef("A"));
|
||||
tileMan.Register(new TileDef("B"));
|
||||
var gridPath = new ResPath($"{nameof(MapDataV6Grid)}.yml");
|
||||
resourceManager.MountString(gridPath.ToString(), MapDataV6Grid);
|
||||
|
||||
MapId mapId = default;
|
||||
EntityUid mapUid = default;
|
||||
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> ent;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> grid;
|
||||
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
await server.WaitPost(() => mapUid = mapSys.CreateMap(out mapId));
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGrid(mapId, gridPath, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(0));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(mapUid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(mapUid), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(mapUid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(mapUid));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
var mapPath = new ResPath($"{nameof(MapDataV6Map)}.yml");
|
||||
resourceManager.MountString(mapPath.ToString(), MapDataV6Map);
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath, out _, out _)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.True);
|
||||
Assert.That(meta.EntityPaused(grid), Is.True);
|
||||
Assert.That(meta.EntityPaused(map), Is.True);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.Initialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Repeat test, but with the initialize maps option enabled.
|
||||
// Apparently mounted strings can only be read a single time.
|
||||
// So have to re-mount them.
|
||||
var mapPath2 = new ResPath($"{nameof(MapDataV6Map)}2.yml");
|
||||
resourceManager.MountString(mapPath2.ToString(), MapDataV6Map);
|
||||
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(mapPath2, out _, out _, opts)));
|
||||
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
map = Find(nameof(map), entMan);
|
||||
|
||||
Assert.That(ent.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(grid.Comp1.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(map.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
Assert.That(meta.EntityPaused(ent), Is.False);
|
||||
Assert.That(meta.EntityPaused(grid), Is.False);
|
||||
Assert.That(meta.EntityPaused(map), Is.False);
|
||||
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(ent).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(grid).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(map).EntityLifeStage,
|
||||
Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private const string MapDataV6Grid = @"
|
||||
meta:
|
||||
format: 6
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
11: A
|
||||
89: B
|
||||
entities:
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 2
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
parent: invalid
|
||||
- type: MapGrid
|
||||
chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAWQAAAAAA
|
||||
version: 6
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
version: 6
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
version: 6
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
version: 6
|
||||
- type: Broadphase
|
||||
- type: Physics
|
||||
bodyStatus: InAir
|
||||
angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
id: grid
|
||||
- proto: V6TestProto
|
||||
entities:
|
||||
- uid: 1
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 0.5,0.5
|
||||
parent: 2
|
||||
- type: EntitySaveTest
|
||||
id: ent
|
||||
";
|
||||
|
||||
private const string MapDataV6Map = @"
|
||||
meta:
|
||||
format: 6
|
||||
postmapinit: false
|
||||
tilemap:
|
||||
0: Space
|
||||
11: A
|
||||
89: B
|
||||
entities:
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 123
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: Map
|
||||
mapPaused: True
|
||||
- type: EntitySaveTest
|
||||
id: map
|
||||
- uid: 2
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
parent: 123
|
||||
- type: MapGrid
|
||||
chunks:
|
||||
-1,-1:
|
||||
ind: -1,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAWQAAAAAA
|
||||
version: 6
|
||||
-1,0:
|
||||
ind: -1,0
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
version: 6
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: CwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
version: 6
|
||||
0,-1:
|
||||
ind: 0,-1
|
||||
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
version: 6
|
||||
- type: Broadphase
|
||||
- type: Physics
|
||||
bodyStatus: InAir
|
||||
angularDamping: 0.05
|
||||
linearDamping: 0.05
|
||||
fixedRotation: False
|
||||
bodyType: Dynamic
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
id: grid
|
||||
- proto: V6TestProto
|
||||
entities:
|
||||
- uid: 1
|
||||
components:
|
||||
- type: Transform
|
||||
pos: 0.5,0.5
|
||||
parent: 2
|
||||
- type: EntitySaveTest
|
||||
id: ent
|
||||
";
|
||||
|
||||
private const string PrototypeV6 = @"
|
||||
- type: entity
|
||||
id: V6TestProto
|
||||
components:
|
||||
- type: EntitySaveTest
|
||||
list: [ 1, 2 ]
|
||||
";
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// Test that older file formats can still be loaded.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed partial class BackwardsCompatibilityTest : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that v7 maps can be loaded. This just re-uses some map files that are generated by other tests, and then
|
||||
/// checks that the post-load debug asserts still pass. specifically, it uses the second to last file from
|
||||
/// <see cref="AutoIncludeSerializationTest"/> and the initial file from
|
||||
/// <see cref="LifestageSerializationTest.TestMixedLifestageSerialization"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// At the time of writing, v7 is the current version, but this is here for when the version increases in the future.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task TestLoadV7()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var resourceManager = server.ResolveDependency<IResourceManagerInternal>();
|
||||
|
||||
tileMan.Register(new TileDef("space"));
|
||||
tileMan.Register(new TileDef("a"));
|
||||
|
||||
void AssertCount(int expected) => Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(expected));
|
||||
|
||||
await server.WaitPost(() => mapSys.CreateMap(out _));
|
||||
var mapLoadOpts = MapLoadOptions.Default with
|
||||
{
|
||||
DeserializationOptions = DeserializationOptions.Default with {LogOrphanedGrids = false}
|
||||
};
|
||||
|
||||
// Test the file from AutoIncludeSerializationTest
|
||||
{
|
||||
var path = new ResPath($"{nameof(MapDataV7)}.yml");
|
||||
resourceManager.MountString(path.ToString(), MapDataV7);
|
||||
|
||||
Entity<TransformComponent, EntitySaveTestComponent> grid;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> onGrid;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> otherMap;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> otherEnt;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullSpace;
|
||||
|
||||
LoadResult? result = default;
|
||||
await server.WaitAssertion(() => Assert.That(loader.TryLoadGeneric(path, out result, mapLoadOpts)));
|
||||
|
||||
Assert.That(result!.Version, Is.EqualTo(7));
|
||||
Assert.That(result.Grids, Has.Count.EqualTo(1));
|
||||
Assert.That(result.Orphans, Is.Empty); // Grid was orphaned, but was adopted after a new map was created
|
||||
Assert.That(result.Maps, Has.Count.EqualTo(2));
|
||||
Assert.That(result.NullspaceEntities, Has.Count.EqualTo(1));
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1)); // auto-generated map isn't marked as "loaded"
|
||||
AssertCount(5);
|
||||
|
||||
grid = Find(nameof(grid), entMan);
|
||||
onGrid = Find(nameof(onGrid), entMan);
|
||||
otherMap = Find(nameof(otherMap), entMan);
|
||||
otherEnt = Find(nameof(otherEnt), entMan);
|
||||
nullSpace = Find(nameof(nullSpace), entMan);
|
||||
|
||||
Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
|
||||
Assert.That(otherEnt.Comp1.ParentUid, Is.EqualTo(otherMap.Owner));
|
||||
Assert.That(otherMap.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(nullSpace.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
await server.WaitPost(() => entMan.DeleteEntity(otherMap));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(grid.Comp1.ParentUid));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
|
||||
AssertCount(0);
|
||||
}
|
||||
|
||||
|
||||
// Test the file from LifestageSerializationTest.TestMixedLifestageSerialization
|
||||
{
|
||||
var pathLifestage = new ResPath($"{nameof(MapDataV7Lifestage)}.yml");
|
||||
resourceManager.MountString(pathLifestage.ToString(), MapDataV7Lifestage);
|
||||
|
||||
Entity<TransformComponent, EntitySaveTestComponent> mapA; // preinit Map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> mapB; // postinit unpaused Map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entA; // postinit entity on preinit map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entB; // paused entity on postinit unpaused map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entC; // preinit entity on postinit map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullA; // postinit nullspace entity
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullB; // preinit nullspace entity
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullC; // paused postinit nullspace entity
|
||||
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
LoadResult? result = default;
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGeneric(pathLifestage, out result)));
|
||||
Assert.That(result!.Version, Is.EqualTo(7));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(8));
|
||||
|
||||
mapA = Find(nameof(mapA), entMan);
|
||||
mapB = Find(nameof(mapB), entMan);
|
||||
entA = Find(nameof(entA), entMan);
|
||||
entB = Find(nameof(entB), entMan);
|
||||
entC = Find(nameof(entC), entMan);
|
||||
nullA = Find(nameof(nullA), entMan);
|
||||
nullB = Find(nameof(nullB), entMan);
|
||||
nullC = Find(nameof(nullC), entMan);
|
||||
|
||||
AssertPaused(true, mapA, entB, nullC);
|
||||
AssertPaused(false, mapB, entA, entC, nullA, nullB);
|
||||
AssertPreInit(true, mapA, entC, nullB);
|
||||
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
void AssertPaused(bool expected, params EntityUid[] uids)
|
||||
{
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
|
||||
void AssertPreInit(bool expected, params EntityUid[] uids)
|
||||
{
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
Assert.That(entMan!.GetComponent<MetaDataComponent>(uid).EntityLifeStage,
|
||||
expected
|
||||
? Is.LessThan(EntityLifeStage.MapInitialized)
|
||||
: Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const string MapDataV7 = @"
|
||||
meta:
|
||||
format: 7
|
||||
category: Unknown
|
||||
engineVersion: 238.0.0
|
||||
forkId: """"
|
||||
forkVersion: """"
|
||||
time: 12/25/2024 00:40:09
|
||||
entityCount: 5
|
||||
maps:
|
||||
- 3
|
||||
grids:
|
||||
- 1
|
||||
orphans:
|
||||
- 1
|
||||
nullspace:
|
||||
- 5
|
||||
tilemap:
|
||||
1: space
|
||||
0: a
|
||||
entities:
|
||||
- proto: """"
|
||||
entities:
|
||||
- uid: 1
|
||||
paused: false
|
||||
components:
|
||||
- type: MetaData
|
||||
name: grid
|
||||
- type: Transform
|
||||
parent: invalid
|
||||
- type: MapGrid
|
||||
chunks:
|
||||
0,0:
|
||||
ind: 0,0
|
||||
tiles: AAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
|
||||
version: 6
|
||||
- type: Broadphase
|
||||
- type: Physics
|
||||
- type: Fixtures
|
||||
fixtures: {}
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: grid
|
||||
- uid: 2
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
pos: 0.5,0.5
|
||||
parent: 1
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
entity: 3
|
||||
id: onGrid
|
||||
- uid: 3
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
name: Map Entity
|
||||
- type: Transform
|
||||
- type: Map
|
||||
mapInitialized: True
|
||||
- type: PhysicsMap
|
||||
- type: GridTree
|
||||
- type: MovedGrids
|
||||
- type: Broadphase
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: otherMap
|
||||
- uid: 4
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
parent: 3
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
entity: 5
|
||||
id: otherEnt
|
||||
- uid: 5
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: nullSpace
|
||||
";
|
||||
|
||||
private const string MapDataV7Lifestage = @"
|
||||
meta:
|
||||
format: 7
|
||||
category: Unknown
|
||||
engineVersion: 238.0.0
|
||||
forkId: """"
|
||||
forkVersion: """"
|
||||
time: 12/25/2024 00:50:59
|
||||
entityCount: 8
|
||||
maps:
|
||||
- 1
|
||||
- 3
|
||||
grids: []
|
||||
orphans: []
|
||||
nullspace:
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
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: EntitySaveTest
|
||||
list: []
|
||||
id: mapA
|
||||
- uid: 2
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
parent: 1
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: entA
|
||||
- uid: 3
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
name: Map Entity
|
||||
- type: Transform
|
||||
- type: Map
|
||||
mapInitialized: True
|
||||
- type: PhysicsMap
|
||||
- type: GridTree
|
||||
- type: MovedGrids
|
||||
- type: Broadphase
|
||||
- type: OccluderTree
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: mapB
|
||||
- uid: 4
|
||||
mapInit: true
|
||||
paused: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
parent: 3
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: entB
|
||||
- uid: 5
|
||||
paused: false
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
parent: 3
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: entC
|
||||
- uid: 6
|
||||
mapInit: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: nullA
|
||||
- uid: 7
|
||||
paused: false
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: nullB
|
||||
- uid: 8
|
||||
mapInit: true
|
||||
paused: true
|
||||
components:
|
||||
- type: MetaData
|
||||
- type: Transform
|
||||
- type: EntitySaveTest
|
||||
list: []
|
||||
id: nullC
|
||||
";
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class CategorizationTest : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that file categories are correctly assigned when saving & loading different combinations of entites.
|
||||
/// </summary>
|
||||
[Test]
|
||||
[TestOf(typeof(FileCategory))]
|
||||
public async Task TestCategorization()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var meta = server.System<MetaDataSystem>();
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var path = new ResPath($"{nameof(TestCategorization)}.yml");
|
||||
|
||||
tileMan.Register(new TileDef("space"));
|
||||
var tDef = new TileDef("a");
|
||||
tileMan.Register(tDef);
|
||||
|
||||
EntityUid mapA = default;
|
||||
EntityUid mapB = default;
|
||||
EntityUid gridA = default; // grid on map A
|
||||
EntityUid gridB = default; // grid on map B
|
||||
EntityUid entA = default; // ent on grid A
|
||||
EntityUid entB = default; // ent on grid B
|
||||
EntityUid entC = default; // a separate entity on grid B
|
||||
EntityUid child = default; // child of entB
|
||||
EntityUid @null = default; // nullspace entity
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapA = mapSys.CreateMap(out var mapIdA);
|
||||
mapB = mapSys.CreateMap(out var mapIdB);
|
||||
var gridEntA = mapMan.CreateGridEntity(mapIdA);
|
||||
var gridEntB = mapMan.CreateGridEntity(mapIdB);
|
||||
mapSys.SetTile(gridEntA, Vector2i.Zero, new Tile(tDef.TileId));
|
||||
mapSys.SetTile(gridEntB, Vector2i.Zero, new Tile(tDef.TileId));
|
||||
gridA = gridEntA.Owner;
|
||||
gridB = gridEntB.Owner;
|
||||
entA = entMan.SpawnEntity(null, new EntityCoordinates(gridA, 0.5f, 0.5f));
|
||||
entB = entMan.SpawnEntity(null, new EntityCoordinates(gridB, 0.5f, 0.5f));
|
||||
entC = entMan.SpawnEntity(null, new EntityCoordinates(gridB, 0.5f, 0.5f));
|
||||
child = entMan.SpawnEntity(null, new EntityCoordinates(entB, 0.5f, 0.5f));
|
||||
@null = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
});
|
||||
|
||||
FileCategory Save(params EntityUid[] ents)
|
||||
{
|
||||
FileCategory cat = FileCategory.Unknown;
|
||||
Assert.That(loader.TrySaveGeneric(ents.ToHashSet(), path, out cat));
|
||||
return cat;
|
||||
}
|
||||
|
||||
async Task<LoadResult> Load(FileCategory expected, int count)
|
||||
{
|
||||
var opts = MapLoadOptions.Default with
|
||||
{
|
||||
ExpectedCategory = expected,
|
||||
DeserializationOptions = DeserializationOptions.Default with { LogOrphanedGrids = false}
|
||||
};
|
||||
LoadResult? result = null;
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGeneric(path, out result, opts)));
|
||||
Assert.That(result!.Category, Is.EqualTo(expected));
|
||||
Assert.That(result.Entities, Has.Count.EqualTo(count));
|
||||
return result;
|
||||
}
|
||||
|
||||
async Task SaveAndLoad(FileCategory expected, int count, params EntityUid[] ents)
|
||||
{
|
||||
var cat = Save(ents);
|
||||
Assert.That(cat, Is.EqualTo(expected));
|
||||
var result = await Load(expected, count);
|
||||
await server.WaitPost(() => loader.Delete(result));
|
||||
}
|
||||
|
||||
// Saving a single entity works as expected, even if it also serializes their children
|
||||
await SaveAndLoad(FileCategory.Entity, 1, entA);
|
||||
await SaveAndLoad(FileCategory.Entity, 2, entB);
|
||||
await SaveAndLoad(FileCategory.Entity, 1, child);
|
||||
|
||||
// Including nullspace entities doesn't change the category, though a file containing only null-space entities
|
||||
// is "unkown". Maybe in future they will get their own category
|
||||
await SaveAndLoad(FileCategory.Entity, 2, entA, @null);
|
||||
await SaveAndLoad(FileCategory.Entity, 3, entB, @null);
|
||||
await SaveAndLoad(FileCategory.Entity, 2, child, @null);
|
||||
await SaveAndLoad(FileCategory.Unknown, 1, @null);
|
||||
|
||||
// More than one entity is unknown
|
||||
await SaveAndLoad(FileCategory.Unknown, 3, entA, entB);
|
||||
await SaveAndLoad(FileCategory.Unknown, 4, entA, entB, @null);
|
||||
|
||||
// Saving grids works as expected. All counts are 1 higher than expected due to a map being automatically created.
|
||||
await SaveAndLoad(FileCategory.Grid, 3, gridA);
|
||||
await SaveAndLoad(FileCategory.Grid, 5, gridB);
|
||||
await SaveAndLoad(FileCategory.Grid, 4, gridA, @null);
|
||||
await SaveAndLoad(FileCategory.Grid, 6, gridB, @null);
|
||||
|
||||
// And saving maps also works
|
||||
await SaveAndLoad(FileCategory.Map, 3, mapA);
|
||||
await SaveAndLoad(FileCategory.Map, 5, mapB);
|
||||
await SaveAndLoad(FileCategory.Map, 4, mapA, @null);
|
||||
await SaveAndLoad(FileCategory.Map, 6, mapB, @null);
|
||||
|
||||
// Combinations of grids, entities, and maps, are unknown
|
||||
await SaveAndLoad(FileCategory.Unknown, 4, mapA, child);
|
||||
await SaveAndLoad(FileCategory.Unknown, 4, gridA, child);
|
||||
await SaveAndLoad(FileCategory.Unknown, 8, gridA, mapB);
|
||||
await SaveAndLoad(FileCategory.Unknown, 5, mapA, child, @null);
|
||||
await SaveAndLoad(FileCategory.Unknown, 5, gridA, child, @null);
|
||||
await SaveAndLoad(FileCategory.Unknown, 9, gridA, mapB, @null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class LifestageSerializationTest : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that whether or not an entity has been paused or map-initialized is preserved across saves & loads.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestLifestageSerialization()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var preInitPath = new ResPath($"{nameof(TestLifestageSerialization)}_preInit.yml");
|
||||
var postInitPath = new ResPath($"{nameof(TestLifestageSerialization)}_postInit.yml");
|
||||
var pausedPostInitPath = new ResPath($"{nameof(TestLifestageSerialization)}_paused.yml");
|
||||
|
||||
// Create a pre-init map, and spawn multiple entities on it
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entA = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entB = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> childA = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> childB = default;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapUid = mapSys.CreateMap(out var mapId, runMapInit: false);
|
||||
var entAUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var entBUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var childAUid = entMan.SpawnEntity(null, new EntityCoordinates(entAUid, 0, 0));
|
||||
var childBUid = entMan.SpawnEntity(null, new EntityCoordinates(entBUid, 0, 0));
|
||||
map = Get(mapUid, entMan);
|
||||
entA = Get(entAUid, entMan);
|
||||
entB = Get(entBUid, entMan);
|
||||
childA = Get(childAUid, entMan);
|
||||
childB = Get(childBUid, entMan);
|
||||
map.Comp2.Id = nameof(map);
|
||||
entA.Comp2.Id = nameof(entA);
|
||||
entB.Comp2.Id = nameof(entB);
|
||||
childA.Comp2.Id = nameof(childA);
|
||||
childB.Comp2.Id = nameof(childB);
|
||||
});
|
||||
|
||||
void AssertPaused(bool expected, params EntityUid[] uids)
|
||||
{
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
|
||||
void AssertPreInit(bool expected, params EntityUid[] uids)
|
||||
{
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
Assert.That(entMan!.GetComponent<MetaDataComponent>(uid).EntityLifeStage,
|
||||
expected
|
||||
? Is.LessThan(EntityLifeStage.MapInitialized)
|
||||
: Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
}
|
||||
}
|
||||
|
||||
// All entities should initially be un-initialized and paused.
|
||||
AssertPaused(true, map, entA, entB, childA, childB);
|
||||
AssertPreInit(true, map, entA, entB, childA, childB);
|
||||
Assert.That(loader.TrySaveMap(map, preInitPath));
|
||||
|
||||
async Task Delete()
|
||||
{
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(5));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(map));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
async Task Load(ResPath f, DeserializationOptions? o)
|
||||
{
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadMap(f, out _, out _, o)));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(5));
|
||||
}
|
||||
|
||||
void FindAll()
|
||||
{
|
||||
map = Find(nameof(map), entMan);
|
||||
entA = Find(nameof(entA), entMan);
|
||||
entB = Find(nameof(entB), entMan);
|
||||
childA = Find(nameof(childA), entMan);
|
||||
childB = Find(nameof(childB), entMan);
|
||||
}
|
||||
|
||||
async Task Reload(ResPath f, DeserializationOptions? o = null)
|
||||
{
|
||||
await Delete();
|
||||
await Load(f, o);
|
||||
FindAll();
|
||||
}
|
||||
|
||||
// Saving and loading the pre-init map should have no effect.
|
||||
await Reload(preInitPath);
|
||||
AssertPaused(true, map, entA, entB, childA, childB);
|
||||
AssertPreInit(true, map, entA, entB, childA, childB);
|
||||
|
||||
// Saving and loading with the map-init option set to true should initialize & unpause all entities
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await Reload(preInitPath, opts);
|
||||
AssertPaused(false, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
Assert.That(loader.TrySaveMap(map, postInitPath));
|
||||
|
||||
// re-loading the post-init map should keep everything initialized, even without explicitly asking to initialize maps.
|
||||
await Reload(postInitPath);
|
||||
AssertPaused(false, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
|
||||
// Load & initialize a pre-init map, but with the pause maps option enabled.
|
||||
opts = DeserializationOptions.Default with {InitializeMaps = true, PauseMaps = true};
|
||||
await Reload(preInitPath, opts);
|
||||
AssertPaused(true, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
Assert.That(loader.TrySaveMap(map, pausedPostInitPath));
|
||||
|
||||
// The pause map option also works when loading un-paused post-init maps
|
||||
opts = DeserializationOptions.Default with {PauseMaps = true};
|
||||
await Reload(postInitPath, opts);
|
||||
AssertPaused(true, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
|
||||
// loading & initializing a post-init map should cause no issues.
|
||||
opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await Reload(postInitPath, opts);
|
||||
AssertPaused(false, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
|
||||
// Loading a paused post init map does NOT automatically un-pause entities
|
||||
await Reload(pausedPostInitPath);
|
||||
AssertPaused(true, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
|
||||
// The above holds even if we are explicitly initialising maps.
|
||||
opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await Reload(pausedPostInitPath, opts);
|
||||
AssertPaused(true, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
|
||||
// And re-paused an already paused map should have no impact.
|
||||
opts = DeserializationOptions.Default with {InitializeMaps = true, PauseMaps = true};
|
||||
await Reload(pausedPostInitPath, opts);
|
||||
AssertPaused(true, map, entA, entB, childA, childB);
|
||||
AssertPreInit(false, map, entA, entB, childA, childB);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="TestLifestageSerialization"/> that has multiple maps and combinations. E.g., a single
|
||||
/// paused entity on an un-paused map.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestMixedLifestageSerialization()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var meta = server.System<MetaDataSystem>();
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var path = new ResPath($"{nameof(TestMixedLifestageSerialization)}.yml");
|
||||
var altPath = new ResPath($"{nameof(TestMixedLifestageSerialization)}_alt.yml");
|
||||
|
||||
Entity<TransformComponent, EntitySaveTestComponent> mapA = default; // preinit Map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> mapB = default; // postinit unpaused Map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entA = default; // postinit entity on preinit map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entB = default; // paused entity on postinit unpaused map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entC = default; // preinit entity on postinit map
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullA = default; // postinit nullspace entity
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullB = default; // preinit nullspace entity
|
||||
Entity<TransformComponent, EntitySaveTestComponent> nullC = default; // paused postinit nullspace entity
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapAUid = mapSys.CreateMap(out var mapIdA, runMapInit: false);
|
||||
var mapBUid = mapSys.CreateMap(out var mapIdB, runMapInit: true);
|
||||
|
||||
var entAUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapIdA));
|
||||
entMan.RunMapInit(entAUid, entMan.GetComponent<MetaDataComponent>(entAUid));
|
||||
meta.SetEntityPaused(entAUid, false);
|
||||
|
||||
var entBUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapIdB));
|
||||
meta.SetEntityPaused(entBUid, true);
|
||||
|
||||
var entCUid = entMan.CreateEntityUninitialized(null, new MapCoordinates(0, 0, mapIdB));
|
||||
entMan.InitializeAndStartEntity(entCUid, doMapInit: false);
|
||||
|
||||
var nullAUid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
|
||||
var nullBUid = entMan.CreateEntityUninitialized(null, MapCoordinates.Nullspace);
|
||||
entMan.InitializeAndStartEntity(nullBUid, doMapInit: false);
|
||||
|
||||
var nullCUid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
meta.SetEntityPaused(nullCUid, true);
|
||||
|
||||
mapA = Get(mapAUid, entMan);
|
||||
mapB = Get(mapBUid, entMan);
|
||||
entA = Get(entAUid, entMan);
|
||||
entB = Get(entBUid, entMan);
|
||||
entC = Get(entCUid, entMan);
|
||||
nullA = Get(nullAUid, entMan);
|
||||
nullB = Get(nullBUid, entMan);
|
||||
nullC = Get(nullCUid, entMan);
|
||||
|
||||
mapA.Comp2.Id = nameof(mapA);
|
||||
mapB.Comp2.Id = nameof(mapB);
|
||||
entA.Comp2.Id = nameof(entA);
|
||||
entB.Comp2.Id = nameof(entB);
|
||||
entC.Comp2.Id = nameof(entC);
|
||||
nullA.Comp2.Id = nameof(nullA);
|
||||
nullB.Comp2.Id = nameof(nullB);
|
||||
nullC.Comp2.Id = nameof(nullC);
|
||||
});
|
||||
|
||||
string? Name(EntityUid uid)
|
||||
{
|
||||
return entMan.GetComponentOrNull<EntitySaveTestComponent>(uid)?.Id;
|
||||
}
|
||||
|
||||
void AssertPaused(bool expected, params EntityUid[] uids)
|
||||
{
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected), Name(uid));
|
||||
}
|
||||
}
|
||||
|
||||
void AssertPreInit(bool expected, params EntityUid[] uids)
|
||||
{
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
Assert.That(entMan!.GetComponent<MetaDataComponent>(uid).EntityLifeStage,
|
||||
expected
|
||||
? Is.LessThan(EntityLifeStage.MapInitialized)
|
||||
: Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
}
|
||||
}
|
||||
|
||||
void Save(ResPath f)
|
||||
{
|
||||
Assert.That(loader.TrySaveGeneric([mapA, mapB, nullA, nullB, nullC], f, out _));
|
||||
}
|
||||
|
||||
async Task Delete()
|
||||
{
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(8));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(mapA));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(mapB));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullA));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullB));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(nullC));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
async Task Load(ResPath f, DeserializationOptions? o)
|
||||
{
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
var oo = MapLoadOptions.Default with
|
||||
{
|
||||
DeserializationOptions = o ?? DeserializationOptions.Default
|
||||
};
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGeneric(f, out _, oo)));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(8));
|
||||
}
|
||||
|
||||
void FindAll()
|
||||
{
|
||||
mapA = Find(nameof(mapA), entMan);
|
||||
mapB = Find(nameof(mapB), entMan);
|
||||
entA = Find(nameof(entA), entMan);
|
||||
entB = Find(nameof(entB), entMan);
|
||||
entC = Find(nameof(entC), entMan);
|
||||
nullA = Find(nameof(nullA), entMan);
|
||||
nullB = Find(nameof(nullB), entMan);
|
||||
nullC = Find(nameof(nullC), entMan);
|
||||
}
|
||||
|
||||
async Task Reload(ResPath f, DeserializationOptions? o = null)
|
||||
{
|
||||
await Delete();
|
||||
await Load(f, o);
|
||||
FindAll();
|
||||
}
|
||||
|
||||
// All entities should initially be in their respective expected states.
|
||||
// entC (pre-mapinit entity on a post-mapinit map) is a bit fucky, and I don't know if that should even be allowed.
|
||||
// Note that its just pre-init, not paused, as pre-mapinit entities get paused due to the maps state, not as a general result of being pre-mapinit.
|
||||
// If this ever changes, these assers need fixing.
|
||||
AssertPaused(true, mapA, entB, nullC);
|
||||
AssertPaused(false, mapB, entA, entC, nullA, nullB);
|
||||
AssertPreInit(true, mapA, entC, nullB);
|
||||
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
// Saving and re-loading entities should leave their metadata unchanged.
|
||||
Save(path);
|
||||
await Reload(path);
|
||||
AssertPaused(true, mapA, entB, nullC);
|
||||
AssertPaused(false, mapB, entA, entC, nullA, nullB);
|
||||
AssertPreInit(true, mapA, entC, nullB);
|
||||
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
// reload maps with the mapinit option. This should only affect mapA, as entA is the only one on the map and it
|
||||
// is already initialized,
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
await Reload(path, opts);
|
||||
AssertPaused(true, entB, nullC);
|
||||
AssertPaused(false, mapA, mapB, entA, entC, nullA, nullB);
|
||||
AssertPreInit(true, entC, nullB);
|
||||
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
// Reloading the new configuration changes nothing
|
||||
Save(altPath);
|
||||
await Reload(altPath, opts);
|
||||
AssertPaused(true, entB, nullC);
|
||||
AssertPaused(false, mapA, mapB, entA, entC, nullA, nullB);
|
||||
AssertPreInit(true, entC, nullB);
|
||||
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
// Pause all maps. This will not actually pause entityA, as mapA is already paused (due to being pre-init), so
|
||||
// it will not iterate through its children. Maybe this will change in future, but I don't think we should even
|
||||
// be trying to actively support having post-init entities on a pre-init map. This is subject to maybe change
|
||||
// one day, though if it does the option should be changed to PauseEntities to clarify that it will pause ALL
|
||||
// entities, not just maps.
|
||||
opts = DeserializationOptions.Default with {PauseMaps = true};
|
||||
await Reload(path, opts);
|
||||
AssertPaused(true, mapA, mapB, entC, entB, nullC);
|
||||
AssertPaused(false, entA, nullA, nullB);
|
||||
AssertPreInit(true, mapA, entC, nullB);
|
||||
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
// Reloading the new configuration changes nothing
|
||||
Save(altPath);
|
||||
await Reload(altPath, opts);
|
||||
AssertPaused(true, mapA, mapB, entC, entB, nullC);
|
||||
AssertPaused(false, entA, nullA, nullB);
|
||||
AssertPreInit(true, mapA, entC, nullB);
|
||||
AssertPreInit(false, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
// Initialise and pause all maps. Similar to the previous test with entA, this will not affect entC even
|
||||
// though it is pre-init, because it is on a post-init map. Again, this is subject to maybe change one day.
|
||||
// Though if it does, the option should be changed to MapInitializeEntities to clarify that it will mapinit ALL
|
||||
// entities, not just maps.
|
||||
opts = DeserializationOptions.Default with {InitializeMaps = true, PauseMaps = true};
|
||||
await Reload(path, opts);
|
||||
AssertPaused(true, mapA, mapB, entB, entC, nullC);
|
||||
AssertPaused(false, entA, nullA, nullB);
|
||||
AssertPreInit(true, entC, nullB);
|
||||
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
||||
|
||||
// Reloading the new configuration changes nothing
|
||||
Save(altPath);
|
||||
await Reload(altPath, opts);
|
||||
AssertPaused(true, mapA, mapB, entB, entC, nullC);
|
||||
AssertPaused(false, entA, nullA, nullB);
|
||||
AssertPreInit(true, entC, nullB);
|
||||
AssertPreInit(false, mapA, mapB, entA, entB, nullA, nullC);
|
||||
}
|
||||
}
|
||||
231
Robust.UnitTesting/Shared/EntitySerialization/MapMergeTest.cs
Normal file
231
Robust.UnitTesting/Shared/EntitySerialization/MapMergeTest.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
/// <summary>
|
||||
/// Test that loading a pre-init map/grid onto a post-init map should initialize, while loading a post-init map/grid
|
||||
/// onto a paused map should pause it.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed partial class MapMergeTest : RobustIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestMapMerge()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
|
||||
var mapPath = new ResPath($"{nameof(TestMapMerge)}_map.yml");
|
||||
var gridPath = new ResPath($"{nameof(TestMapMerge)}_grid.yml");
|
||||
|
||||
tileMan.Register(new TileDef("space"));
|
||||
var tDef = new TileDef("a");
|
||||
tileMan.Register(tDef);
|
||||
|
||||
MapId mapId = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> ent = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> grid = default;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapUid = mapSys.CreateMap(out mapId, runMapInit: false);
|
||||
var gridEnt = mapMan.CreateGridEntity(mapId);
|
||||
mapSys.SetTile(gridEnt, Vector2i.Zero, new Tile(tDef.TileId));
|
||||
var entUid = entMan.SpawnEntity(null, new MapCoordinates(10, 10, mapId));
|
||||
map = Get(mapUid, entMan);
|
||||
ent = Get(entUid, entMan);
|
||||
grid = Get(gridEnt.Owner, entMan);
|
||||
});
|
||||
|
||||
void AssertPaused(EntityUid uid, bool expected = true)
|
||||
{
|
||||
Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
void AssertPreInit(EntityUid uid, bool expected = true)
|
||||
{
|
||||
Assert.That(entMan!.GetComponent<MetaDataComponent>(uid).EntityLifeStage,
|
||||
expected
|
||||
? Is.LessThan(EntityLifeStage.MapInitialized)
|
||||
: Is.EqualTo(EntityLifeStage.MapInitialized));
|
||||
}
|
||||
|
||||
map.Comp2!.Id = nameof(map);
|
||||
ent.Comp2!.Id = nameof(ent);
|
||||
grid.Comp2!.Id = nameof(grid);
|
||||
|
||||
AssertPaused(map);
|
||||
AssertPreInit(map);
|
||||
AssertPaused(ent);
|
||||
AssertPreInit(ent);
|
||||
AssertPaused(grid);
|
||||
AssertPreInit(grid);
|
||||
|
||||
// Save then delete everything
|
||||
Assert.That(loader.TrySaveMap(map, mapPath));
|
||||
Assert.That(loader.TrySaveGrid(grid, gridPath));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load a grid onto a pre-init map.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: false));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.False);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.True);
|
||||
Assert.That(loader.TryLoadGrid(mapId, gridPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(1));
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid);
|
||||
AssertPreInit(grid);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Merge a map onto a pre-init map.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: false));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.False);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.True);
|
||||
Assert.That(loader.TryMergeMap(mapId, mapPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2)); // The loaded map entity gets deleted after merging
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid);
|
||||
AssertPreInit(grid);
|
||||
AssertPaused(ent);
|
||||
AssertPreInit(ent);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load a grid onto a post-init map.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: true));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.True);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.False);
|
||||
Assert.That(loader.TryLoadGrid(mapId, gridPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(1));
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid, false);
|
||||
AssertPreInit(grid, false);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Merge a map onto a post-init map.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: true));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.True);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.False);
|
||||
Assert.That(loader.TryMergeMap(mapId, mapPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid, false);
|
||||
AssertPreInit(grid, false);
|
||||
AssertPaused(ent, false);
|
||||
AssertPreInit(ent, false);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load a grid onto a paused post-init map.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: true));
|
||||
await server.WaitPost(() => mapSys.SetPaused(mapId, true));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.True);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.True);
|
||||
Assert.That(loader.TryLoadGrid(mapId, gridPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(1));
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid);
|
||||
AssertPreInit(grid, false);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Merge a map onto a paused post-init map.
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: true));
|
||||
await server.WaitPost(() => mapSys.SetPaused(mapId, true));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.True);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.True);
|
||||
Assert.That(loader.TryMergeMap(mapId, mapPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid);
|
||||
AssertPreInit(grid, false);
|
||||
AssertPaused(ent);
|
||||
AssertPreInit(ent, false);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Check that the map initialization deserialziation options have no effect.
|
||||
// We are loading onto an existing map, deserialization shouldn't modify it directly.
|
||||
|
||||
|
||||
// Load a grid onto a pre-init map, with InitializeMaps = true
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: false));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.False);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.True);
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
Assert.That(loader.TryLoadGrid(mapId, gridPath, out _, opts));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(1));
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid);
|
||||
AssertPreInit(grid);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Merge a map onto a pre-init map, with InitializeMaps = true
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: false));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.False);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.True);
|
||||
opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
Assert.That(loader.TryMergeMap(mapId, mapPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2)); // The loaded map entity gets deleted after merging
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid);
|
||||
AssertPreInit(grid);
|
||||
AssertPaused(ent);
|
||||
AssertPreInit(ent);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load a grid onto a post-init map, with PauseMaps = true
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: true));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.True);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.False);
|
||||
opts = DeserializationOptions.Default with {PauseMaps = true};
|
||||
Assert.That(loader.TryLoadGrid(mapId, gridPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(1));
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid, false);
|
||||
AssertPreInit(grid, false);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load a grid onto a post-init map, with PauseMaps = true
|
||||
await server.WaitPost(() => mapSys.CreateMap(out mapId, runMapInit: true));
|
||||
Assert.That(mapSys.IsInitialized(mapId), Is.True);
|
||||
Assert.That(mapSys.IsPaused(mapId), Is.False);
|
||||
opts = DeserializationOptions.Default with {PauseMaps = true};
|
||||
Assert.That(loader.TryMergeMap(mapId, mapPath, out _));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
ent = Find(nameof(ent), entMan);
|
||||
grid = Find(nameof(grid), entMan);
|
||||
AssertPaused(grid, false);
|
||||
AssertPreInit(grid, false);
|
||||
AssertPaused(ent, false);
|
||||
AssertPreInit(ent, false);
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Components;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[TestFixture]
|
||||
public sealed partial class OrphanSerializationTest : RobustIntegrationTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Check that we can save & load a file containing multiple orphaned (non-grid) entities.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestMultipleOrphanSerialization()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var xform = server.System<SharedTransformSystem>();
|
||||
var pathA = new ResPath($"{nameof(TestMultipleOrphanSerialization)}_A.yml");
|
||||
var pathB = new ResPath($"{nameof(TestMultipleOrphanSerialization)}_B.yml");
|
||||
var pathCombined = new ResPath($"{nameof(TestMultipleOrphanSerialization)}_C.yml");
|
||||
|
||||
// Spawn multiple entities on a map
|
||||
MapId mapId = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entA = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> entB = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> child = default;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
mapSys.CreateMap(out mapId);
|
||||
var entAUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var entBUid = entMan.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var childUid = entMan.SpawnEntity(null, new EntityCoordinates(entBUid, 0, 0));
|
||||
entA = Get(entAUid, entMan);
|
||||
entB = Get(entBUid, entMan);
|
||||
child = Get(childUid, entMan);
|
||||
entA.Comp2.Id = nameof(entA);
|
||||
entB.Comp2.Id = nameof(entB);
|
||||
child.Comp2.Id = nameof(child);
|
||||
xform.SetLocalPosition(entB.Owner, new (100,100));
|
||||
});
|
||||
|
||||
// Entities are not in null-space
|
||||
Assert.That(entA.Comp1!.ParentUid, Is.Not.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(entB.Comp1!.ParentUid, Is.Not.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(child.Comp1!.ParentUid, Is.EqualTo(entB.Owner));
|
||||
|
||||
// Save the entities without their map
|
||||
Assert.That(loader.TrySaveEntity(entA, pathA));
|
||||
Assert.That(loader.TrySaveEntity(entB, pathB));
|
||||
Assert.That(loader.TrySaveGeneric([entA.Owner, entB.Owner], pathCombined, out var cat));
|
||||
Assert.That(cat, Is.EqualTo(FileCategory.Unknown));
|
||||
|
||||
// Delete all the entities.
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load in the file containing only entA.
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadEntity(pathA, out _)));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(1));
|
||||
entA = Find(nameof(entA), entMan);
|
||||
Assert.That(entA.Comp1!.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(entA));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load in the file containing entB and its child
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadEntity(pathB, out _)));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
entB = Find(nameof(entB), entMan);
|
||||
child = Find(nameof(child), entMan);
|
||||
// Even though the entities are in null-space their local position is preserved.
|
||||
// This is so that you can save multiple entities on a map, without saving the map, while still preserving
|
||||
// relative positions for loading them onto some other map.
|
||||
Assert.That(entB.Comp1.LocalPosition, Is.Approximately(new Vector2(100, 100)));
|
||||
Assert.That(entB.Comp1!.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(child.Comp1!.ParentUid, Is.EqualTo(entB.Owner));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(entB));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load the file that contains both of them
|
||||
LoadResult? result = null;
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGeneric(pathCombined, out result)));
|
||||
Assert.That(result!.Category, Is.EqualTo(FileCategory.Unknown));
|
||||
Assert.That(result.Orphans, Has.Count.EqualTo(2));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
entA = Find(nameof(entA), entMan);
|
||||
entB = Find(nameof(entB), entMan);
|
||||
child = Find(nameof(child), entMan);
|
||||
Assert.That(entA.Comp1!.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(entB.Comp1!.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(entB.Comp1.LocalPosition, Is.Approximately(new Vector2(100, 100)));
|
||||
Assert.That(child.Comp1!.ParentUid, Is.EqualTo(entB.Owner));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(entA));
|
||||
await server.WaitPost(() => entMan.DeleteEntity(entB));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that we can save & load a file containing multiple orphaned grid entities.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task TestOrphanedGridSerialization()
|
||||
{
|
||||
var server = StartServer();
|
||||
await server.WaitIdleAsync();
|
||||
var entMan = server.EntMan;
|
||||
var mapSys = server.System<SharedMapSystem>();
|
||||
var loader = server.System<MapLoaderSystem>();
|
||||
var xform = server.System<SharedTransformSystem>();
|
||||
var mapMan = server.ResolveDependency<IMapManager>();
|
||||
var tileMan = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var pathA = new ResPath($"{nameof(TestOrphanedGridSerialization)}_A.yml");
|
||||
var pathB = new ResPath($"{nameof(TestOrphanedGridSerialization)}_B.yml");
|
||||
var pathCombined = new ResPath($"{nameof(TestOrphanedGridSerialization)}_C.yml");
|
||||
|
||||
tileMan.Register(new TileDef("space"));
|
||||
var tDef = new TileDef("a");
|
||||
tileMan.Register(tDef);
|
||||
|
||||
// Spawn multiple entities on a map
|
||||
MapId mapId = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> map = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> gridA = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> gridB = default;
|
||||
Entity<TransformComponent, EntitySaveTestComponent> child = default;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var mapUid = mapSys.CreateMap(out mapId);
|
||||
map = Get(mapUid, entMan);
|
||||
|
||||
var gridAUid = mapMan.CreateGridEntity(mapId);
|
||||
mapSys.SetTile(gridAUid, Vector2i.Zero, new Tile(tDef.TileId));
|
||||
gridA = Get(gridAUid, entMan);
|
||||
xform.SetLocalPosition(gridA.Owner, new(100, 100));
|
||||
|
||||
var gridBUid = mapMan.CreateGridEntity(mapId);
|
||||
mapSys.SetTile(gridBUid, Vector2i.Zero, new Tile(tDef.TileId));
|
||||
gridB = Get(gridBUid, entMan);
|
||||
|
||||
var childUid = entMan.SpawnEntity(null, new EntityCoordinates(gridBUid, 0.5f, 0.5f));
|
||||
child = Get(childUid, entMan);
|
||||
|
||||
map.Comp2.Id = nameof(map);
|
||||
gridA.Comp2.Id = nameof(gridA);
|
||||
gridB.Comp2.Id = nameof(gridB);
|
||||
child.Comp2.Id = nameof(child);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
// grids are not in null-space
|
||||
Assert.That(gridA.Comp1!.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(gridB.Comp1!.ParentUid, Is.EqualTo(map.Owner));
|
||||
Assert.That(child.Comp1!.ParentUid, Is.EqualTo(gridB.Owner));
|
||||
Assert.That(map.Comp1!.ParentUid, Is.EqualTo(EntityUid.Invalid));
|
||||
|
||||
// Save the grids without their map
|
||||
Assert.That(loader.TrySaveGrid(gridA, pathA));
|
||||
Assert.That(loader.TrySaveGrid(gridB, pathB));
|
||||
Assert.That(loader.TrySaveGeneric([gridA.Owner, gridB.Owner], pathCombined, out var cat));
|
||||
Assert.That(cat, Is.EqualTo(FileCategory.Unknown));
|
||||
|
||||
// Delete all the entities.
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(4));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load in the file containing only gridA.
|
||||
EntityUid newMap = default;
|
||||
await server.WaitPost(() => newMap = mapSys.CreateMap(out mapId));
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGrid(mapId, pathA, out _)));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(1));
|
||||
gridA = Find(nameof(gridA), entMan);
|
||||
Assert.That(gridA.Comp1.LocalPosition, Is.Approximately(new Vector2(100, 100)));
|
||||
Assert.That(gridA.Comp1!.ParentUid, Is.EqualTo(newMap));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load in the file containing gridB and its child
|
||||
await server.WaitPost(() => newMap = mapSys.CreateMap(out mapId));
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGrid(mapId, pathB, out _)));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(2));
|
||||
gridB = Find(nameof(gridB), entMan);
|
||||
child = Find(nameof(child), entMan);
|
||||
Assert.That(gridB.Comp1!.ParentUid, Is.EqualTo(newMap));
|
||||
Assert.That(child.Comp1!.ParentUid, Is.EqualTo(gridB.Owner));
|
||||
await server.WaitPost(() => mapSys.DeleteMap(mapId));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
|
||||
// Load the file that contains both of them.
|
||||
// This uses the generic loader, and should automatically create maps for both grids.
|
||||
LoadResult? result = null;
|
||||
var opts = MapLoadOptions.Default with
|
||||
{
|
||||
DeserializationOptions = DeserializationOptions.Default with {LogOrphanedGrids = false}
|
||||
};
|
||||
await server.WaitPost(() => Assert.That(loader.TryLoadGeneric(pathCombined, out result, opts)));
|
||||
Assert.That(result!.Category, Is.EqualTo(FileCategory.Unknown));
|
||||
Assert.That(result.Grids, Has.Count.EqualTo(2));
|
||||
Assert.That(result.Maps, Has.Count.EqualTo(2));
|
||||
Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(0));
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(3));
|
||||
gridA = Find(nameof(gridA), entMan);
|
||||
gridB = Find(nameof(gridB), entMan);
|
||||
child = Find(nameof(child), entMan);
|
||||
Assert.That(gridA.Comp1.LocalPosition, Is.Approximately(new Vector2(100, 100)));
|
||||
Assert.That(gridA.Comp1!.ParentUid, Is.Not.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(gridB.Comp1!.ParentUid, Is.Not.EqualTo(EntityUid.Invalid));
|
||||
Assert.That(child.Comp1!.ParentUid, Is.EqualTo(gridB.Owner));
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
foreach (var ent in result.Maps)
|
||||
{
|
||||
entMan.DeleteEntity(ent.Owner);
|
||||
}
|
||||
});
|
||||
Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.EntitySerialization;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class EntitySaveTestComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Give each entity a unique id to identify them across map saves & loads.
|
||||
/// </summary>
|
||||
[DataField] public string? Id;
|
||||
|
||||
[DataField] public EntityUid? Entity;
|
||||
|
||||
[DataField, AlwaysPushInheritance] public List<int> List = [];
|
||||
|
||||
/// <summary>
|
||||
/// Find an entity with a <see cref="EntitySaveTestComponent"/> with the matching id.
|
||||
/// </summary>
|
||||
public static Entity<TransformComponent, EntitySaveTestComponent> Find(string id, IEntityManager entMan)
|
||||
{
|
||||
var ents = entMan.AllEntities<EntitySaveTestComponent>();
|
||||
var matching = ents.Where(x => x.Comp.Id == id).ToArray();
|
||||
Assert.That(matching, Has.Length.EqualTo(1));
|
||||
return (matching[0].Owner, entMan.GetComponent<TransformComponent>(matching[0].Owner), matching[0].Comp);
|
||||
}
|
||||
|
||||
public static Entity<TransformComponent, EntitySaveTestComponent> Get(EntityUid uid, IEntityManager entMan)
|
||||
{
|
||||
return new Entity<TransformComponent, EntitySaveTestComponent>(
|
||||
uid,
|
||||
entMan.GetComponent<TransformComponent>(uid),
|
||||
entMan.EnsureComponent<EntitySaveTestComponent>(uid));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dummy tile definition for serializing grids.
|
||||
/// </summary>
|
||||
public sealed class TileDef(string id) : ITileDefinition
|
||||
{
|
||||
public ushort TileId { get; set; }
|
||||
public string Name => id;
|
||||
public string ID => id;
|
||||
public ResPath? Sprite => null;
|
||||
public Dictionary<Direction, ResPath> EdgeSprites => new();
|
||||
public int EdgeSpritePriority => 0;
|
||||
public float Friction => 0;
|
||||
public byte Variants => 0;
|
||||
public void AssignTileId(ushort id) => TileId = id;
|
||||
}
|
||||
@@ -7,10 +7,12 @@ using Robust.Client.Timing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
@@ -291,15 +293,16 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
await Task.WhenAll(server.WaitIdleAsync());
|
||||
|
||||
var sEntManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var mapSys = sEntManager.System<SharedMapSystem>();
|
||||
var sContainerSys = sEntManager.System<SharedContainerSystem>();
|
||||
var sMetadataSys = sEntManager.System<MetaDataSystem>();
|
||||
var path = new ResPath("container_test.yml");
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// build the map
|
||||
sEntManager.System<SharedMapSystem>().CreateMap(out var mapIdOne);
|
||||
Assert.That(mapManager.IsMapInitialized(mapIdOne), Is.True);
|
||||
Assert.That(mapSys.IsInitialized(mapIdOne), Is.True);
|
||||
|
||||
var containerEnt = sEntManager.SpawnEntity(null, new MapCoordinates(1, 1, mapIdOne));
|
||||
sMetadataSys.SetEntityName(containerEnt, "ContainerEnt");
|
||||
@@ -315,8 +318,8 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
// save the map
|
||||
var mapLoader = sEntManager.EntitySysManager.GetEntitySystem<MapLoaderSystem>();
|
||||
|
||||
mapLoader.SaveMap(mapIdOne, "container_test.yml");
|
||||
mapManager.DeleteMap(mapIdOne);
|
||||
Assert.That(mapLoader.TrySaveMap(mapIdOne, path));
|
||||
mapSys.DeleteMap(mapIdOne);
|
||||
});
|
||||
|
||||
// A few moments later...
|
||||
@@ -325,11 +328,10 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapLoader = sEntManager.System<MapLoaderSystem>();
|
||||
sEntManager.System<SharedMapSystem>().CreateMap(out var mapIdTwo);
|
||||
|
||||
// load the map
|
||||
mapLoader.Load(mapIdTwo, "container_test.yml");
|
||||
Assert.That(mapManager.IsMapInitialized(mapIdTwo), Is.True); // Map Initialize-ness is saved in the map file.
|
||||
Assert.That(mapLoader.TryLoadMap(path, out var map, out _));
|
||||
Assert.That(mapSys.IsInitialized(map), Is.True); // Map Initialize-ness is saved in the map file.
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
@@ -40,7 +40,6 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
|
||||
// Adds the map with id 1, and spawns entity 1 as the map entity.
|
||||
var testMapId = sim.CreateMap().MapId;
|
||||
var coords = new MapCoordinates(new Vector2(7, 7), testMapId);
|
||||
// Add grid 1, as the default grid to anchor things to.
|
||||
|
||||
@@ -108,8 +108,6 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
public void GetGridId_Map()
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
var mapEnt = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
|
||||
@@ -139,8 +137,6 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
public void GetMapId_Map()
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
var mapEnt = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers.Custom;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class TimeOffsetSerializerTest : RobustIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task SerializationTest()
|
||||
{
|
||||
var sim = StartServer();
|
||||
await sim.WaitIdleAsync();
|
||||
var serialization = sim.ResolveDependency<ISerializationManager>();
|
||||
var timing = sim.ResolveDependency<IGameTiming>();
|
||||
var entMan = sim.ResolveDependency<IEntityManager>();
|
||||
var ctx = new MapSerializationContext(entMan, timing);
|
||||
|
||||
await sim.WaitRunTicks(10);
|
||||
Assert.That(timing.CurTime.TotalSeconds, Is.GreaterThan(0));
|
||||
|
||||
// "pause" a map at this time
|
||||
var pauseTime = timing.CurTime;
|
||||
await sim.WaitRunTicks(10);
|
||||
|
||||
// Spawn a paused entity
|
||||
var uid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
var metaSys = entMan.System<MetaDataSystem>();
|
||||
metaSys.SetEntityPaused(uid, true);
|
||||
|
||||
await sim.WaitRunTicks(10);
|
||||
Assert.That(metaSys.GetPauseTime(uid).TotalSeconds, Is.GreaterThan(0));
|
||||
|
||||
var curTime = timing.CurTime;
|
||||
var dataTime = curTime + TimeSpan.FromSeconds(2);
|
||||
ctx.PauseTime = curTime - pauseTime;
|
||||
var entPauseDuration = metaSys.GetPauseTime(uid);
|
||||
|
||||
Assert.That(curTime.TotalSeconds, Is.GreaterThan(0));
|
||||
Assert.That(entPauseDuration.TotalSeconds, Is.GreaterThan(0));
|
||||
Assert.That(ctx.PauseTime.TotalSeconds, Is.GreaterThan(0));
|
||||
|
||||
Assert.That(ctx.PauseTime, Is.Not.EqualTo(curTime));
|
||||
Assert.That(ctx.PauseTime, Is.Not.EqualTo(entPauseDuration));
|
||||
Assert.That(entPauseDuration, Is.Not.EqualTo(curTime));
|
||||
|
||||
// time gets properly offset when reading a post-init map
|
||||
ctx.MapInitialized = true;
|
||||
var node = serialization.WriteValue<TimeSpan, TimeOffsetSerializer>(dataTime, context: ctx);
|
||||
var value = ((ValueDataNode) node).Value;
|
||||
var expected = (dataTime - curTime + ctx.PauseTime).TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||
Assert.That(value, Is.EqualTo(expected));
|
||||
|
||||
// When writing paused entities, it will instead use the entity's pause time:
|
||||
ctx.CurrentWritingEntity = uid;
|
||||
node = serialization.WriteValue<TimeSpan, TimeOffsetSerializer>(dataTime, context: ctx);
|
||||
value = ((ValueDataNode) node).Value;
|
||||
expected = (dataTime - curTime + entPauseDuration).TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||
Assert.That(value, Is.EqualTo(expected));
|
||||
|
||||
// Uninitialized maps always serialize as zero
|
||||
ctx.MapInitialized = false;
|
||||
node = serialization.WriteValue<TimeSpan, TimeOffsetSerializer>(dataTime, context: ctx);
|
||||
value = ((ValueDataNode) node).Value;
|
||||
Assert.That(value, Is.EqualTo("0"));
|
||||
|
||||
ctx.CurrentWritingEntity = null;
|
||||
node = serialization.WriteValue<TimeSpan, TimeOffsetSerializer>(dataTime, context: ctx);
|
||||
value = ((ValueDataNode) node).Value;
|
||||
Assert.That(value, Is.EqualTo("0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeserializationTest()
|
||||
{
|
||||
var sim = StartServer();
|
||||
await sim.WaitIdleAsync();
|
||||
|
||||
var serialization = sim.ResolveDependency<ISerializationManager>();
|
||||
|
||||
await sim.WaitRunTicks(10);
|
||||
|
||||
var timing = sim.ResolveDependency<IGameTiming>();
|
||||
var entMan = sim.ResolveDependency<IEntityManager>();
|
||||
var ctx = new MapSerializationContext(entMan, timing);
|
||||
var curTime = timing.CurTime;
|
||||
var node = new ValueDataNode("2");
|
||||
|
||||
// time gets properly offset when reading a post-init map
|
||||
ctx.MapInitialized = true;
|
||||
var time = serialization.Read<TimeSpan, ValueDataNode, TimeOffsetSerializer>(node, ctx);
|
||||
Assert.That(time, Is.EqualTo(curTime + TimeSpan.FromSeconds(2)));
|
||||
|
||||
// pre-init maps read time offsets as 0.
|
||||
ctx.MapInitialized = false;
|
||||
time = serialization.Read<TimeSpan, ValueDataNode, TimeOffsetSerializer>(node, ctx);
|
||||
Assert.That(time, Is.EqualTo(TimeSpan.Zero));
|
||||
|
||||
// Same goes for no-context reads
|
||||
time = serialization.Read<TimeSpan, ValueDataNode, TimeOffsetSerializer>(node);
|
||||
Assert.That(time, Is.EqualTo(TimeSpan.Zero));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user