mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Vector3, Vector4, Matrix4, and Quaternion are now gone. Use System.Numerics instead. This commit is just replacing usages, cleaning up using declarations, and moving over the (couple) helpers that are actually important.
305 lines
11 KiB
C#
305 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.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;
|
|
|
|
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, taking in a raw byte stream.
|
|
/// </summary>
|
|
/// <param name="file">The file contents to load from.</param>
|
|
/// <param name="fileName">
|
|
/// The name of the file being loaded. This is used purely for logging/informational purposes.
|
|
/// </param>
|
|
/// <param name="result">The result of the load operation.</param>
|
|
/// <param name="options">Options for the load operation.</param>
|
|
/// <returns>True if the load succeeded, false otherwise.</returns>
|
|
/// <seealso cref="M:Robust.Shared.EntitySerialization.Systems.MapLoaderSystem.TryLoadGeneric(Robust.Shared.Utility.ResPath,Robust.Shared.EntitySerialization.LoadResult@,System.Nullable{Robust.Shared.EntitySerialization.MapLoadOptions})"/>
|
|
public bool TryLoadGeneric(
|
|
Stream file,
|
|
string fileName,
|
|
[NotNullWhen(true)] out LoadResult? result,
|
|
MapLoadOptions? options = null)
|
|
{
|
|
result = null;
|
|
|
|
if (!TryReadFile(new StreamReader(file), out var data))
|
|
return false;
|
|
|
|
return TryLoadGeneric(data, fileName, out result, options);
|
|
}
|
|
|
|
/// <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;
|
|
|
|
return TryLoadGeneric(data, file.ToString(), out result, options);
|
|
}
|
|
|
|
private bool TryLoadGeneric(
|
|
MappingDataNode data,
|
|
string fileName,
|
|
[NotNullWhen(true)] out LoadResult? result,
|
|
MapLoadOptions? options = null)
|
|
{
|
|
result = null;
|
|
|
|
_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 {fileName}");
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
deserializer.CreateEntities();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error($"Caught exception while creating entities for map {fileName}: {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($"Map {fileName} 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to load a grid entity from a file and parent it to a newly created map.
|
|
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
|
/// </summary>
|
|
public bool TryLoadGrid(
|
|
ResPath path,
|
|
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
|
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
|
DeserializationOptions? options = null,
|
|
Vector2 offset = default,
|
|
Angle rot = default)
|
|
{
|
|
var opts = options ?? DeserializationOptions.Default;
|
|
|
|
var mapUid = _mapSystem.CreateMap(out var mapId, runMapInit: opts.InitializeMaps);
|
|
if (opts.PauseMaps)
|
|
_mapSystem.SetPaused(mapUid, true);
|
|
|
|
if (!TryLoadGrid(mapId, path, out grid, options, offset, rot))
|
|
{
|
|
Del(mapUid);
|
|
map = null;
|
|
return false;
|
|
}
|
|
|
|
map = new(mapUid, Comp<MapComponent>(mapUid));
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|