diff --git a/OpenRA.Game/Scripting/ScriptActorInterface.cs b/OpenRA.Game/Scripting/ScriptActorInterface.cs index 047f903849..c750850fa8 100644 --- a/OpenRA.Game/Scripting/ScriptActorInterface.cs +++ b/OpenRA.Game/Scripting/ScriptActorInterface.cs @@ -25,10 +25,10 @@ namespace OpenRA.Scripting { this.actor = actor; - var args = new [] { actor }; + var args = new object[] { context, actor }; var objects = context.ActorCommands[actor.Info].Select(cg => { - var groupCtor = cg.GetConstructor(new Type[] { typeof(Actor) }); + var groupCtor = cg.GetConstructor(new Type[] { typeof(ScriptContext), typeof(Actor) }); return groupCtor.Invoke(args); }); diff --git a/OpenRA.Game/Scripting/ScriptContext.cs b/OpenRA.Game/Scripting/ScriptContext.cs index b947f2237f..d737adfb02 100644 --- a/OpenRA.Game/Scripting/ScriptContext.cs +++ b/OpenRA.Game/Scripting/ScriptContext.cs @@ -44,13 +44,23 @@ namespace OpenRA.Scripting public abstract class ScriptActorProperties { protected readonly Actor self; - public ScriptActorProperties(Actor self) { this.self = self; } + protected readonly ScriptContext context; + public ScriptActorProperties(ScriptContext context, Actor self) + { + this.self = self; + this.context = context; + } } public abstract class ScriptPlayerProperties { protected readonly Player player; - public ScriptPlayerProperties(Player player) { this.player = player; } + protected readonly ScriptContext context; + public ScriptPlayerProperties(ScriptContext context, Player player) + { + this.player = player; + this.context = context; + } } // For global-level bindings diff --git a/OpenRA.Game/Scripting/ScriptPlayerInterface.cs b/OpenRA.Game/Scripting/ScriptPlayerInterface.cs index 4cc418ec12..8593b12262 100644 --- a/OpenRA.Game/Scripting/ScriptPlayerInterface.cs +++ b/OpenRA.Game/Scripting/ScriptPlayerInterface.cs @@ -25,10 +25,10 @@ namespace OpenRA.Scripting { this.player = player; - var args = new [] { player }; + var args = new object[] { context, player }; var objects = context.PlayerCommands.Select(cg => { - var groupCtor = cg.GetConstructor(new Type[] { typeof(Player) }); + var groupCtor = cg.GetConstructor(new Type[] { typeof(ScriptContext), typeof(Player) }); return groupCtor.Invoke(args); }); diff --git a/OpenRA.Mods.RA/ConquestVictoryConditions.cs b/OpenRA.Mods.RA/ConquestVictoryConditions.cs index 8e37a578ea..00c871ecaf 100644 --- a/OpenRA.Mods.RA/ConquestVictoryConditions.cs +++ b/OpenRA.Mods.RA/ConquestVictoryConditions.cs @@ -52,12 +52,6 @@ namespace OpenRA.Mods.RA mo.MarkCompleted(self.Owner, objectiveID); } - public void ResolveOrder(Actor self, Order order) - { - if (order.OrderString == "Surrender") - mo.MarkFailed(self.Owner, objectiveID); - } - public void OnPlayerLost(Player player) { Game.Debug("{0} is defeated.".F(player.PlayerName)); 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/Player/MissionObjectives.cs b/OpenRA.Mods.RA/Player/MissionObjectives.cs index 8c907495eb..bdaf011718 100644 --- a/OpenRA.Mods.RA/Player/MissionObjectives.cs +++ b/OpenRA.Mods.RA/Player/MissionObjectives.cs @@ -79,8 +79,8 @@ namespace OpenRA.Mods.RA objectives.Insert(newID, new MissionObjective(type, description)); ObjectiveAdded(player); - foreach (var imo in player.PlayerActor.TraitsImplementing()) - imo.OnObjectiveAdded(player, newID); + foreach (var inou in player.PlayerActor.TraitsImplementing()) + inou.OnObjectiveAdded(player, newID); return newID; } @@ -90,22 +90,23 @@ namespace OpenRA.Mods.RA if (objectiveID >= objectives.Count || objectives[objectiveID].State == ObjectiveState.Completed) return; + var inous = player.PlayerActor.TraitsImplementing(); + objectives[objectiveID].State = ObjectiveState.Completed; + foreach (var inou in inous) + inou.OnObjectiveCompleted(player, objectiveID); if (objectives[objectiveID].Type == ObjectiveType.Primary) { var playerWon = objectives.Where(o => o.Type == ObjectiveType.Primary).All(o => o.State == ObjectiveState.Completed); - foreach (var imo in player.PlayerActor.TraitsImplementing()) - { - imo.OnObjectiveCompleted(player, objectiveID); - - if (playerWon) - imo.OnPlayerWon(player); - } - if (playerWon) + { + foreach (var inou in inous) + inou.OnPlayerWon(player); + CheckIfGameIsOver(player); + } } } @@ -114,89 +115,118 @@ namespace OpenRA.Mods.RA if (objectiveID >= objectives.Count || objectives[objectiveID].State == ObjectiveState.Failed) return; + var inous = player.PlayerActor.TraitsImplementing(); + objectives[objectiveID].State = ObjectiveState.Failed; + foreach (var inou in inous) + inou.OnObjectiveFailed(player, objectiveID); if (objectives[objectiveID].Type == ObjectiveType.Primary) { var playerLost = objectives.Where(o => o.Type == ObjectiveType.Primary).Any(o => o.State == ObjectiveState.Failed); - foreach (var imo in player.PlayerActor.TraitsImplementing()) - { - imo.OnObjectiveFailed(player, objectiveID); - - if (playerLost) - imo.OnPlayerLost(player); - } - if (playerLost) + { + foreach (var inou in inous) + inou.OnPlayerLost(player); + CheckIfGameIsOver(player); + } } } void CheckIfGameIsOver(Player player) { var players = player.World.Players.Where(p => !p.NonCombatant); - var allies = players.Where(p => p.IsAlliedWith(player)); - - var gameOver = ((info.EarlyGameOver && !info.Cooperative && player.WinState != WinState.Undefined) || - (info.EarlyGameOver && info.Cooperative && allies.All(p => p.WinState != WinState.Undefined)) || - players.All(p => p.WinState != WinState.Undefined)); + var gameOver = players.All(p => p.WinState != WinState.Undefined); if (gameOver) - { Game.RunAfterDelay(info.GameOverDelay, () => { player.World.EndGame(); player.World.SetPauseState(true); player.World.PauseStateLocked = true; }); - } } public void OnPlayerWon(Player player) { + var players = player.World.Players.Where(p => !p.NonCombatant); + var enemies = players.Where(p => !p.IsAlliedWith(player)); + if (info.Cooperative) { WinStateCooperative = WinState.Won; - var players = player.World.Players.Where(p => !p.NonCombatant); var allies = players.Where(p => p.IsAlliedWith(player)); if (allies.All(p => p.PlayerActor.Trait().WinStateCooperative == WinState.Won)) + { foreach (var p in allies) { p.WinState = WinState.Won; p.World.OnPlayerWinStateChanged(p); } + + if (info.EarlyGameOver) + foreach (var p in enemies) + p.PlayerActor.Trait().ForceDefeat(p); + } } else { player.WinState = WinState.Won; player.World.OnPlayerWinStateChanged(player); + + if (info.EarlyGameOver) + foreach (var p in enemies) + p.PlayerActor.Trait().ForceDefeat(p); } } public void OnPlayerLost(Player player) { + var players = player.World.Players.Where(p => !p.NonCombatant); + var enemies = players.Where(p => !p.IsAlliedWith(player)); + if (info.Cooperative) { WinStateCooperative = WinState.Lost; - var players = player.World.Players.Where(p => !p.NonCombatant); var allies = players.Where(p => p.IsAlliedWith(player)); if (allies.Any(p => p.PlayerActor.Trait().WinStateCooperative == WinState.Lost)) + { foreach (var p in allies) { p.WinState = WinState.Lost; p.World.OnPlayerWinStateChanged(p); } + + if (info.EarlyGameOver) + foreach (var p in enemies) + p.PlayerActor.Trait().ForceDefeat(p); + } } else { player.WinState = WinState.Lost; player.World.OnPlayerWinStateChanged(player); + + if (info.EarlyGameOver) + foreach (var p in enemies) + { + p.WinState = WinState.Won; + p.World.OnPlayerWinStateChanged(p); + } } } + public void ForceDefeat(Player player) + { + for (var id = 0; id < Objectives.Count; id++) + if (Objectives[id].State == ObjectiveState.Incomplete) + MarkFailed(player, id); + } + public event Action ObjectiveAdded = player => { }; public void OnObjectiveAdded(Player player, int id) {} @@ -206,8 +236,7 @@ namespace OpenRA.Mods.RA public void ResolveOrder(Actor self, Order order) { if (order.OrderString == "Surrender") - for (var id = 0; id < objectives.Count; id++) - MarkFailed(self.Owner, id); + ForceDefeat(self.Owner); } } diff --git a/OpenRA.Mods.RA/Scripting/Global/CoordinateGlobals.cs b/OpenRA.Mods.RA/Scripting/Global/CoordinateGlobals.cs index dcc1763c22..f4c1775456 100644 --- a/OpenRA.Mods.RA/Scripting/Global/CoordinateGlobals.cs +++ b/OpenRA.Mods.RA/Scripting/Global/CoordinateGlobals.cs @@ -65,5 +65,8 @@ namespace OpenRA.Scripting [Desc("Create a new WRange.")] public WRange New(int r) { return new WRange(r); } + + [Desc("Create a new WRange by cell distance")] + public WRange FromCells(int numCells) { return WRange.FromCells(numCells); } } } diff --git a/OpenRA.Mods.RA/Scripting/Global/MapGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/MapGlobal.cs index 34c61859f0..d7d45366f7 100644 --- a/OpenRA.Mods.RA/Scripting/Global/MapGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/MapGlobal.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Linq; using Eluant; using OpenRA.Scripting; @@ -43,6 +44,34 @@ namespace OpenRA.Mods.RA.Scripting return actors.ToLuaTable(context); } + [Desc("Returns a table of all actors within the requested rectangle, filtered using the specified function.")] + public LuaTable ActorsInBox(WPos topLeft, WPos bottomRight, LuaFunction filter = null) + { + var actors = context.World.ActorMap.ActorsInBox(topLeft, bottomRight) + .Select(a => a.ToLuaValue(context)); + + if (filter != null) + actors = actors.Where(a => + { + using (var f = filter.Call(a)) + return f.First().ToBoolean(); + }); + + return actors.ToLuaTable(context); + } + + [Desc("Returns the location of the top-left corner of the map.")] + public WPos TopLeft + { + get { return new WPos(context.World.Map.Bounds.Left * 1024, context.World.Map.Bounds.Top * 1024, 0); } + } + + [Desc("Returns the location of the bottom-right corner of the map.")] + public WPos BottomRight + { + get { return new WPos(context.World.Map.Bounds.Right * 1024, context.World.Map.Bounds.Bottom * 1024, 0); } + } + [Desc("Returns a random cell inside the visible region of the map.")] public CPos RandomCell() { 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 diff --git a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs index a2dca58344..4fd96055d1 100644 --- a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs @@ -144,6 +144,27 @@ namespace OpenRA.Mods.RA.Scripting GetScriptTriggers(player.PlayerActor).RegisterCallback(Trigger.OnObjectiveFailed, func, context); } + [Desc("Call a function when this actor is added to the world. " + + "The callback function will be called as func(Actor self).")] + public void OnAddedToWorld(Actor a, LuaFunction func) + { + GetScriptTriggers(a).RegisterCallback(Trigger.OnAddedToWorld, func, context); + } + + [Desc("Call a function when this actor is removed from the world. " + + "The callback function will be called as func(Actor self).")] + public void OnRemovedFromWorld(Actor a, LuaFunction func) + { + GetScriptTriggers(a).RegisterCallback(Trigger.OnRemovedFromWorld, func, context); + } + + [Desc("Call a function when this actor is captured. The callback function " + + "will be called as func(Actor self, Actor captor, Player oldOwner, Player newOwner).")] + public void OnCapture(Actor a, LuaFunction func) + { + GetScriptTriggers(a).RegisterCallback(Trigger.OnCapture, func, context); + } + [Desc("Removes all triggers from this actor")] public void ClearAll(Actor a) { diff --git a/OpenRA.Mods.RA/Scripting/Global/UtilsGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/UtilsGlobal.cs index cb330e43cb..95b9c089e3 100644 --- a/OpenRA.Mods.RA/Scripting/Global/UtilsGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/UtilsGlobal.cs @@ -58,6 +58,17 @@ namespace OpenRA.Mods.RA.Scripting return true; } + [Desc("Skips over the first numElements members of the array and returns the rest")] + public LuaTable Skip(LuaTable table, int numElements) + { + var t = context.CreateTable(); + + for (var i = numElements; i <= table.Count; i++) + t.Add(t.Count + 1, table[i]); + + return t; + } + [Desc("Returns a random value from table.")] public LuaValue Random(LuaTable table) { @@ -94,5 +105,17 @@ namespace OpenRA.Mods.RA.Scripting { return context.World.Map.CenterOfCell(cell); } + + [Desc("Converts the number of seconds into game time (ticks).")] + public int Seconds(int seconds) + { + return seconds * 25; + } + + [Desc("Converts the number of minutes into game time (ticks).")] + public int Minutes(int minutes) + { + return Seconds(minutes * 60); + } } } diff --git a/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs index f0257dd320..66b46aa92e 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs @@ -17,8 +17,8 @@ namespace OpenRA.Mods.RA.Scripting [ScriptPropertyGroup("Support Powers")] public class ChronsphereProperties : ScriptActorProperties, Requires { - public ChronsphereProperties(Actor self) - : base(self) { } + public ChronsphereProperties(ScriptContext context, Actor self) + : base(context, self) { } [Desc("Chronoshift a group of actors. A duration of 0 will teleport the actors permanently.")] public void Chronoshift(LuaTable unitLocationPairs, int duration = 0, bool killCargo = false) diff --git a/OpenRA.Mods.RA/Scripting/Properties/CombatProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/CombatProperties.cs index 46a678bb88..20b9fad366 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/CombatProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/CombatProperties.cs @@ -17,7 +17,8 @@ namespace OpenRA.Mods.RA.Scripting [ScriptPropertyGroup("Combat")] public class CombatProperties : ScriptActorProperties, Requires, Requires { - public CombatProperties(Actor self) : base(self) { } + public CombatProperties(ScriptContext context, Actor self) + : base(context, self) { } [ScriptActorPropertyActivity] [Desc("Seek out and attack nearby targets.")] @@ -35,4 +36,4 @@ namespace OpenRA.Mods.RA.Scripting self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Move.Move(cell, WRange.FromCells(closeEnough)))); } } -} \ No newline at end of file +} diff --git a/OpenRA.Mods.RA/Scripting/Properties/GeneralProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/GeneralProperties.cs index 0ab242684b..ca49084cc2 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/GeneralProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/GeneralProperties.cs @@ -21,7 +21,8 @@ namespace OpenRA.Mods.RA.Scripting readonly IFacing facing; readonly AutoTarget autotarget; - public GeneralProperties(Actor self) : base(self) + public GeneralProperties(ScriptContext context, Actor self) + : base(context, self) { facing = self.TraitOrDefault(); autotarget = self.TraitOrDefault(); @@ -135,4 +136,4 @@ namespace OpenRA.Mods.RA.Scripting return self.HasScriptProperty(name); } } -} \ No newline at end of file +} diff --git a/OpenRA.Mods.RA/Scripting/Properties/GuardProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/GuardProperties.cs index 30f6c8b6dd..fd1e744b98 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/GuardProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/GuardProperties.cs @@ -17,8 +17,8 @@ namespace OpenRA.Mods.RA.Scripting public class GuardProperties : ScriptActorProperties, Requires, Requires { Guard guard; - public GuardProperties(Actor self) - : base(self) + public GuardProperties(ScriptContext context, Actor self) + : base(context, self) { guard = self.Trait(); } diff --git a/OpenRA.Mods.RA/Scripting/Properties/HealthProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/HealthProperties.cs index 27620af598..fc80b5762e 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/HealthProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/HealthProperties.cs @@ -17,8 +17,8 @@ namespace OpenRA.Mods.RA.Scripting public class HealthProperties : ScriptActorProperties, Requires { Health health; - public HealthProperties(Actor self) - : base(self) + public HealthProperties(ScriptContext context, Actor self) + : base(context, self) { health = self.Trait(); } @@ -41,8 +41,8 @@ namespace OpenRA.Mods.RA.Scripting public class InvulnerableProperties : ScriptActorProperties, Requires { ScriptInvulnerable invulnerable; - public InvulnerableProperties(Actor self) - : base(self) + public InvulnerableProperties(ScriptContext context, Actor self) + : base(context, self) { invulnerable = self.Trait(); } diff --git a/OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs index b11a6b6af4..89a14e9b14 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/MissionObjectiveProperties.cs @@ -20,8 +20,8 @@ namespace OpenRA.Mods.RA.Scripting { readonly MissionObjectives mo; - public MissionObjectiveProperties(Player player) - : base(player) + public MissionObjectiveProperties(ScriptContext context, Player player) + : base(context, player) { mo = player.PlayerActor.Trait(); } diff --git a/OpenRA.Mods.RA/Scripting/Properties/MobileProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/MobileProperties.cs index fd990dec55..511aad91ff 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/MobileProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/MobileProperties.cs @@ -17,7 +17,8 @@ namespace OpenRA.Mods.RA.Scripting [ScriptPropertyGroup("Movement")] public class MobileProperties : ScriptActorProperties, Requires { - public MobileProperties(Actor self) : base(self) { } + public MobileProperties(ScriptContext context, Actor self) + : base(context, self) { } [ScriptActorPropertyActivity] [Desc("Moves within the cell grid. closeEnough defines an optional range " + diff --git a/OpenRA.Mods.RA/Scripting/Properties/PlayerProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/PlayerProperties.cs index 538b611c1f..ee1e52a064 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/PlayerProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/PlayerProperties.cs @@ -9,22 +9,28 @@ #endregion using System; +using System.Linq; +using Eluant; using OpenRA.Scripting; +using OpenRA.Mods.RA.Move; namespace OpenRA.Mods.RA.Scripting { [ScriptPropertyGroup("Player")] public class PlayerProperties : ScriptPlayerProperties { - readonly Player p; - - public PlayerProperties(Player player) - : base(player) - { - p = player; - } + public PlayerProperties(ScriptContext context, Player player) + : base(context, player) { } [Desc("The player's name.")] - public string PlayerName { get { return p.PlayerName; } } + public string Name { get { return player.PlayerName; } } + + [Desc("Returns an array of actors representing all ground attack units of this player.")] + public LuaTable GetGroundAttackers() + { + return player.World.ActorsWithTrait().Select(a => a.Actor) + .Where(a => a.Owner == player && !a.IsDead() && a.IsInWorld && a.HasTrait()) + .Select(a => a.ToLuaValue(context)).ToLuaTable(context); + } } } diff --git a/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs index d51c43bdd8..6b8596b6a1 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/ProductionProperties.cs @@ -20,8 +20,8 @@ namespace OpenRA.Mods.RA.Scripting { readonly Production p; - public ProductionProperties(Actor self) - : base(self) + public ProductionProperties(ScriptContext context, Actor self) + : base(context, self) { p = self.Trait(); } diff --git a/OpenRA.Mods.RA/Scripting/Properties/ResourceProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/ResourceProperties.cs index bfe54bda15..377d0bce17 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/ResourceProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/ResourceProperties.cs @@ -19,8 +19,8 @@ namespace OpenRA.Mods.RA.Scripting { readonly PlayerResources pr; - public ResourceProperties(Player player) - : base(player) + public ResourceProperties(ScriptContext context, Player player) + : base(context, player) { pr = player.PlayerActor.Trait(); } diff --git a/OpenRA.Mods.RA/Scripting/Properties/TransportProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/TransportProperties.cs index 58d87dc5bc..ff826ed717 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/TransportProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/TransportProperties.cs @@ -21,8 +21,8 @@ namespace OpenRA.Mods.RA.Scripting { readonly Cargo cargo; - public TransportProperties(Actor self) - : base(self) + public TransportProperties(ScriptContext context, Actor self) + : base(context, self) { cargo = self.Trait(); } @@ -46,8 +46,8 @@ namespace OpenRA.Mods.RA.Scripting { readonly ParaDrop paradrop; - public ParadropPowers(Actor self) - : base(self) + public ParadropPowers(ScriptContext context, Actor self) + : base(context, self) { paradrop = self.Trait(); } diff --git a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs index 9c2a8e597b..c507a09616 100644 --- a/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs +++ b/OpenRA.Mods.RA/Scripting/ScriptTriggers.cs @@ -18,12 +18,13 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Scripting { - public enum Trigger { OnIdle, OnDamaged, OnKilled, OnProduction, OnPlayerWon, OnPlayerLost, OnObjectiveAdded, OnObjectiveCompleted, OnObjectiveFailed }; + public enum Trigger { OnIdle, OnDamaged, OnKilled, OnProduction, 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 : TraitInfo { } - public sealed class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction, INotifyObjectivesUpdated, IDisposable + public sealed class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction, INotifyObjectivesUpdated, INotifyCapture, INotifyAddedToWorld, INotifyRemovedFromWorld, IDisposable { public event Action OnKilledInternal = _ => {}; @@ -43,36 +44,25 @@ namespace OpenRA.Mods.RA.Scripting public void TickIdle(Actor self) { foreach (var f in Triggers[Trigger.OnIdle]) - { - var a = self.ToLuaValue(f.Second); - f.First.Call(a).Dispose(); - a.Dispose(); - } + using (var a = self.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); } public void Damaged(Actor self, AttackInfo e) { foreach (var f in Triggers[Trigger.OnDamaged]) - { - var a = self.ToLuaValue(f.Second); - var b = e.Attacker.ToLuaValue(f.Second); - f.First.Call(a, b).Dispose(); - a.Dispose(); - b.Dispose(); - } + using (var a = self.ToLuaValue(f.Second)) + using (var b = e.Attacker.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); } public void Killed(Actor self, AttackInfo e) { // Run lua callbacks foreach (var f in Triggers[Trigger.OnKilled]) - { - var a = self.ToLuaValue(f.Second); - var b = e.Attacker.ToLuaValue(f.Second); - f.First.Call(a, b).Dispose(); - a.Dispose(); - b.Dispose(); - } + using (var a = self.ToLuaValue(f.Second)) + using (var b = e.Attacker.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); // Run any internally bound callbacks OnKilledInternal(self); @@ -81,69 +71,71 @@ namespace OpenRA.Mods.RA.Scripting public void UnitProduced(Actor self, Actor other, CPos exit) { foreach (var f in Triggers[Trigger.OnProduction]) - { - var a = self.ToLuaValue(f.Second); - var b = other.ToLuaValue(f.Second); - f.First.Call(a, b).Dispose(); - a.Dispose(); - b.Dispose(); - } + using (var a = self.ToLuaValue(f.Second)) + using (var b = other.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); } public void OnPlayerWon(Player player) { foreach (var f in Triggers[Trigger.OnPlayerWon]) - { - var a = player.ToLuaValue(f.Second); - f.First.Call(a).Dispose(); - a.Dispose(); - } + using (var a = player.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); } public void OnPlayerLost(Player player) { foreach (var f in Triggers[Trigger.OnPlayerLost]) - { - var a = player.ToLuaValue(f.Second); - f.First.Call(a).Dispose(); - a.Dispose(); - } + using (var a = player.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); } public void OnObjectiveAdded(Player player, int id) { foreach (var f in Triggers[Trigger.OnObjectiveAdded]) - { - var a = player.ToLuaValue(f.Second); - var b = id.ToLuaValue(f.Second); - f.First.Call(a, b).Dispose(); - a.Dispose(); - b.Dispose(); - } + using (var a = player.ToLuaValue(f.Second)) + using (var b = id.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); } public void OnObjectiveCompleted(Player player, int id) { foreach (var f in Triggers[Trigger.OnObjectiveCompleted]) - { - var a = player.ToLuaValue(f.Second); - var b = id.ToLuaValue(f.Second); - f.First.Call(a, b).Dispose(); - a.Dispose(); - b.Dispose(); - } + using (var a = player.ToLuaValue(f.Second)) + using (var b = id.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); } public void OnObjectiveFailed(Player player, int id) { foreach (var f in Triggers[Trigger.OnObjectiveFailed]) - { - var a = player.ToLuaValue(f.Second); - var b = id.ToLuaValue(f.Second); - f.First.Call(a, b).Dispose(); - a.Dispose(); - b.Dispose(); - } + using (var a = player.ToLuaValue(f.Second)) + using (var b = id.ToLuaValue(f.Second)) + f.First.Call(a, b).Dispose(); + } + + public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner) + { + foreach (var f in Triggers[Trigger.OnCapture]) + using (var a = self.ToLuaValue(f.Second)) + using (var b = captor.ToLuaValue(f.Second)) + using (var c = oldOwner.ToLuaValue(f.Second)) + using (var d = newOwner.ToLuaValue(f.Second)) + f.First.Call(a, b, c, d).Dispose(); + } + + public void AddedToWorld(Actor self) + { + foreach (var f in Triggers[Trigger.OnAddedToWorld]) + using (var a = self.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); + } + + public void RemovedFromWorld(Actor self) + { + foreach (var f in Triggers[Trigger.OnRemovedFromWorld]) + using (var a = self.ToLuaValue(f.Second)) + f.First.Call(a).Dispose(); } public void Clear(Trigger trigger) diff --git a/mods/cnc/maps/gdi01/gdi01.lua b/mods/cnc/maps/gdi01/gdi01.lua index b54c5f8e98..3c08f00494 100644 --- a/mods/cnc/maps/gdi01/gdi01.lua +++ b/mods/cnc/maps/gdi01/gdi01.lua @@ -3,9 +3,7 @@ VehicleReinforcements = { "jeep" } NodPatrol = { "e1", "e1" } SendNodPatrol = function() - Utils.Do(NodPatrol, function(type) - local soldier = Actor.Create(type, true, { Location = nod0.Location, Owner = enemy }) - soldier.Move(nod1.Location) + Reinforcements.Reinforce(enemy, NodPatrol, { nod0.Location, nod1.Location }, 15, function(soldier) soldier.AttackMove(nod2.Location) soldier.Move(nod3.Location) soldier.Hunt() @@ -17,21 +15,27 @@ SetGunboatPath = function(gunboat) gunboat.AttackMove(gunboatRight.Location) end -ReinforceFromSea = function(passengers) - local transport = Actor.Create("oldlst", true, { Location = lstStart.Location, Owner = player }) +Reinforce = function(units) + Media.PlaySpeechNotification(player, "Reinforce") + Reinforcements.ReinforceWithTransport(player, "oldlst", units, { lstStart.Location, lstEnd.Location }, { lstStart.Location }) +end - Utils.Do(passengers, function(actorType) - local passenger = Actor.Create(actorType, false, { Owner = player }) - transport.LoadPassenger(passenger) +triggerAdded = false +CheckForBase = function() + baseBuildings = Map.ActorsInBox(Map.TopLeft, Map.BottomRight, function(actor) + return actor.Type == "fact" or actor.Type == "pyle" or actor.Type == "nuke" end) - transport.Move(lstEnd.Location) - transport.UnloadPassengers() - transport.Wait(50) - transport.Move(lstStart.Location) - transport.Destroy() + Utils.Do(baseBuildings, function(building) + if not triggerAdded and building.Type == "fact" then + Trigger.OnRemovedFromWorld(building, function() + player.MarkFailedObjective(gdiObjective2) + end) + triggerAdded = true + end + end) - Media.PlaySpeechNotification(player, "Reinforce") + return #baseBuildings >= 3 end WorldLoaded = function() @@ -40,7 +44,12 @@ WorldLoaded = function() player = Player.GetPlayer("GDI") enemy = Player.GetPlayer("Nod") - gdiObjective = player.AddPrimaryObjective("Destroy all Nod forces in the area!") + nodObjective = enemy.AddPrimaryObjective("Destroy all GDI troops") + gdiObjective1 = player.AddPrimaryObjective("Eliminate all Nod forces in the area") + gdiObjective2 = player.AddSecondaryObjective("Establish a beachhead") + + Trigger.OnObjectiveCompleted(player, function() Media.DisplayMessage("Objective completed") end) + Trigger.OnObjectiveFailed(player, function() Media.DisplayMessage("Objective failed") end) Trigger.OnPlayerWon(player, function() Media.PlaySpeechNotification(player, "Win") @@ -60,18 +69,26 @@ WorldLoaded = function() SendNodPatrol() - Trigger.AfterDelay(25 * 5, function() ReinforceFromSea(InfantryReinforcements) end) - Trigger.AfterDelay(25 * 15, function() ReinforceFromSea(InfantryReinforcements) end) - Trigger.AfterDelay(25 * 30, function() ReinforceFromSea(VehicleReinforcements) end) - Trigger.AfterDelay(25 * 60, function() ReinforceFromSea(VehicleReinforcements) end) + Trigger.AfterDelay(Utils.Seconds(5), function() Reinforce(InfantryReinforcements) end) + Trigger.AfterDelay(Utils.Seconds(15), function() Reinforce(InfantryReinforcements) end) + Trigger.AfterDelay(Utils.Seconds(30), function() Reinforce(VehicleReinforcements) end) + Trigger.AfterDelay(Utils.Seconds(60), function() Reinforce(VehicleReinforcements) end) end +tick = 0 +baseEstablished = false Tick = function() + tick = tick + 1 if enemy.HasNoRequiredUnits() then - player.MarkCompletedObjective(gdiObjective) + player.MarkCompletedObjective(gdiObjective1) end if player.HasNoRequiredUnits() then - player.MarkFailedObjective(gdiObjective) + enemy.MarkCompletedObjective(nodObjective) + end + + if not baseEstablished and tick % Utils.Seconds(1) == 0 and CheckForBase() then + baseEstablished = true + player.MarkCompletedObjective(gdiObjective2) end end diff --git a/mods/cnc/maps/gdi01/map.yaml b/mods/cnc/maps/gdi01/map.yaml index 8948d0b9f6..df4e1b7c72 100644 --- a/mods/cnc/maps/gdi01/map.yaml +++ b/mods/cnc/maps/gdi01/map.yaml @@ -467,6 +467,8 @@ Rules: EarlyGameOver: true ^Infantry: MustBeDestroyed: + ^Vehicle: + MustBeDestroyed: PROC: Buildable: Prerequisites: ~disabled