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) {