#region Copyright & License Information /* * Copyright 2007-2021 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA { public interface ISuppressInitExport { } public class ActorReference : IEnumerable { public string Type; readonly Lazy initDict; internal TypeDictionary InitDict => initDict.Value; public ActorReference(string type) : this(type, new Dictionary()) { } public ActorReference(string type, Dictionary inits) { Type = type; initDict = Exts.Lazy(() => { var dict = new TypeDictionary(); foreach (var i in inits) { var init = LoadInit(i.Key, i.Value); if (init is ISingleInstanceInit && dict.Contains(init.GetType())) throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'"); dict.Add(init); } return dict; }); } public ActorReference(string type, TypeDictionary inits) { Type = type; initDict = new Lazy(() => { var dict = new TypeDictionary(); foreach (var i in inits) dict.Add(i); return dict; }); } static ActorInit LoadInit(string initName, MiniYaml initYaml) { var initInstance = initName.Split(ActorInfo.TraitInstanceSeparator); var type = Game.ModData.ObjectCreator.FindType(initInstance[0] + "Init"); if (type == null) throw new InvalidDataException($"Unknown initializer type '{initInstance[0]}Init'"); var init = (ActorInit)FormatterServices.GetUninitializedObject(type); if (initInstance.Length > 1) type.GetField("InstanceName").SetValue(init, initInstance[1]); var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) }); if (loader == null) throw new InvalidDataException($"{initInstance[0]}Init does not define a yaml-assignable type."); loader.Invoke(init, new[] { initYaml }); return init; } public MiniYaml Save(Func initFilter = null) { var ret = new MiniYaml(Type); foreach (var o in initDict.Value) { var init = o as ActorInit; if (init == null || o is ISuppressInitExport) continue; if (initFilter != null && !initFilter(init)) continue; var initTypeName = init.GetType().Name; var initName = initTypeName.Substring(0, initTypeName.Length - 4); if (!string.IsNullOrEmpty(init.InstanceName)) initName += ActorInfo.TraitInstanceSeparator + init.InstanceName; ret.Nodes.Add(new MiniYamlNode(initName, init.Save())); } return ret; } public IEnumerator GetEnumerator() { return initDict.Value.GetEnumerator(); } public ActorReference Clone() { var clone = new ActorReference(Type); foreach (var init in initDict.Value) clone.initDict.Value.Add(init); return clone; } public void Add(ActorInit init) { if (init is ISingleInstanceInit && InitDict.Contains(init.GetType())) throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'"); InitDict.Add(init); } public void Remove(ActorInit o) { initDict.Value.Remove(o); } public int RemoveAll() where T : ActorInit { var removed = 0; foreach (var o in initDict.Value.WithInterface().ToList()) { removed++; initDict.Value.Remove(o); } return removed; } public IEnumerable GetAll() where T : ActorInit { return initDict.Value.WithInterface(); } public T GetOrDefault(TraitInfo info) where T : ActorInit { var inits = initDict.Value.WithInterface(); // Traits tagged with an instance name prefer inits with the same name. // If a more specific init is not available, fall back to an unnamed init. // If duplicate inits are defined, take the last to match standard yaml override expectations if (info != null && !string.IsNullOrEmpty(info.InstanceName)) return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ?? inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName)); // Untagged traits will only use untagged inits return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName)); } public T Get(TraitInfo info) where T : ActorInit { var init = GetOrDefault(info); if (init == null) throw new InvalidOperationException($"TypeDictionary does not contain instance of type `{typeof(T)}`"); return init; } public U GetValue(TraitInfo info) where T : ValueActorInit { return Get(info).Value; } public U GetValue(TraitInfo info, U fallback) where T : ValueActorInit { var init = GetOrDefault(info); return init != null ? init.Value : fallback; } public bool Contains(TraitInfo info) where T : ActorInit { return GetOrDefault(info) != null; } public T GetOrDefault() where T : ActorInit, ISingleInstanceInit { return initDict.Value.GetOrDefault(); } public T Get() where T : ActorInit, ISingleInstanceInit { return initDict.Value.Get(); } public U GetValue() where T : ValueActorInit, ISingleInstanceInit { return Get().Value; } public U GetValue(U fallback) where T : ValueActorInit, ISingleInstanceInit { var init = GetOrDefault(); return init != null ? init.Value : fallback; } public bool Contains() where T : ActorInit, ISingleInstanceInit { return GetOrDefault() != null; } } }