diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
index f5ff3d3d64..188bd7823e 100644
--- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
+++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
@@ -566,6 +566,7 @@
+
@@ -611,4 +612,4 @@ copy "FuzzyLogicLibrary.dll" "$(SolutionDir)"
cd "$(SolutionDir)"
-
\ No newline at end of file
+
diff --git a/OpenRA.Mods.RA/Scripting/Global/ReinforcementsGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/ReinforcementsGlobal.cs
new file mode 100644
index 0000000000..c21b66a007
--- /dev/null
+++ b/OpenRA.Mods.RA/Scripting/Global/ReinforcementsGlobal.cs
@@ -0,0 +1,220 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2014 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. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using Eluant;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using OpenRA;
+using OpenRA.Primitives;
+using OpenRA.Traits;
+using OpenRA.Scripting;
+using OpenRA.Effects;
+using OpenRA.Mods.RA.Activities;
+using OpenRA.Mods.RA.Air;
+
+namespace OpenRA.Mods.RA.Scripting
+{
+ [ScriptGlobal("Reinforcements")]
+ public class ReinforcementsGlobal : ScriptGlobal
+ {
+ public ReinforcementsGlobal(ScriptContext context) : base(context) { }
+
+ Actor CreateActor(Player owner, string actorType, bool addToWorld, CPos? entryLocation = null, CPos? nextLocation = null)
+ {
+ ActorInfo ai;
+ if (!context.World.Map.Rules.Actors.TryGetValue(actorType, out ai))
+ throw new LuaException("Unknown actor type '{0}'".F(actorType));
+
+ var initDict = new TypeDictionary();
+
+ initDict.Add(new OwnerInit(owner));
+
+ if (entryLocation.HasValue)
+ {
+ var pi = ai.Traits.GetOrDefault();
+ initDict.Add(new CenterPositionInit(owner.World.Map.CenterOfCell(entryLocation.Value) + new WVec(0, 0, pi != null ? pi.CruiseAltitude.Range : 0)));
+ initDict.Add(new LocationInit(entryLocation.Value));
+ }
+
+ if (entryLocation.HasValue && nextLocation.HasValue)
+ initDict.Add(new FacingInit(context.World.Map.FacingBetween(CPos.Zero, CPos.Zero + (nextLocation.Value - entryLocation.Value), 0)));
+
+ var actor = context.World.CreateActor(addToWorld, actorType, initDict);
+
+ return actor;
+ }
+
+ void Move(Actor actor, CPos dest)
+ {
+ if (actor.HasTrait())
+ {
+ if (actor.HasTrait())
+ actor.QueueActivity(new HeliFly(actor, Target.FromCell(actor.World, dest)));
+ else
+ actor.QueueActivity(new Fly(actor, Target.FromCell(actor.World, dest)));
+ }
+ else
+ {
+ actor.QueueActivity(new Move.Move(dest, 2));
+ }
+ }
+
+ [Desc("Send reinforcements consisting of multiple units. Supports ground-based, naval and air units. " +
+ "The first member of the 'entryPath' array will be the units' spawnpoint, " +
+ "while the last one will be their destination. If 'actionFunc' is given, " +
+ "it will be executed once a unit has reached its destination. 'actionFunc' " +
+ "will be called as 'actionFunc(Actor actor)'")]
+ public LuaTable Reinforce(Player owner, LuaTable actorTypes, LuaTable entryPath, int interval = 25, LuaFunction actionFunc = null)
+ {
+ var actors = new List();
+ for (var i = 1; i <= actorTypes.Count; i++)
+ {
+ string actorType;
+ if (!(actorTypes[i].TryGetClrValue(out actorType)))
+ throw new LuaException("Invalid data in actorTypes array");
+
+ CPos entry, next = new CPos();
+ if (!(entryPath[1].TryGetClrValue(out entry)
+ && (entryPath.Count < 2 || entryPath[2].TryGetClrValue(out next))))
+ throw new LuaException("Invalid data in entryPath array");
+
+ var actor = CreateActor(owner, actorType, false, entry, entryPath.Count > 1 ? next : (CPos?)null);
+ actors.Add(actor);
+
+ var ep = entryPath.CopyReference() as LuaTable;
+ var af = actionFunc != null ? actionFunc.CopyReference() as LuaFunction : null;
+
+ var actionDelay = (i - 1) * interval;
+ Action actorAction = () =>
+ {
+ context.World.Add(actor);
+ for (var j = 2; j <= ep.Count; j++)
+ {
+ CPos wpt;
+ if (!(ep[j].TryGetClrValue(out wpt)))
+ throw new LuaException("Invalid data in entryPath array");
+
+ Move(actor, wpt);
+ }
+ ep.Dispose();
+
+ if (af != null)
+ actor.QueueActivity(new CallFunc(() =>
+ {
+ af.Call(actor.ToLuaValue(context));
+ af.Dispose();
+ }));
+ };
+
+ context.World.AddFrameEndTask(w => w.Add(new DelayedAction(actionDelay, actorAction)));
+ }
+ return actors.Select(a => a.ToLuaValue(context)).ToLuaTable(context);
+ }
+
+ [Desc("Send reinforcements in a transport. A transport can be a ground unit (APC etc.), ships and aircraft. " +
+ "The first member of the 'entryPath' array will be the spawnpoint for the transport, " +
+ "while the last one will be its destination. The last member of the 'exitPath' array " +
+ "is be the place where the transport will be removed from the game. When the transport " +
+ "has reached the destination, it will unload its cargo unless a custom 'actionFunc' has " +
+ "been supplied. Afterwards, the transport will follow the 'exitPath' and leave the map, " +
+ "unless a custom 'exitFunc' has been supplied. 'actionFunc' will be called as " +
+ "'actionFunc(Actor transport, Actor[] cargo). 'exitFunc' will be called as 'exitFunc(Actor transport)'.")]
+ public LuaTable ReinforceWithTransport(Player owner, string actorType, LuaTable cargoTypes, LuaTable entryPath, LuaTable exitPath = null,
+ LuaFunction actionFunc = null, LuaFunction exitFunc = null)
+ {
+ CPos entry, next = new CPos();
+ if (!(entryPath[1].TryGetClrValue(out entry)
+ && (entryPath.Count < 2 || entryPath[2].TryGetClrValue(out next))))
+ throw new LuaException("Invalid data in entryPath array");
+
+ var transport = CreateActor(owner, actorType, true, entry, entryPath.Count > 1 ? next : (CPos?)null);
+ var cargo = transport.TraitOrDefault();
+
+ var passengers = context.CreateTable();
+
+ if (cargo != null && cargoTypes != null)
+ {
+ for (var i = 1; i <= cargoTypes.Count; i++)
+ {
+ string cargoType;
+ if (!(cargoTypes [i].TryGetClrValue(out cargoType)))
+ throw new LuaException("Invalid data in cargoTypes array");
+
+ var passenger = CreateActor(owner, cargoType, false);
+ passengers.Add(passengers.Count + 1, passenger.ToLuaValue(context));
+ cargo.Load(transport, passenger);
+ }
+ }
+
+ for (var i = 2; i <= entryPath.Count; i++)
+ {
+ CPos wpt;
+ if (!(entryPath[i].TryGetClrValue(out wpt)))
+ throw new LuaException("Invalid data in entryPath array");
+
+ Move(transport, wpt);
+ }
+
+ if (actionFunc != null)
+ {
+ var af = actionFunc.CopyReference() as LuaFunction;
+ transport.QueueActivity(new CallFunc(() =>
+ {
+ af.Call(transport.ToLuaValue(context), passengers);
+ af.Dispose();
+ }));
+ }
+ else
+ {
+ var heli = transport.TraitOrDefault();
+ if (heli != null)
+ {
+ transport.QueueActivity(new Turn(heli.Info.InitialFacing));
+ transport.QueueActivity(new HeliLand(true));
+ transport.QueueActivity(new Wait(15));
+ }
+ if (cargo != null)
+ {
+ transport.QueueActivity(new UnloadCargo(transport, true));
+ transport.QueueActivity(new WaitFor(() => cargo.IsEmpty(transport)));
+ }
+ transport.QueueActivity(new Wait(heli != null ? 50 : 25));
+ }
+
+ if (exitFunc != null)
+ {
+ var ef = exitFunc.CopyReference() as LuaFunction;
+ transport.QueueActivity(new CallFunc(() =>
+ {
+ ef.Call(transport.ToLuaValue(context));
+ ef.Dispose();
+ }));
+ }
+ else if (exitPath != null)
+ {
+ for (var i = 1; i <= exitPath.Count; i++)
+ {
+ CPos wpt;
+ if (!(exitPath[i].TryGetClrValue(out wpt)))
+ throw new LuaException("Invalid data in exitPath array.");
+
+ Move(transport, wpt);
+ }
+ transport.QueueActivity(new RemoveSelf());
+ }
+
+ var ret = context.CreateTable();
+ ret.Add(1, transport.ToLuaValue(context));
+ ret.Add(2, passengers);
+ return ret;
+ }
+ }
+}
\ No newline at end of file