Files
OpenRA/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs
2022-05-28 00:35:10 -05:00

539 lines
12 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2022 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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using Eluant;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Scripting
{
public enum Trigger
{
OnIdle, OnDamaged, OnKilled, OnProduction, OnOtherProduction, OnPlayerWon, OnPlayerLost,
OnObjectiveAdded, OnObjectiveCompleted, OnObjectiveFailed, OnCapture, OnInfiltrated,
OnAddedToWorld, OnRemovedFromWorld, OnDiscovered, OnPlayerDiscovered,
OnPassengerEntered, OnPassengerExited, OnSold, OnTimerExpired
}
[Desc("Allows map scripts to attach triggers to this actor via the Triggers global.")]
public class ScriptTriggersInfo : TraitInfo
{
public override object Create(ActorInitializer init) { return new ScriptTriggers(init.World, init.Self); }
}
public sealed class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction, INotifyOtherProduction,
INotifyObjectivesUpdated, INotifyCapture, INotifyInfiltrated, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyDiscovered, INotifyActorDisposing,
INotifyPassengerEntered, INotifyPassengerExited, INotifySold, INotifyWinStateChanged, INotifyTimeLimit
{
readonly World world;
readonly Actor self;
public event Action<Actor> OnKilledInternal = _ => { };
public event Action<Actor> OnCapturedInternal = _ => { };
public event Action<Actor> OnRemovedInternal = _ => { };
public event Action<Actor> OnAddedInternal = _ => { };
public event Action<Actor, Actor> OnProducedInternal = (a, b) => { };
public event Action<Actor, Actor> OnOtherProducedInternal = (a, b) => { };
readonly List<Triggerable>[] triggerables = Exts.MakeArray(Enum.GetValues(typeof(Trigger)).Length, _ => new List<Triggerable>());
readonly struct Triggerable : IDisposable
{
public readonly LuaFunction Function;
public readonly ScriptContext Context;
public readonly LuaValue Self;
public Triggerable(LuaFunction function, ScriptContext context, Actor self)
{
Function = (LuaFunction)function.CopyReference();
Context = context;
Self = self.ToLuaValue(Context);
}
public void Dispose()
{
Function.Dispose();
Self.Dispose();
}
}
public ScriptTriggers(World world, Actor self)
{
this.world = world;
this.self = self;
}
List<Triggerable> Triggerables(Trigger trigger)
{
return triggerables[(int)trigger];
}
public void RegisterCallback(Trigger trigger, LuaFunction func, ScriptContext context)
{
Triggerables(trigger).Add(new Triggerable(func, context, self));
}
public bool HasAnyCallbacksFor(Trigger trigger)
{
return Triggerables(trigger).Count > 0;
}
void INotifyIdle.TickIdle(Actor self)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnIdle))
{
try
{
f.Function.Call(f.Self).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyDamage.Damaged(Actor self, AttackInfo e)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnDamaged))
{
try
{
using (var b = e.Attacker.ToLuaValue(f.Context))
using (var c = e.Damage.Value.ToLuaValue(f.Context))
f.Function.Call(f.Self, b, c).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyKilled.Killed(Actor self, AttackInfo e)
{
if (world.Disposing)
return;
// Run Lua callbacks
foreach (var f in Triggerables(Trigger.OnKilled))
{
try
{
using (var b = e.Attacker.ToLuaValue(f.Context))
f.Function.Call(f.Self, b).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks
OnKilledInternal(self);
}
void INotifyProduction.UnitProduced(Actor self, Actor other, CPos exit)
{
if (world.Disposing)
return;
// Run Lua callbacks
foreach (var f in Triggerables(Trigger.OnProduction))
{
try
{
using (var b = other.ToLuaValue(f.Context))
f.Function.Call(f.Self, b).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks
OnProducedInternal(self, other);
}
void INotifyWinStateChanged.OnPlayerWon(Player player)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnPlayerWon))
{
try
{
using (var a = player.ToLuaValue(f.Context))
f.Function.Call(a).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyWinStateChanged.OnPlayerLost(Player player)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnPlayerLost))
{
try
{
using (var a = player.ToLuaValue(f.Context))
f.Function.Call(a).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyObjectivesUpdated.OnObjectiveAdded(Player player, int id)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnObjectiveAdded))
{
try
{
using (var a = player.ToLuaValue(f.Context))
using (var b = id.ToLuaValue(f.Context))
f.Function.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyObjectivesUpdated.OnObjectiveCompleted(Player player, int id)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnObjectiveCompleted))
{
try
{
using (var a = player.ToLuaValue(f.Context))
using (var b = id.ToLuaValue(f.Context))
f.Function.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyObjectivesUpdated.OnObjectiveFailed(Player player, int id)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnObjectiveFailed))
{
try
{
using (var a = player.ToLuaValue(f.Context))
using (var b = id.ToLuaValue(f.Context))
f.Function.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyCapture.OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner, BitSet<CaptureType> captureTypes)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnCapture))
{
try
{
using (var b = captor.ToLuaValue(f.Context))
using (var c = oldOwner.ToLuaValue(f.Context))
using (var d = newOwner.ToLuaValue(f.Context))
f.Function.Call(f.Self, b, c, d).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks
OnCapturedInternal(self);
}
void INotifyInfiltrated.Infiltrated(Actor self, Actor infiltrator, BitSet<TargetableType> types)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnInfiltrated))
{
try
{
using (var b = infiltrator.ToLuaValue(f.Context))
f.Function.Call(f.Self, b).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyAddedToWorld.AddedToWorld(Actor self)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnAddedToWorld))
{
try
{
f.Function.Call(f.Self).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks
OnAddedInternal(self);
}
void INotifyRemovedFromWorld.RemovedFromWorld(Actor self)
{
if (world.Disposing)
return;
// Run Lua callbacks
foreach (var f in Triggerables(Trigger.OnRemovedFromWorld))
{
try
{
f.Function.Call(f.Self).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks
OnRemovedInternal(self);
}
void INotifySold.Selling(Actor self) { }
void INotifySold.Sold(Actor self)
{
if (world.Disposing)
return;
// Run Lua callbacks
foreach (var f in Triggerables(Trigger.OnSold))
{
try
{
f.Function.Call(f.Self).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyOtherProduction.UnitProducedByOther(Actor self, Actor producee, Actor produced, string productionType, TypeDictionary init)
{
if (world.Disposing)
return;
// Run Lua callbacks
foreach (var f in Triggerables(Trigger.OnOtherProduction))
{
try
{
using (var a = producee.ToLuaValue(f.Context))
using (var b = produced.ToLuaValue(f.Context))
using (var c = productionType.ToLuaValue(f.Context))
f.Function.Call(a, b, c).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks
OnOtherProducedInternal(producee, produced);
}
void INotifyDiscovered.OnDiscovered(Actor self, Player discoverer, bool playNotification)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnDiscovered))
{
try
{
using (var b = discoverer.ToLuaValue(f.Context))
f.Function.Call(f.Self, b).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
foreach (var f in Triggerables(Trigger.OnPlayerDiscovered))
{
try
{
using (var a = self.Owner.ToLuaValue(f.Context))
using (var b = discoverer.ToLuaValue(f.Context))
f.Function.Call(a, b, f.Self).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyPassengerEntered.OnPassengerEntered(Actor self, Actor passenger)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnPassengerEntered))
{
try
{
using (var trans = self.ToLuaValue(f.Context))
using (var pass = passenger.ToLuaValue(f.Context))
f.Function.Call(trans, pass).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyPassengerExited.OnPassengerExited(Actor self, Actor passenger)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnPassengerExited))
{
try
{
using (var trans = self.ToLuaValue(f.Context))
using (var pass = passenger.ToLuaValue(f.Context))
f.Function.Call(trans, pass).Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
void INotifyTimeLimit.NotifyTimerExpired(Actor self)
{
if (world.Disposing)
return;
foreach (var f in Triggerables(Trigger.OnTimerExpired))
{
try
{
f.Function.Call().Dispose();
}
catch (Exception ex)
{
f.Context.FatalError(ex.Message);
return;
}
}
}
public void Clear(Trigger trigger)
{
world.AddFrameEndTask(w =>
{
var triggerables = Triggerables(trigger);
foreach (var f in triggerables)
f.Dispose();
triggerables.Clear();
});
}
public void ClearAll()
{
foreach (Trigger t in Enum.GetValues(typeof(Trigger)))
Clear(t);
}
void INotifyActorDisposing.Disposing(Actor self)
{
ClearAll();
}
}
}