From a61fdba44da82a51b0ba7e4e45182f9610aeca57 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 27 Sep 2014 10:34:27 +1200 Subject: [PATCH 1/3] Implement cell triggers. Closes #4400. --- OpenRA.Game/Traits/World/ActorMap.cs | 106 +++++++++++++++++- .../Scripting/Global/TriggerGlobal.cs | 59 ++++++++++ 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/OpenRA.Game/Traits/World/ActorMap.cs b/OpenRA.Game/Traits/World/ActorMap.cs index 4511fa174c..c560624033 100644 --- a/OpenRA.Game/Traits/World/ActorMap.cs +++ b/OpenRA.Game/Traits/World/ActorMap.cs @@ -33,8 +33,58 @@ namespace OpenRA.Traits public Actor Actor; } + class CellTrigger + { + public readonly int Id; + public readonly CPos[] Footprint; + public bool Dirty; + + Action onActorEntered; + Action onActorExited; + + IEnumerable currentActors = Enumerable.Empty(); + + public CellTrigger(int id, CPos[] footprint, Action onActorEntered, Action onActorExited) + { + Id = id; + Footprint = footprint; + + this.onActorEntered = onActorEntered; + this.onActorExited = onActorExited; + + // Notify any actors that are initially inside the trigger zone + Dirty = true; + } + + public void Tick(ActorMap am) + { + if (!Dirty) + return; + + var oldActors = currentActors; + currentActors = Footprint.SelectMany(c => am.GetUnitsAt(c)).ToList(); + + var entered = currentActors.Except(oldActors); + var exited = oldActors.Except(currentActors); + + if (onActorEntered != null) + foreach (var a in entered) + onActorEntered(a); + + if (onActorExited != null) + foreach (var a in exited) + onActorExited(a); + + Dirty = false; + } + } + readonly ActorMapInfo info; readonly Map map; + readonly Dictionary cellTriggers = new Dictionary(); + readonly Dictionary> cellTriggerInfluence = new Dictionary>(); + int nextTriggerId; + readonly CellLayer influence; readonly List[] actors; @@ -166,8 +216,17 @@ namespace OpenRA.Traits public void AddInfluence(Actor self, IOccupySpace ios) { foreach (var c in ios.OccupiedCells()) - if (map.Contains(c.First)) - influence[c.First] = new InfluenceNode { Next = influence[c.First], SubCell = c.Second, Actor = self }; + { + if (!map.Contains(c.First)) + continue; + + influence[c.First] = new InfluenceNode { Next = influence[c.First], SubCell = c.Second, Actor = self }; + + List triggers; + if (cellTriggerInfluence.TryGetValue(c.First, out triggers)) + foreach (var t in triggers) + t.Dirty = true; + } } public void RemoveInfluence(Actor self, IOccupySpace ios) @@ -180,6 +239,11 @@ namespace OpenRA.Traits var temp = influence[c.First]; RemoveInfluenceInner(ref temp, self); influence[c.First] = temp; + + List triggers; + if (cellTriggerInfluence.TryGetValue(c.First, out triggers)) + foreach (var t in triggers) + t.Dirty = true; } } @@ -213,6 +277,44 @@ namespace OpenRA.Traits } addActorPosition.Clear(); + + foreach (var t in cellTriggers) + t.Value.Tick(this); + } + + public int AddCellTrigger(CPos[] cells, Action onEntry, Action onExit) + { + var id = nextTriggerId++; + var t = new CellTrigger(id, cells, onEntry, onExit); + cellTriggers.Add(id, t); + + foreach (var c in cells) + { + if (!map.Contains(c)) + continue; + + if (!cellTriggerInfluence.ContainsKey(c)) + cellTriggerInfluence.Add(c, new List()); + + cellTriggerInfluence[c].Add(t); + } + + return id; + } + + public void RemoveCellTrigger(int id) + { + CellTrigger trigger; + if (!cellTriggers.TryGetValue(id, out trigger)) + return; + + foreach (var c in trigger.Footprint) + { + if (!cellTriggerInfluence.ContainsKey(c)) + continue; + + cellTriggerInfluence[c].RemoveAll(t => t == trigger); + } } public void AddPosition(Actor a, IOccupySpace ios) diff --git a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs index ecf93f3a89..b912d66db1 100644 --- a/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/TriggerGlobal.cs @@ -197,6 +197,65 @@ namespace OpenRA.Mods.RA.Scripting GetScriptTriggers(a).RegisterCallback(Trigger.OnCapture, func, context); } + static CPos[] MakeCellFootprint(LuaTable table) + { + var cells = new List(); + foreach (var kv in table) + { + CPos cell; + if (!kv.Value.TryGetClrValue(out cell)) + throw new LuaException("Cell footprints must be specified as a table of int,Cell pairs. Recieved {0},{1}".F(kv.Key.GetType().Name, kv.Value.GetType().Name)); + + cells.Add(cell); + } + + return cells.ToArray(); + } + + [Desc("Call a function when a ground-based actor enters this cell footprint." + + "Returns the trigger id for later removal using RemoveFootprintTrigger(int id)." + + "The callback function will be called as func(Actor a, int id).")] + public int OnEnteredFootprint(LuaTable cells, LuaFunction func) + { + var triggerId = 0; + var onEntry = (LuaFunction)func.CopyReference(); + Action invokeEntry = a => + { + using (var luaActor = a.ToLuaValue(context)) + using (var id = triggerId.ToLuaValue(context)) + onEntry.Call(luaActor, id).Dispose(); + }; + + triggerId = context.World.ActorMap.AddCellTrigger(MakeCellFootprint(cells), invokeEntry, null); + + return triggerId; + } + + [Desc("Call a function when a ground-based actor leaves this cell footprint." + + "Returns the trigger id for later removal using RemoveFootprintTrigger(int id)." + + "The callback function will be called as func(Actor a, int id).")] + public int OnExitedFootprint(LuaTable cells, LuaFunction func) + { + var triggerId = 0; + var onExit = (LuaFunction)func.CopyReference(); + Action invokeExit = a => + { + using (var luaActor = a.ToLuaValue(context)) + using (var id = triggerId.ToLuaValue(context)) + onExit.Call(luaActor, id).Dispose(); + }; + + triggerId = context.World.ActorMap.AddCellTrigger(MakeCellFootprint(cells), null, invokeExit); + + return triggerId; + } + + [Desc("Removes a previously created footprint trigger.")] + public void RemoveFootprintTrigger(int id) + { + context.World.ActorMap.RemoveCellTrigger(id); + } + [Desc("Removes all triggers from this actor.")] public void ClearAll(Actor a) { From 154ebc58571d105b3e2a48b770e18d75f8ea6879 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Mon, 29 Sep 2014 21:26:21 +0200 Subject: [PATCH 2/3] Update gdi04c to use cell triggers --- mods/cnc/maps/gdi04c/gdi04c.lua | 39 ++++++++++++++------------------- mods/cnc/maps/gdi04c/map.yaml | 7 ------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/mods/cnc/maps/gdi04c/gdi04c.lua b/mods/cnc/maps/gdi04c/gdi04c.lua index 0497bec674..5ff4c6a4a0 100644 --- a/mods/cnc/maps/gdi04c/gdi04c.lua +++ b/mods/cnc/maps/gdi04c/gdi04c.lua @@ -1,4 +1,6 @@ LoseTriggerHouses = { TrigLos2Farm1, TrigLos2Farm2, TrigLos2Farm3, TrigLos2Farm4 } +TownAttackTrigger = { CPos.New(54, 38), CPos.New(55, 38), CPos.New(56, 38), CPos.New(57, 38) } +GDIReinforcementsTrigger = { CPos.New(32, 51), CPos.New(32, 52), CPos.New(33, 52) } GDIReinforcementsPart1 = { "jeep", "jeep" } GDIReinforcementsPart2 = { "e2", "e2", "e2", "e2", "e2" } @@ -34,8 +36,6 @@ TownAttackAction = function(actor) end AttackTown = function() - TownAttackTriggered = true - Reinforcements.Reinforce(nod, TownAttackWave1, { NodReinfEntry.Location, waypoint0.Location }, Utils.Seconds(0.25), TownAttackAction) Trigger.AfterDelay(Utils.Seconds(2), function() Reinforcements.Reinforce(nod, TownAttackWave2, { NodReinfEntry.Location, waypoint0.Location }, Utils.Seconds(1), TownAttackAction) @@ -46,8 +46,6 @@ AttackTown = function() end SendGDIReinforcements = function() - GDIReinforcementsTriggered = true - Reinforcements.Reinforce(player, GDIReinforcementsPart1, { GDIReinfEntry1.Location, waypoint12.Location }, Utils.Seconds(1), function(actor) Media.PlaySpeechNotification(player, "Reinforce") actor.Move(waypoint10.Location) @@ -63,17 +61,6 @@ SendGDIReinforcements = function() end) end --- FIXME: replace with real cell trigger when available -CellTrigger = function(player, trigger, radius, func) - local units = Map.ActorsInCircle(trigger.CenterPosition, WRange.FromCells(radius), function(actor) - return actor.Owner == player and actor.HasProperty("Move") - end) - - if #units > 0 then - func() - end -end - WorldLoaded = function() player = Player.GetPlayer("GDI") nod = Player.GetPlayer("Nod") @@ -107,6 +94,20 @@ WorldLoaded = function() end) end) + Trigger.OnExitedFootprint(TownAttackTrigger, function(a, id) + if a.Owner == player then + Trigger.RemoveFootprintTrigger(id) + AttackTown() + end + end) + + Trigger.OnEnteredFootprint(GDIReinforcementsTrigger, function(a, id) + if a.Owner == player then + Trigger.RemoveFootprintTrigger(id) + SendGDIReinforcements() + end + end) + Utils.Do(player.GetGroundAttackers(), function(unit) unit.Stance = "Defend" end) @@ -121,8 +122,6 @@ WorldLoaded = function() Media.PlayMovieFullscreen("bkground.vqa", function() Media.PlayMovieFullscreen("gdi4a.vqa", function() Media.PlayMovieFullscreen("nodsweep.vqa") end) end) end -TownAttackTriggered = false -GDIReinforcementsTriggered = false Tick = function() if player.HasNoRequiredUnits() then nod.MarkCompletedObjective(nodObjective) @@ -131,10 +130,4 @@ Tick = function() player.MarkCompletedObjective(gdiObjective1) player.MarkCompletedObjective(gdiObjective2) end - - if not TownAttackTriggered then - CellTrigger(player, TownAttackTrigger, 2, AttackTown) - elseif not GDIReinforcementsTriggered then - CellTrigger(player, GDIReinfTrigger, 2, SendGDIReinforcements) - end end diff --git a/mods/cnc/maps/gdi04c/map.yaml b/mods/cnc/maps/gdi04c/map.yaml index d6b674b73c..041d74d466 100644 --- a/mods/cnc/maps/gdi04c/map.yaml +++ b/mods/cnc/maps/gdi04c/map.yaml @@ -860,19 +860,12 @@ Actors: waypoint0: waypoint Location: 55,54 Owner: Neutral - TownAttackTrigger: waypoint -# Location: 46,44 - Location: 56,38 - Owner: Neutral TownAttackWpt: waypoint Location: 24,48 Owner: Neutral NodReinfEntry: waypoint Location: 61,55 Owner: Neutral - GDIReinfTrigger: waypoint - Location: 32,52 - Owner: Neutral GDIReinfEntry1: waypoint Location: 14,51 Owner: Neutral From df64f79753091220aa1c2a803f5bb54c9154c461 Mon Sep 17 00:00:00 2001 From: Oliver Brakmann Date: Mon, 29 Sep 2014 23:56:08 +0200 Subject: [PATCH 3/3] Whitespace fixes --- mods/cnc/maps/gdi04c/gdi04c.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mods/cnc/maps/gdi04c/gdi04c.lua b/mods/cnc/maps/gdi04c/gdi04c.lua index 5ff4c6a4a0..9b9d6960dc 100644 --- a/mods/cnc/maps/gdi04c/gdi04c.lua +++ b/mods/cnc/maps/gdi04c/gdi04c.lua @@ -7,10 +7,10 @@ GDIReinforcementsPart2 = { "e2", "e2", "e2", "e2", "e2" } TownAttackWave1 = { "bggy", "bggy" } TownAttackWave2 = { "ltnk", "ltnk" } TownAttackWave3 = { "e1", "e1", "e1", "e1", "e3", "e3", "e3", "e3" } -TownAttackWpts = { waypoint1, waypoint2 } +TownAttackWpts = { waypoint1, waypoint2 } -Civvie1Wpts = { waypoint3, waypoint17 } -Civvie2Wpts = { waypoint26, waypoint3, waypoint9, waypoint4, waypoint5, waypoint6, waypoint8, waypoint7, waypoint1, waypoint2 } +Civvie1Wpts = { waypoint3, waypoint17 } +Civvie2Wpts = { waypoint26, waypoint3, waypoint9, waypoint4, waypoint5, waypoint6, waypoint8, waypoint7, waypoint1, waypoint2 } FollowCivvieWpts = function(actor, wpts) Utils.Do(wpts, function(wpt) @@ -62,8 +62,8 @@ SendGDIReinforcements = function() end WorldLoaded = function() - player = Player.GetPlayer("GDI") - nod = Player.GetPlayer("Nod") + player = Player.GetPlayer("GDI") + nod = Player.GetPlayer("Nod") nodObjective = nod.AddPrimaryObjective("Destroy all GDI troops") gdiObjective1 = player.AddPrimaryObjective("Defend the town of BiaƂystok")