mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
320 lines
11 KiB
C#
320 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
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;
|
|
|
|
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 YAML 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 file,
|
|
[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;
|
|
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
|
return false;
|
|
|
|
using (reader)
|
|
{
|
|
return TryLoadMap(reader, file.ToString(), out map, out grids, options, offset, rot);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to load a YAML stream 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(
|
|
TextReader reader,
|
|
string source,
|
|
[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(reader, source, 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 YAML 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 file,
|
|
[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;
|
|
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
|
return false;
|
|
|
|
using (reader)
|
|
{
|
|
return TryLoadMapWithId(mapId, reader, file.ToString(), out map, out grids, options, offset, rot);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to load a YAML text stream 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,
|
|
TextReader reader,
|
|
string source,
|
|
[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(reader, source, 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 YAML text stream 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 file,
|
|
[NotNullWhen(true)] out HashSet<Entity<MapGridComponent>>? grids,
|
|
DeserializationOptions? options = null,
|
|
Vector2 offset = default,
|
|
Angle rot = default)
|
|
{
|
|
grids = null;
|
|
if (!TryGetReader(file.ToRootedPath(), out var reader))
|
|
return false;
|
|
|
|
using (reader)
|
|
{
|
|
return TryMergeMap(mapId, reader, file.ToString(), out grids, options, offset, rot);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to load a YAML 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,
|
|
TextReader reader,
|
|
string source,
|
|
[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(reader, source, 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;
|
|
}
|
|
}
|