mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Renames SS14.* to Robust.* (#793)
RobustToolbox projects should be named Robust.* This PR changes the RobustToolbox projects from SS14.* to Robust.* Updates SS14.* prefixes/namespaces to Robust.* Updates SpaceStation14.sln to RobustToolbox.sln Updates MSBUILD/SS14.* to MSBUILD/Robust.* Updates CSProject and MSBuild references for the above Updates git_helper.py Removes Runserver and Runclient as they are unusable
This commit is contained in:
576
Robust.Shared/Prototypes/EntityPrototype.cs
Normal file
576
Robust.Shared/Prototypes/EntityPrototype.cs
Normal file
@@ -0,0 +1,576 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Prototype that represents game entities.
|
||||
/// </summary>
|
||||
[Prototype("entity")]
|
||||
public class EntityPrototype : IPrototype, IIndexedPrototype, ISyncingPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// The type string of this prototype used in files.
|
||||
/// </summary>
|
||||
public string TypeString { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The "in code name" of the object. Must be unique.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string ID { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The "in game name" of the object. What is displayed to most players.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of the object that shows upon using examine
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string Description { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, this object should not show up in the entity spawn panel.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Abstract { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of entity instantiated when a new entity is created from this template.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Type ClassType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The different mounting points on walls. (If any).
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public List<int> MountingPoints { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Placement mode used for client-initiated placement. This is used for admin and editor placement. The serverside version controls what type the server assigns in normal gameplay.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public string PlacementMode { get; protected set; } = "PlaceFree";
|
||||
|
||||
/// <summary>
|
||||
/// The Range this entity can be placed from. This is only used serverside since the server handles normal gameplay. The client uses unlimited range since it handles things like admin spawning and editing.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int PlacementRange { get; protected set; } = DEFAULT_RANGE;
|
||||
private const int DEFAULT_RANGE = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Set to hold snapping categories that this object has applied to it such as pipe/wire/wallmount
|
||||
/// </summary>
|
||||
private readonly HashSet<string> _snapFlags = new HashSet<string>();
|
||||
private bool _snapOverriden = false;
|
||||
|
||||
/// <summary>
|
||||
/// Offset that is added to the position when placing. (if any). Client only.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Vector2i PlacementOffset { get; protected set; }
|
||||
private bool _placementOverriden = false;
|
||||
|
||||
/// <summary>
|
||||
/// True if this entity will be saved by the map loader.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool MapSavable { get; protected set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype we inherit from.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public EntityPrototype Parent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of children inheriting from this prototype.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public List<EntityPrototype> Children { get; private set; }
|
||||
public bool IsRoot => Parent == null;
|
||||
|
||||
/// <summary>
|
||||
/// Used to store the parent id until we sync when all templates are done loading.
|
||||
/// </summary>
|
||||
private string parentTemp;
|
||||
|
||||
/// <summary>
|
||||
/// A dictionary mapping the component type list to the YAML mapping containing their settings.
|
||||
/// </summary>
|
||||
public Dictionary<string, YamlMappingNode> Components { get; } = new Dictionary<string, YamlMappingNode>();
|
||||
|
||||
/// <summary>
|
||||
/// The mapping node inside the <c>data</c> field of the prototype. Null if no data field exists.
|
||||
/// </summary>
|
||||
public YamlMappingNode DataNode { get; set; }
|
||||
|
||||
private readonly HashSet<Type> ReferenceTypes = new HashSet<Type>();
|
||||
|
||||
string CurrentDeserializingComponent;
|
||||
readonly Dictionary<string, Dictionary<(string, Type), object>> FieldCache = new Dictionary<string, Dictionary<(string, Type), object>>();
|
||||
readonly Dictionary<string, object> DataCache = new Dictionary<string, object>();
|
||||
|
||||
public EntityPrototype()
|
||||
{
|
||||
// Everybody gets a transform component!
|
||||
Components.Add("Transform", new YamlMappingNode());
|
||||
}
|
||||
|
||||
public void LoadFrom(YamlMappingNode mapping)
|
||||
{
|
||||
TypeString = mapping.GetNode("type").ToString();
|
||||
|
||||
ID = mapping.GetNode("id").AsString();
|
||||
|
||||
if (mapping.TryGetNode("name", out YamlNode node))
|
||||
{
|
||||
Name = node.AsString();
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("class", out node))
|
||||
{
|
||||
var manager = IoCManager.Resolve<IReflectionManager>();
|
||||
ClassType = manager.GetType(node.AsString());
|
||||
|
||||
if (ClassType == null)
|
||||
Logger.Error($"[ENG] Prototype \"{ID}\" - Cannot find class type \"{node.AsString()}\"!");
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("parent", out node))
|
||||
{
|
||||
parentTemp = node.AsString();
|
||||
}
|
||||
|
||||
// DESCRIPTION
|
||||
if (mapping.TryGetNode("description", out node))
|
||||
{
|
||||
Description = node.AsString();
|
||||
}
|
||||
|
||||
// COMPONENTS
|
||||
if (mapping.TryGetNode<YamlSequenceNode>("components", out var componentsequence))
|
||||
{
|
||||
var factory = IoCManager.Resolve<IComponentFactory>();
|
||||
foreach (var componentMapping in componentsequence.Cast<YamlMappingNode>())
|
||||
{
|
||||
ReadComponent(componentMapping, factory);
|
||||
}
|
||||
|
||||
// Assert that there are no conflicting component references.
|
||||
foreach (var componentName in Components.Keys)
|
||||
{
|
||||
var registration = factory.GetRegistration(componentName);
|
||||
foreach (var type in registration.References)
|
||||
{
|
||||
if (ReferenceTypes.Contains(type))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate component reference in prototype: '{type}'");
|
||||
}
|
||||
ReferenceTypes.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DATA FIELD
|
||||
if (mapping.TryGetNode<YamlMappingNode>("data", out var dataMapping))
|
||||
{
|
||||
DataNode = dataMapping;
|
||||
}
|
||||
|
||||
// PLACEMENT
|
||||
// TODO: move to a component or something. Shouldn't be a root part of prototypes IMO.
|
||||
if (mapping.TryGetNode<YamlMappingNode>("placement", out var placementMapping))
|
||||
{
|
||||
ReadPlacementProperties(placementMapping);
|
||||
}
|
||||
|
||||
// SAVING
|
||||
if (mapping.TryGetNode("save", out node))
|
||||
{
|
||||
MapSavable = node.AsBool();
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("abstract", out node))
|
||||
{
|
||||
Abstract = node.AsBool();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadPlacementProperties(YamlMappingNode mapping)
|
||||
{
|
||||
if (mapping.TryGetNode("mode", out YamlNode node))
|
||||
{
|
||||
PlacementMode = node.AsString();
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("offset", out node))
|
||||
{
|
||||
PlacementOffset = node.AsVector2i();
|
||||
_placementOverriden = true;
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode<YamlSequenceNode>("nodes", out var sequence))
|
||||
{
|
||||
MountingPoints = sequence.Select(p => p.AsInt()).ToList();
|
||||
}
|
||||
|
||||
if (mapping.TryGetNode("range", out node))
|
||||
{
|
||||
PlacementRange = node.AsInt();
|
||||
}
|
||||
|
||||
// Reads snapping flags that this object holds that describe its properties to such as wire/pipe/wallmount, used to prevent certain stacked placement
|
||||
if (mapping.TryGetNode<YamlSequenceNode>("snap", out var snapSequence))
|
||||
{
|
||||
var flagsList = snapSequence.Select(p => p.AsString());
|
||||
foreach (var flag in flagsList)
|
||||
{
|
||||
_snapFlags.Add(flag);
|
||||
}
|
||||
_snapOverriden = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve inheritance.
|
||||
public bool Sync(IPrototypeManager manager, int stage)
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case 0:
|
||||
if (parentTemp == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Parent = manager.Index<EntityPrototype>(parentTemp);
|
||||
if (Parent.Children == null)
|
||||
{
|
||||
Parent.Children = new List<EntityPrototype>();
|
||||
}
|
||||
Parent.Children.Add(this);
|
||||
return false;
|
||||
|
||||
case 1:
|
||||
// We are a root-level prototype.
|
||||
// As such we're getting the duty of pushing inheritance into everybody's face.
|
||||
// Can't do a "puller" system where each queries the parent because it requires n stages
|
||||
// (n being the depth of each inheritance tree)
|
||||
|
||||
if (Children == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
PushInheritanceAll();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iteratively pushes inheritance down to all children, children's children, etc. breadth-first.
|
||||
/// </summary>
|
||||
private void PushInheritanceAll()
|
||||
{
|
||||
if (Children == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceTargets = new List<(EntityPrototype, List<EntityPrototype>)> {(this, Children)};
|
||||
var newSources = new List<EntityPrototype>();
|
||||
while (true)
|
||||
{
|
||||
foreach (var (source, targetList) in sourceTargets)
|
||||
{
|
||||
if (targetList == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var target in targetList)
|
||||
{
|
||||
PushInheritance(source, target);
|
||||
}
|
||||
newSources.AddRange(targetList);
|
||||
}
|
||||
|
||||
if (newSources.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
sourceTargets.Clear();
|
||||
foreach (var newSource in newSources)
|
||||
{
|
||||
sourceTargets.Add((newSource, newSource.Children));
|
||||
}
|
||||
newSources.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void PushInheritance(EntityPrototype source, EntityPrototype target)
|
||||
{
|
||||
// Copy component data over.
|
||||
foreach (KeyValuePair<string, YamlMappingNode> component in source.Components)
|
||||
{
|
||||
if (target.Components.TryGetValue(component.Key, out YamlMappingNode targetComponent))
|
||||
{
|
||||
// Copy over values the target component does not have.
|
||||
foreach (YamlNode key in component.Value.Children.Keys)
|
||||
{
|
||||
if (!targetComponent.Children.ContainsKey(key))
|
||||
{
|
||||
targetComponent.Children[key] = component.Value[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy component into the target, since it doesn't have it yet.
|
||||
// Unless it'd cause a conflict.
|
||||
var factory = IoCManager.Resolve<IComponentFactory>();
|
||||
foreach (var refType in factory.GetRegistration(component.Key).References)
|
||||
{
|
||||
if (target.ReferenceTypes.Contains(refType))
|
||||
{
|
||||
// yeah nope the child's got it!! NEXT!!
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
target.Components[component.Key] = new YamlMappingNode(component.Value.AsEnumerable());
|
||||
}
|
||||
next:;
|
||||
}
|
||||
|
||||
// Copy all simple data over.
|
||||
if (!target._placementOverriden)
|
||||
{
|
||||
target.PlacementMode = source.PlacementMode;
|
||||
}
|
||||
|
||||
if (!target._placementOverriden)
|
||||
{
|
||||
target.PlacementOffset = source.PlacementOffset;
|
||||
}
|
||||
|
||||
if (target.MountingPoints == null && source.MountingPoints != null)
|
||||
{
|
||||
target.MountingPoints = new List<int>(source.MountingPoints);
|
||||
}
|
||||
|
||||
if (target.PlacementRange == DEFAULT_RANGE)
|
||||
{
|
||||
target.PlacementRange = source.PlacementRange;
|
||||
}
|
||||
|
||||
if (!target._snapOverriden)
|
||||
{
|
||||
foreach (var flag in source._snapFlags)
|
||||
{
|
||||
target._snapFlags.Add(flag);
|
||||
}
|
||||
}
|
||||
|
||||
if (target.Name == null)
|
||||
{
|
||||
target.Name = source.Name;
|
||||
}
|
||||
|
||||
if (target.ClassType == null)
|
||||
{
|
||||
target.ClassType = source.ClassType;
|
||||
}
|
||||
|
||||
if (target.Children == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
internal Entity AllocEntity(EntityUid uid, IEntityManager manager)
|
||||
{
|
||||
var entity = (Entity)Activator.CreateInstance(ClassType ?? typeof(Entity));
|
||||
|
||||
entity.SetManagers(manager);
|
||||
entity.SetUid(uid);
|
||||
entity.Prototype = this;
|
||||
entity.Name = Name;
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
internal void LoadEntity(Entity entity, IComponentFactory factory, IEntityLoadContext context)
|
||||
{
|
||||
YamlObjectSerializer.Context defaultContext = null;
|
||||
if (context == null)
|
||||
{
|
||||
defaultContext = new PrototypeSerializationContext(this);
|
||||
}
|
||||
foreach (var (name, data) in Components)
|
||||
{
|
||||
var component = (Component)factory.GetComponent(name);
|
||||
|
||||
component.Owner = entity;
|
||||
ObjectSerializer ser;
|
||||
if (context != null)
|
||||
{
|
||||
ser = context.GetComponentSerializer(name, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentDeserializingComponent = name;
|
||||
ser = YamlObjectSerializer.NewReader(data, defaultContext);
|
||||
}
|
||||
component.ExposeData(ser);
|
||||
|
||||
entity.AddComponent(component);
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// This is for map loading.
|
||||
// Components that have been ADDED NEW need to be handled too.
|
||||
foreach (var name in context.GetExtraComponentTypes())
|
||||
{
|
||||
if (Components.ContainsKey(name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var component = (Component)factory.GetComponent(name);
|
||||
component.Owner = entity;
|
||||
var ser = context.GetComponentSerializer(name, null);
|
||||
component.ExposeData(ser);
|
||||
|
||||
entity.AddComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether the prototype is going to conflict with anything previously placed in this location on the snap grid
|
||||
/// </summary>
|
||||
public bool CanSpawnAt(IMapGrid grid, Vector2 position)
|
||||
{
|
||||
if (!grid.OnSnapCenter(position) && !grid.OnSnapBorder(position)) //We only check snap position logic at this time, extensible for other behavior
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var entitymanager = IoCManager.Resolve<IEntityManager>();
|
||||
var entities = entitymanager.GetEntitiesAt(position);
|
||||
return !entities.SelectMany(e => e.Prototype._snapFlags).Any(f => _snapFlags.Contains(f));
|
||||
}
|
||||
|
||||
private void ReadComponent(YamlMappingNode mapping, IComponentFactory factory)
|
||||
{
|
||||
string type = mapping.GetNode("type").AsString();
|
||||
// See if type exists to detect errors.
|
||||
switch (factory.GetComponentAvailability(type))
|
||||
{
|
||||
case ComponentAvailability.Available:
|
||||
break;
|
||||
|
||||
case ComponentAvailability.Ignore:
|
||||
return;
|
||||
|
||||
case ComponentAvailability.Unknown:
|
||||
Log.Logger.Error($"Unknown component '{type}' in prototype {ID}!");
|
||||
return;
|
||||
}
|
||||
|
||||
var copy = new YamlMappingNode(mapping.AsEnumerable());
|
||||
// TODO: figure out a better way to exclude the type node.
|
||||
// Also maybe deep copy this? Right now it's pretty error prone.
|
||||
copy.Children.Remove(new YamlScalarNode("type"));
|
||||
|
||||
Components[type] = copy;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"EntityPrototype({ID})";
|
||||
}
|
||||
|
||||
class PrototypeSerializationContext : YamlObjectSerializer.Context
|
||||
{
|
||||
readonly EntityPrototype prototype;
|
||||
|
||||
public PrototypeSerializationContext(EntityPrototype owner)
|
||||
{
|
||||
prototype = owner;
|
||||
}
|
||||
|
||||
public override void SetCachedField<T>(string field, T value)
|
||||
{
|
||||
if (StackDepth != 0)
|
||||
{
|
||||
base.SetCachedField<T>(field, value);
|
||||
return;
|
||||
}
|
||||
if (!prototype.FieldCache.TryGetValue(prototype.CurrentDeserializingComponent, out var fieldList))
|
||||
{
|
||||
fieldList = new Dictionary<(string, Type), object>();
|
||||
prototype.FieldCache[prototype.CurrentDeserializingComponent] = fieldList;
|
||||
}
|
||||
|
||||
fieldList[(field, typeof(T))] = value;
|
||||
}
|
||||
|
||||
public override bool TryGetCachedField<T>(string field, out T value)
|
||||
{
|
||||
if (StackDepth != 0)
|
||||
{
|
||||
return base.TryGetCachedField<T>(field, out value);
|
||||
}
|
||||
if (prototype.FieldCache.TryGetValue(prototype.CurrentDeserializingComponent, out var dict))
|
||||
{
|
||||
if (dict.TryGetValue((field, typeof(T)), out var theValue))
|
||||
{
|
||||
value = (T)theValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void SetDataCache(string field, object value)
|
||||
{
|
||||
if (StackDepth != 0)
|
||||
{
|
||||
base.SetDataCache(field, value);
|
||||
return;
|
||||
}
|
||||
prototype.DataCache[field] = value;
|
||||
}
|
||||
|
||||
public override bool TryGetDataCache(string field, out object value)
|
||||
{
|
||||
if (StackDepth != 0)
|
||||
{
|
||||
return base.TryGetDataCache(field, out value);
|
||||
}
|
||||
return prototype.DataCache.TryGetValue(field, out value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Robust.Shared/Prototypes/IPrototype.cs
Normal file
51
Robust.Shared/Prototypes/IPrototype.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// An IPrototype is a prototype that can be loaded from the global YAML prototypes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this, the prototype must be accessible through IoC with <see cref="IoCTargetAttribute"/>
|
||||
/// and it must have a <see cref="PrototypeAttribute"/> to give it a type string.
|
||||
/// </remarks>
|
||||
public interface IPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// Load data from the YAML mappings in the prototype files.
|
||||
/// </summary>
|
||||
void LoadFrom(YamlMappingNode mapping);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension on <see cref="IPrototype"/> that allows it to be "indexed" by a string ID.
|
||||
/// </summary>
|
||||
public interface IIndexedPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// An ID for this prototype instance.
|
||||
/// If this is a duplicate, an error will be thrown.
|
||||
/// </summary>
|
||||
string ID { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension on <see cref="IPrototype"/> that allows "syncing" between prototypes after all prototypes have done initial loading.
|
||||
/// To resolve reference like the entity prototype parenting.
|
||||
/// </summary>
|
||||
public interface ISyncingPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// Sync and update cross-referencing data.
|
||||
/// Syncing works in stages, each time it will be called with the stage it's currently on.
|
||||
/// Each prototype will be called in a stage, then the stage count goes up.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The order of syncing is in no way guaranteed to be consistent across stages.
|
||||
/// This means that on stage 1 prototype A might sync first, but on stage 2 prototype B might.
|
||||
/// </remarks>
|
||||
/// <param name="stage">The current sync stage.</param>
|
||||
/// <returns>Whether or not the prototype will be included in the next sync stage</returns>
|
||||
bool Sync(IPrototypeManager manager, int stage);
|
||||
}
|
||||
}
|
||||
376
Robust.Shared/Prototypes/PrototypeManager.cs
Normal file
376
Robust.Shared/Prototypes/PrototypeManager.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using Robust.Shared.Interfaces.Reflection;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.IoC.Exceptions;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces;
|
||||
using Robust.Shared.Log;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Resources;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Handle storage and loading of YAML prototypes.
|
||||
/// </summary>
|
||||
public interface IPrototypeManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype;
|
||||
/// <summary>
|
||||
/// Return an IEnumerable to iterate all prototypes of a certain type.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IIndexedPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the type of prototype is not registered.
|
||||
/// </exception>
|
||||
T Index<T>(string id) where T : class, IIndexedPrototype;
|
||||
/// <summary>
|
||||
/// Index for a <see cref="IIndexedPrototype"/> by ID.
|
||||
/// </summary>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// Thrown if the ID does not exist or the type of prototype is not registered.
|
||||
/// </exception>
|
||||
IIndexedPrototype Index(Type type, string id);
|
||||
bool HasIndex<T>(string id) where T : IIndexedPrototype;
|
||||
bool TryIndex<T>(string id, out T prototype) where T : IIndexedPrototype;
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
/// </summary>
|
||||
void LoadDirectory(ResourcePath path);
|
||||
void LoadFromStream(TextReader stream);
|
||||
/// <summary>
|
||||
/// Clear out all prototypes and reset to a blank slate.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
/// <summary>
|
||||
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
|
||||
/// </summary>
|
||||
void Resync();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a specific prototype name to be ignored.
|
||||
/// </summary>
|
||||
void RegisterIgnore(string name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quick attribute to give the prototype its type string.
|
||||
/// To prevent needing to instantiate it because interfaces can't declare statics.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
[BaseTypeRequired(typeof(IPrototype))]
|
||||
public class PrototypeAttribute : Attribute
|
||||
{
|
||||
private readonly string type;
|
||||
public string Type => type;
|
||||
public PrototypeAttribute(string type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
public class PrototypeManager : IPrototypeManager, IPostInjectInit
|
||||
{
|
||||
[Dependency]
|
||||
private readonly IReflectionManager ReflectionManager;
|
||||
private readonly Dictionary<string, Type> prototypeTypes = new Dictionary<string, Type>();
|
||||
|
||||
[Dependency]
|
||||
private readonly IResourceManager _resources;
|
||||
|
||||
private bool _hasEverBeenReloaded;
|
||||
|
||||
#region IPrototypeManager members
|
||||
private readonly Dictionary<Type, List<IPrototype>> prototypes = new Dictionary<Type, List<IPrototype>>();
|
||||
private readonly Dictionary<Type, Dictionary<string, IIndexedPrototype>> indexedPrototypes = new Dictionary<Type, Dictionary<string, IIndexedPrototype>>();
|
||||
|
||||
private readonly HashSet<string> IgnoredPrototypeTypes = new HashSet<string>();
|
||||
|
||||
public IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
{
|
||||
throw new InvalidOperationException("No prototypes have been loaded yet.");
|
||||
}
|
||||
return prototypes[typeof(T)].Select((IPrototype p) => (T)p);
|
||||
}
|
||||
|
||||
public IEnumerable<IPrototype> EnumeratePrototypes(Type type)
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
{
|
||||
throw new InvalidOperationException("No prototypes have been loaded yet.");
|
||||
}
|
||||
return prototypes[type];
|
||||
}
|
||||
|
||||
public T Index<T>(string id) where T : class, IIndexedPrototype
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
{
|
||||
throw new InvalidOperationException("No prototypes have been loaded yet.");
|
||||
}
|
||||
try
|
||||
{
|
||||
return (T)indexedPrototypes[typeof(T)][id];
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
throw new UnknownPrototypeException(id);
|
||||
}
|
||||
}
|
||||
|
||||
public IIndexedPrototype Index(Type type, string id)
|
||||
{
|
||||
if (!_hasEverBeenReloaded)
|
||||
{
|
||||
throw new InvalidOperationException("No prototypes have been loaded yet.");
|
||||
}
|
||||
return indexedPrototypes[type][id];
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
prototypes.Clear();
|
||||
prototypeTypes.Clear();
|
||||
indexedPrototypes.Clear();
|
||||
}
|
||||
|
||||
public void Resync()
|
||||
{
|
||||
foreach (Type type in prototypeTypes.Values.Where(t => typeof(ISyncingPrototype).IsAssignableFrom(t)))
|
||||
{
|
||||
// This list is the list of prototypes we're syncing.
|
||||
// Iterate using indices.
|
||||
// IF the prototype wants to NOT by synced again,
|
||||
// Swap remove it with the one at the end of the list,
|
||||
// and do the whole thing again with the one formerly at the end of the list
|
||||
// otherwise keep it and move up an index
|
||||
// When we get to the end, do the whole thing again!
|
||||
// Yes this is ridiculously overengineered BUT IT PERFORMS WELL.
|
||||
// I hope.
|
||||
List<ISyncingPrototype> currentRun = prototypes[type].Select(p => (ISyncingPrototype)p).ToList();
|
||||
int stage = 0;
|
||||
// Outer loop to iterate stages.
|
||||
while (currentRun.Count > 0)
|
||||
{
|
||||
// Increase positions to iterate over list.
|
||||
// If we need to stick, i gets reduced down below.
|
||||
for (int i = 0; i < currentRun.Count; i++)
|
||||
{
|
||||
ISyncingPrototype prototype = currentRun[i];
|
||||
bool result = prototype.Sync(this, stage);
|
||||
// Keep prototype and move on to next one if it returns true.
|
||||
// Thus it stays in the list for next stage.
|
||||
if (result)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move the last element in the list to where we are currently.
|
||||
// Since we don't break we'll do this one next, as i stays the same.
|
||||
// (for loop cancels out decrement here)
|
||||
currentRun.RemoveSwap(i);
|
||||
i--;
|
||||
}
|
||||
stage++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadDirectory(ResourcePath path)
|
||||
{
|
||||
foreach (var filePath in _resources.ContentFindFiles(path))
|
||||
{
|
||||
using (var reader = new StreamReader(_resources.ContentFileRead(filePath), EncodingHelpers.UTF8))
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadFromStream(reader);
|
||||
}
|
||||
catch (Exception e)
|
||||
when (e is YamlException || e is PrototypeLoadException)
|
||||
{
|
||||
Logger.Error($"[ENG] Exception whilst loading prototypes from {filePath}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFromStream(TextReader stream)
|
||||
{
|
||||
_hasEverBeenReloaded = true;
|
||||
var yaml = new YamlStream();
|
||||
yaml.Load(stream);
|
||||
|
||||
for (int i = 0; i < yaml.Documents.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadFromDocument(yaml.Documents[i]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new PrototypeLoadException(string.Format("Failed to load prototypes from document#{0}", i), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion IPrototypeManager members
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
ReflectionManager.OnAssemblyAdded += (_, __) => ReloadPrototypeTypes();
|
||||
ReloadPrototypeTypes();
|
||||
}
|
||||
|
||||
private void ReloadPrototypeTypes()
|
||||
{
|
||||
Clear();
|
||||
foreach (var type in ReflectionManager.GetAllChildren<IPrototype>())
|
||||
{
|
||||
var attribute = (PrototypeAttribute)Attribute.GetCustomAttribute(type, typeof(PrototypeAttribute));
|
||||
if (attribute == null)
|
||||
{
|
||||
throw new InvalidImplementationException(type, typeof(IPrototype), "No " + nameof(PrototypeAttribute) + " to give it a type string.");
|
||||
}
|
||||
|
||||
if (prototypeTypes.ContainsKey(attribute.Type))
|
||||
{
|
||||
throw new InvalidImplementationException(type, typeof(IPrototype), string.Format("Duplicate prototype type ID: {0}. Current: {1}", attribute.Type, prototypeTypes[attribute.Type]));
|
||||
}
|
||||
|
||||
prototypeTypes[attribute.Type] = type;
|
||||
prototypes[type] = new List<IPrototype>();
|
||||
if (typeof(IIndexedPrototype).IsAssignableFrom(type))
|
||||
{
|
||||
indexedPrototypes[type] = new Dictionary<string, IIndexedPrototype>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadFromDocument(YamlDocument document)
|
||||
{
|
||||
var rootNode = (YamlSequenceNode)document.RootNode;
|
||||
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
|
||||
{
|
||||
var type = node.GetNode("type").AsString();
|
||||
if (!prototypeTypes.ContainsKey(type))
|
||||
{
|
||||
if (IgnoredPrototypeTypes.Contains(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
throw new PrototypeLoadException(string.Format("Unknown prototype type: '{0}'", type));
|
||||
}
|
||||
|
||||
var prototypeType = prototypeTypes[type];
|
||||
var prototype = (IPrototype)Activator.CreateInstance(prototypeType);
|
||||
prototype.LoadFrom(node);
|
||||
prototypes[prototypeType].Add(prototype);
|
||||
var indexedPrototype = prototype as IIndexedPrototype;
|
||||
if (indexedPrototype != null)
|
||||
{
|
||||
var id = indexedPrototype.ID;
|
||||
if (indexedPrototypes[prototypeType].ContainsKey(id))
|
||||
{
|
||||
throw new PrototypeLoadException(string.Format("Duplicate ID: '{0}'", id));
|
||||
}
|
||||
indexedPrototypes[prototypeType][id] = (IIndexedPrototype)prototype;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasIndex<T>(string id) where T : IIndexedPrototype
|
||||
{
|
||||
if (!indexedPrototypes.TryGetValue(typeof(T), out var index))
|
||||
{
|
||||
throw new UnknownPrototypeException(id);
|
||||
}
|
||||
return index.ContainsKey(id);
|
||||
}
|
||||
|
||||
public bool TryIndex<T>(string id, out T prototype) where T : IIndexedPrototype
|
||||
{
|
||||
if (!indexedPrototypes.TryGetValue(typeof(T), out var index))
|
||||
{
|
||||
throw new UnknownPrototypeException(id);
|
||||
}
|
||||
var returned = index.TryGetValue(id, out var uncast);
|
||||
prototype = (T)uncast;
|
||||
return returned;
|
||||
}
|
||||
|
||||
public void RegisterIgnore(string name)
|
||||
{
|
||||
IgnoredPrototypeTypes.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class PrototypeLoadException : Exception
|
||||
{
|
||||
public PrototypeLoadException()
|
||||
{
|
||||
}
|
||||
public PrototypeLoadException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
public PrototypeLoadException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
public PrototypeLoadException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UnknownPrototypeException : Exception
|
||||
{
|
||||
public override string Message => "Unknown prototype: " + Prototype;
|
||||
public readonly string Prototype;
|
||||
public UnknownPrototypeException(string prototype)
|
||||
{
|
||||
Prototype = prototype;
|
||||
}
|
||||
|
||||
public UnknownPrototypeException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
Prototype = (string)info.GetValue("prototype", typeof(string));
|
||||
}
|
||||
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
base.GetObjectData(info, context);
|
||||
info.AddValue("prototype", Prototype, typeof(string));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user