mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* Un-hardcode behavior to make a component not saved to map file. MapSaveId is a special component that can't be saved to map files due to a hardcoded type check. This behavior can now be applied to any component with [UnsavedComponent]. Moved "component registration" attributes into a single file because they don't deserve their own (poorly organized) .cs files. * Add ITileDefinitionManager.TryGetDefinition Try-pattern version of the existing indexers. * Preserve tile maps when saving maps This changes the map saver and loader code so that the "tilemap" can be preserved between map modifications as much as possible. The tile map from the loaded map gets stored onto MapSaveTileMapComponent components on all loaded grids. This tile map is then used when saving, meaning that changes to the engine's internal tile IDs do not cause diffs. Fixes #5000 * Changelog * Fix tests
174 lines
5.8 KiB
C#
174 lines
5.8 KiB
C#
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);
|
|
}
|
|
}
|