diff --git a/OpenRA.Game/Map/ActorInitializer.cs b/OpenRA.Game/Map/ActorInitializer.cs index 5012907617..cf8fb0114a 100644 --- a/OpenRA.Game/Map/ActorInitializer.cs +++ b/OpenRA.Game/Map/ActorInitializer.cs @@ -86,7 +86,7 @@ namespace OpenRA * The object will be allocated directly then the best matching Initialize() method will be called to set valid state. * - ActorReference will always attempt to call Initialize(MiniYaml). ActorGlobal will use whichever one it first * finds with an argument type that matches the given LuaValue. - * - Most ActorInits will want to inherit ValueActorInit which hides the low-level plumbing. + * - Most ActorInits will want to inherit either ValueActorInit or CompositeActorInit which hide the low-level plumbing. * - Inits that reference actors should use ActorInitActorReference which allows actors to be referenced by name in map.yaml */ public abstract class ActorInit @@ -134,6 +134,54 @@ namespace OpenRA } } + public abstract class CompositeActorInit : ActorInit + { + protected CompositeActorInit(TraitInfo info) + : base(info.InstanceName) { } + + protected CompositeActorInit() + : base() { } + + public virtual void Initialize(MiniYaml yaml) + { + FieldLoader.Load(this, yaml); + } + + public virtual void Initialize(Dictionary values) + { + object value; + foreach (var field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + var sa = field.GetCustomAttributes(false).DefaultIfEmpty(FieldLoader.SerializeAttribute.Default).First(); + if (!sa.Serialize) + continue; + + if (values.TryGetValue(field.Name, out value)) + field.SetValue(this, value); + } + } + + public virtual Dictionary InitializeArgs() + { + var dict = new Dictionary(); + foreach (var field in GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + var sa = field.GetCustomAttributes(false).DefaultIfEmpty(FieldLoader.SerializeAttribute.Default).First(); + if (!sa.Serialize) + continue; + + dict[field.Name] = field.FieldType; + } + + return dict; + } + + public override MiniYaml Save() + { + return FieldSaver.Save(this); + } + } + public class LocationInit : ValueActorInit { public LocationInit(TraitInfo info, CPos value) diff --git a/OpenRA.Mods.Cnc/Traits/Chronoshiftable.cs b/OpenRA.Mods.Cnc/Traits/Chronoshiftable.cs index 6ec09f14c0..890ec6583d 100644 --- a/OpenRA.Mods.Cnc/Traits/Chronoshiftable.cs +++ b/OpenRA.Mods.Cnc/Traits/Chronoshiftable.cs @@ -9,7 +9,6 @@ */ #endregion -using System; using OpenRA.Mods.Cnc.Activities; using OpenRA.Mods.Common; using OpenRA.Mods.Common.Activities; @@ -60,14 +59,18 @@ namespace OpenRA.Mods.Cnc.Traits : base(info) { self = init.Self; - ReturnTicks = init.GetValue(info, 0); - duration = init.GetValue(info, 0); - Origin = init.GetValue(info, CPos.Zero); - // Defer to the end of tick as the lazy value may reference an actor that hasn't been created yet - var chronosphereInit = init.GetOrDefault(info); - if (chronosphereInit != null) - init.World.AddFrameEndTask(w => chronosphere = chronosphereInit.Value.Actor(init.World).Value); + var returnInit = init.GetOrDefault(info); + if (returnInit != null) + { + ReturnTicks = returnInit.Ticks; + duration = returnInit.Duration; + Origin = returnInit.Origin; + + // Defer to the end of tick as the lazy value may reference an actor that hasn't been created yet + if (returnInit.Chronosphere != null) + init.World.AddFrameEndTask(w => chronosphere = returnInit.Chronosphere.Actor(init.World).Value); + } } void ITick.Tick(Actor self) @@ -91,7 +94,7 @@ namespace OpenRA.Mods.Cnc.Traits typeof(Actor).GetProperty("CurrentActivity").SetValue(self, null); // The actor is killed using Info.DamageTypes if the teleport fails - self.QueueActivity(false, new Teleport(chronosphere, Origin, null, true, killCargo, Info.ChronoshiftSound, + self.QueueActivity(false, new Teleport(chronosphere ?? self, Origin, null, true, killCargo, Info.ChronoshiftSound, false, true, Info.DamageTypes)); } } @@ -166,38 +169,26 @@ namespace OpenRA.Mods.Cnc.Traits if (IsTraitDisabled || !Info.ReturnToOrigin || ReturnTicks <= 0) return; - init.Add(new ChronoshiftOriginInit(Origin)); - init.Add(new ChronoshiftReturnInit(ReturnTicks)); - init.Add(new ChronoshiftDurationInit(duration)); - if (chronosphere != self) - init.Add(new ChronoshiftChronosphereInit(chronosphere)); + init.Add(new ChronoshiftReturnInit(ReturnTicks, duration, Origin, chronosphere)); } void IDeathActorInitModifier.ModifyDeathActorInit(Actor self, TypeDictionary init) { ModifyActorInit(init); } void ITransformActorInitModifier.ModifyTransformActorInit(Actor self, TypeDictionary init) { ModifyActorInit(init); } } - public class ChronoshiftReturnInit : ValueActorInit + public class ChronoshiftReturnInit : CompositeActorInit { - public ChronoshiftReturnInit(int value) - : base(value) { } - } + public readonly int Ticks; + public readonly int Duration; + public readonly CPos Origin; + public readonly ActorInitActorReference Chronosphere; - public class ChronoshiftDurationInit : ValueActorInit - { - public ChronoshiftDurationInit(int value) - : base(value) { } - } - - public class ChronoshiftOriginInit : ValueActorInit - { - public ChronoshiftOriginInit(CPos value) - : base(value) { } - } - - public class ChronoshiftChronosphereInit : ValueActorInit - { - public ChronoshiftChronosphereInit(Actor value) - : base(value) { } + public ChronoshiftReturnInit(int ticks, int duration, CPos origin, Actor chronosphere) + { + Ticks = ticks; + Duration = duration; + Origin = origin; + Chronosphere = chronosphere; + } } } diff --git a/OpenRA.Mods.Cnc/Traits/ConyardChronoReturn.cs b/OpenRA.Mods.Cnc/Traits/ConyardChronoReturn.cs index d140d4bf98..4ea89117c3 100644 --- a/OpenRA.Mods.Cnc/Traits/ConyardChronoReturn.cs +++ b/OpenRA.Mods.Cnc/Traits/ConyardChronoReturn.cs @@ -96,14 +96,18 @@ namespace OpenRA.Mods.Cnc.Traits wsb = self.TraitsImplementing().Single(w => w.Info.Name == info.Body); faction = init.GetValue(info, self.Owner.Faction.InternalName); - returnTicks = init.GetValue(info, 0); - duration = init.GetValue(info, 0); - origin = init.GetValue(info, CPos.Zero); - // Defer to the end of tick as the lazy value may reference an actor that hasn't been created yet - var chronosphereInit = init.GetOrDefault(info); - if (chronosphereInit != null) - init.World.AddFrameEndTask(w => chronosphere = chronosphereInit.Value.Actor(init.World).Value); + var returnInit = init.GetOrDefault(info); + if (returnInit != null) + { + returnTicks = returnInit.Ticks; + duration = returnInit.Duration; + origin = returnInit.Origin; + + // Defer to the end of tick as the lazy value may reference an actor that hasn't been created yet + if (returnInit.Chronosphere != null) + init.World.AddFrameEndTask(w => chronosphere = returnInit.Chronosphere.Actor(init.World).Value); + } } IEnumerable IObservesVariables.GetVariableObservers() @@ -228,11 +232,7 @@ namespace OpenRA.Mods.Cnc.Traits if (returnTicks <= 0) return; - init.Add(new ChronoshiftOriginInit(origin)); - init.Add(new ChronoshiftReturnInit(returnTicks)); - init.Add(new ChronoshiftDurationInit(duration)); - if (chronosphere != self) - init.Add(new ChronoshiftChronosphereInit(chronosphere)); + init.Add(new ChronoshiftReturnInit(returnTicks, duration, origin, chronosphere)); } void IDeathActorInitModifier.ModifyDeathActorInit(Actor self, TypeDictionary init) { ModifyActorInit(init); } diff --git a/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs index 28e91362fb..6b0b039bd5 100644 --- a/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs @@ -40,6 +40,41 @@ namespace OpenRA.Mods.Common.Scripting if (initInstance.Length > 1) initType.GetField("InstanceName").SetValue(init, initInstance[1]); + var compositeInit = init as CompositeActorInit; + var tableValue = value as LuaTable; + if (tableValue != null && compositeInit != null) + { + var args = compositeInit.InitializeArgs(); + var initValues = new Dictionary(); + foreach (var kv in tableValue) + { + using (kv.Key) + using (kv.Value) + { + var key = kv.Key.ToString(); + Type type; + if (!args.TryGetValue(key, out type)) + throw new LuaException("Unknown initializer type '{0}.{1}'".F(initInstance[0], key)); + + object clrValue; + var isActorReference = type == typeof(ActorInitActorReference); + if (isActorReference) + type = kv.Value is LuaString ? typeof(string) : typeof(Actor); + + if (!kv.Value.TryGetClrValue(type, out clrValue)) + throw new LuaException("Invalid data type for '{0}.{1}' (expected {2}, got {3})".F(initInstance[0], key, type.Name, kv.Value.WrappedClrType())); + + if (isActorReference) + clrValue = type == typeof(string) ? new ActorInitActorReference((string)clrValue) : new ActorInitActorReference((Actor)clrValue); + + initValues[key] = clrValue; + } + } + + compositeInit.Initialize(initValues); + return init; + } + var initializers = initType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(m => m.Name == "Initialize" && m.GetParameters().Length == 1);