From c0da807ee76a9b86cef4d70de316c15c19e76610 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Wed, 15 Oct 2014 19:44:39 +0200 Subject: [PATCH] Add production support to the new Lua API --- .../Player/ClassicProductionQueue.cs | 2 +- .../Scripting/Global/TriggerGlobal.cs | 2 +- .../Properties/ProductionProperties.cs | 234 ++++++++++++++++++ OpenRA.Mods.RA/Scripting/ScriptTriggers.cs | 34 ++- 4 files changed, 267 insertions(+), 5 deletions(-) diff --git a/OpenRA.Mods.RA/Player/ClassicProductionQueue.cs b/OpenRA.Mods.RA/Player/ClassicProductionQueue.cs index 1a0dbd5452..242b29ef14 100644 --- a/OpenRA.Mods.RA/Player/ClassicProductionQueue.cs +++ b/OpenRA.Mods.RA/Player/ClassicProductionQueue.cs @@ -16,7 +16,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { - [Desc("Attach this to the world actor (not a building!) to define a new shared build queue.", + [Desc("Attach this to the player actor (not a building!) to define a new shared build queue.", "Will only work together with the Production: trait on the actor that actually does the production.", "You will also want to add PrimaryBuildings: to let the user choose where new units should exit.")] public class ClassicProductionQueueInfo : ProductionQueueInfo, Requires, Requires, Requires diff --git a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs index 08a2b5be90..4e8cdf98e2 100644 --- a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.RA.Scripting { public TriggerGlobal(ScriptContext context) : base(context) { } - static ScriptTriggers GetScriptTriggers(Actor a) + public static ScriptTriggers GetScriptTriggers(Actor a) { var events = a.TraitOrDefault(); if (events == null) diff --git a/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs index 5e923fbe73..ed47d59cfd 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs @@ -9,6 +9,10 @@ #endregion using Eluant; +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Mods.Common; using OpenRA.Mods.RA.Activities; using OpenRA.Scripting; using OpenRA.Traits; @@ -37,4 +41,234 @@ namespace OpenRA.Mods.RA.Scripting self.QueueActivity(new WaitFor(() => p.Produce(self, actorInfo, raceVariant))); } } + + [ScriptPropertyGroup("Production")] + public class RallyPointProperties : ScriptActorProperties, Requires + { + readonly RallyPoint rp; + + public RallyPointProperties(ScriptContext context, Actor self) + : base(context, self) + { + rp = self.Trait(); + } + + [Desc("Query or set a factory's rally point")] + public CPos RallyPoint + { + get { return rp.Location; } + set { rp.Location = value; } + } + } + + [ScriptPropertyGroup("Production")] + public class PrimaryBuildingProperties : ScriptActorProperties, Requires + { + readonly PrimaryBuilding pb; + + public PrimaryBuildingProperties(ScriptContext context, Actor self) + : base(context, self) + { + pb = self.Trait(); + } + + [Desc("Query or set the factory's primary building status")] + public bool IsPrimaryBuilding + { + get { return pb.IsPrimary; } + set { pb.SetPrimaryProducer(self, value); } + } + } + + [ScriptPropertyGroup("Production")] + public class ProductionQueueProperties : ScriptActorProperties, Requires, Requires + { + readonly List queues; + readonly ScriptTriggers triggers; + + public ProductionQueueProperties(ScriptContext context, Actor self) + : base(context, self) + { + queues = self.TraitsImplementing().Where(q => q.Enabled).ToList(); + triggers = TriggerGlobal.GetScriptTriggers(self); + } + + [Desc("Build the specified set of actors using a TD-style (per building) production queue. " + + "The function will return true if production could be started, false otherwise. " + + "If an actionFunc is given, it will be called as actionFunc(Actor[] actors) once " + + "production of all actors has been completed. The actors array is guaranteed to " + + "only contain alive actors.")] + public bool Build(string[] actorTypes, LuaFunction actionFunc = null) + { + if (triggers.Triggers[Trigger.OnProduction].Any()) + return false; + + var queue = queues.Where(q => actorTypes.All(t => GetBuildableInfo(t).Queue.Contains(q.Info.Type))) + .FirstOrDefault(q => q.CurrentItem() == null); + + if (queue == null) + return false; + + if (actionFunc != null) + { + var playerIndex = self.Owner.ClientIndex; + var squadSize = actorTypes.Length; + var squad = new List(); + var func = actionFunc.CopyReference() as LuaFunction; + + Action productionHandler = (_, __) => { }; + productionHandler = (factory, unit) => + { + if (playerIndex != factory.Owner.ClientIndex) + { + triggers.OnProducedInternal -= productionHandler; + return; + } + + squad.Add(unit); + if (squad.Count >= squadSize) + { + using (func) + using (var luaSquad = squad.Where(u => !u.IsDead()).ToArray().ToLuaValue(context)) + func.Call(luaSquad).Dispose(); + + triggers.OnProducedInternal -= productionHandler; + } + }; + + triggers.OnProducedInternal += productionHandler; + } + + foreach (var actorType in actorTypes) + queue.ResolveOrder(self, Order.StartProduction(self, actorType, 1)); + + return true; + } + + [Desc("Checks whether the factory is currently producing anything on the queue that produces this type of actor.")] + public bool IsProducing(string actorType) + { + if (triggers.Triggers[Trigger.OnProduction].Any()) + return true; + + return queues.Where(q => GetBuildableInfo(actorType).Queue.Contains(q.Info.Type)) + .Any(q => q.CurrentItem() != null); + } + + BuildableInfo GetBuildableInfo(string actorType) + { + var ri = self.World.Map.Rules.Actors[actorType]; + var bi = ri.Traits.GetOrDefault(); + + if (bi == null) + throw new LuaException("Actor of type {0} cannot be produced".F(actorType)); + else + return bi; + } + } + + [ScriptPropertyGroup("Production")] + public class ClassicProductionQueueProperties : ScriptPlayerProperties, Requires, Requires + { + readonly Dictionary> productionHandlers; + readonly Dictionary queues; + + public ClassicProductionQueueProperties(ScriptContext context, Player player) + : base(context, player) + { + productionHandlers = new Dictionary>(); + + queues = new Dictionary(); + foreach (var q in player.PlayerActor.TraitsImplementing().Where(q => q.Enabled)) + queues.Add(q.Info.Type, q); + + Action globalProductionHandler = (factory, unit) => + { + if (factory.Owner != player) + return; + + var queue = GetBuildableInfo(unit.Info.Name).Queue.First(); + + if (productionHandlers.ContainsKey(queue)) + productionHandlers[queue](factory, unit); + }; + + var triggers = TriggerGlobal.GetScriptTriggers(player.PlayerActor); + triggers.OnOtherProducedInternal += globalProductionHandler; + } + + [Desc("Build the specified set of actors using classic (RA-style) production queues. " + + "The function will return true if production could be started, false otherwise. " + + "If an actionFunc is given, it will be called as actionFunc(Actor[] actors) once " + + "production of all actors has been completed. The actors array is guaranteed to " + + "only contain alive actors.")] + public bool Build(string[] actorTypes, LuaFunction actionFunc = null) + { + var typeToQueueMap = new Dictionary(); + foreach (var actorType in actorTypes.Distinct()) + typeToQueueMap.Add(actorType, GetBuildableInfo(actorType).Queue.First()); + + var queueTypes = typeToQueueMap.Values.Distinct(); + + if (queueTypes.Any(t => !queues.ContainsKey(t) || productionHandlers.ContainsKey(t))) + return false; + + if (queueTypes.Any(t => queues[t].CurrentItem() != null)) + return false; + + if (actionFunc != null) + { + var squadSize = actorTypes.Length; + var squad = new List(); + var func = actionFunc.CopyReference() as LuaFunction; + + Action productionHandler = (factory, unit) => + { + squad.Add(unit); + if (squad.Count >= squadSize) + { + using (func) + using (var luaSquad = squad.Where(u => !u.IsDead()).ToArray().ToLuaValue(context)) + func.Call(luaSquad).Dispose(); + + foreach (var q in queueTypes) + productionHandlers.Remove(q); + } + }; + + foreach (var q in queueTypes) + productionHandlers.Add(q, productionHandler); + } + + foreach (var actorType in actorTypes) + { + var queue = queues[typeToQueueMap[actorType]]; + queue.ResolveOrder(queue.Actor, Order.StartProduction(queue.Actor, actorType, 1)); + } + + return true; + } + + [Desc("Checks whether the player is currently producing anything on the queue that produces this type of actor.")] + public bool IsProducing(string actorType) + { + var queue = GetBuildableInfo(actorType).Queue.First(); + + if (!queues.ContainsKey(queue)) + return true; + + return productionHandlers.ContainsKey(queue) || queues[queue].CurrentItem() != null; + } + + BuildableInfo GetBuildableInfo(string actorType) + { + var ri = player.World.Map.Rules.Actors[actorType]; + var bi = ri.Traits.GetOrDefault(); + + if (bi == null) + throw new LuaException("Actor of type {0} cannot be produced".F(actorType)); + else + return bi; + } + } } \ No newline at end of file diff --git a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs index 02d99e4eac..3c8926d707 100644 --- a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs +++ b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs @@ -18,8 +18,8 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Scripting { - public enum Trigger { OnIdle, OnDamaged, OnKilled, OnProduction, OnPlayerWon, OnPlayerLost, OnObjectiveAdded, - OnObjectiveCompleted, OnObjectiveFailed, OnCapture, OnAddedToWorld, OnRemovedFromWorld }; + public enum Trigger { OnIdle, OnDamaged, OnKilled, OnProduction, OnOtherProduction, OnPlayerWon, OnPlayerLost, + OnObjectiveAdded, OnObjectiveCompleted, OnObjectiveFailed, OnCapture, OnAddedToWorld, OnRemovedFromWorld }; [Desc("Allows map scripts to attach triggers to this actor via the Triggers global.")] public class ScriptTriggersInfo : ITraitInfo @@ -27,12 +27,14 @@ namespace OpenRA.Mods.RA.Scripting public object Create(ActorInitializer init) { return new ScriptTriggers(init.world); } } - public sealed class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction, INotifyObjectivesUpdated, INotifyCapture, INotifyAddedToWorld, INotifyRemovedFromWorld, IDisposable + public sealed class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction, INotifyOtherProduction, INotifyObjectivesUpdated, INotifyCapture, INotifyAddedToWorld, INotifyRemovedFromWorld, IDisposable { readonly World world; public event Action OnKilledInternal = _ => { }; public event Action OnRemovedInternal = _ => { }; + public event Action OnProducedInternal = (a, b) => { }; + public event Action OnOtherProducedInternal = (a, b) => { }; public Dictionary>> Triggers = new Dictionary>>(); @@ -108,6 +110,7 @@ namespace OpenRA.Mods.RA.Scripting public void UnitProduced(Actor self, Actor other, CPos exit) { + // Run Lua callbacks foreach (var f in Triggers[Trigger.OnProduction]) { try @@ -122,6 +125,9 @@ namespace OpenRA.Mods.RA.Scripting return; } } + + // Run any internally bound callbacks + OnProducedInternal(self, other); } public void OnPlayerWon(Player player) @@ -270,6 +276,28 @@ namespace OpenRA.Mods.RA.Scripting OnRemovedInternal(self); } + public void UnitProducedByOther(Actor self, Actor producee, Actor produced) + { + // Run Lua callbacks + foreach (var f in Triggers[Trigger.OnOtherProduction]) + { + try + { + using (var a = producee.ToLuaValue(f.Second)) + using (var b = produced.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + catch (Exception ex) + { + f.Second.FatalError(ex.Message); + return; + } + } + + // Run any internally bound callbacks + OnOtherProducedInternal(producee, produced); + } + public void Clear(Trigger trigger) { world.AddFrameEndTask(w =>