Add map scripting support

This commit is contained in:
ScottNZ
2013-11-15 21:57:44 +13:00
parent b2f46a56ea
commit cd0b3d8862
5 changed files with 317 additions and 11 deletions

View File

@@ -0,0 +1,134 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using LuaInterface;
namespace OpenRA.Mods.RA.Scripting
{
public class LuaScriptContext : IDisposable
{
public Lua Lua { get; private set; }
public LuaScriptContext()
{
Log.Write("debug", "Creating Lua script context");
Lua = new Lua();
}
public void RegisterObject(object target, string tableName, bool exposeAllMethods)
{
Log.Write("debug", "Registering object {0}", target);
if (tableName != null && Lua.GetTable(tableName) == null)
Lua.NewTable(tableName);
var type = target.GetType();
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
RegisterMethods(tableName, target, methods, exposeAllMethods);
}
public void RegisterType(Type type, string tableName, bool exposeAllMethods)
{
Log.Write("debug", "Registering type {0}", type);
if (tableName != null && Lua.GetTable(tableName) == null)
Lua.NewTable(tableName);
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static);
RegisterMethods(tableName, null, methods, exposeAllMethods);
}
void RegisterMethods(string tableName, object target, IEnumerable<MethodInfo> methods, bool allMethods)
{
foreach (var method in methods)
{
string methodName;
var attr = method.GetCustomAttributes<LuaGlobalAttribute>(true).FirstOrDefault();
if (attr == null)
{
if (allMethods)
methodName = method.Name;
else
continue;
}
else
methodName = attr.Name ?? method.Name;
var methodTarget = method.IsStatic ? null : target;
if (tableName != null)
Lua.RegisterFunction(tableName + "." + methodName, methodTarget, method);
else
Lua.RegisterFunction(methodName, methodTarget, method);
}
}
void LogException(Exception e)
{
Game.Debug("{0}", e.Message);
Game.Debug("See debug.log for details");
Log.Write("debug", "{0}", e);
}
public void LoadLuaScripts(Func<string, string> getFileContents, params string[] files)
{
foreach (var file in files)
{
try
{
Log.Write("debug", "Loading Lua script {0}", file);
var content = getFileContents(file);
Lua.DoString(content, file);
}
catch (Exception e)
{
LogException(e);
}
}
}
public object[] InvokeLuaFunction(string name, params object[] args)
{
try
{
var function = Lua.GetFunction(name);
if (function == null)
return null;
return function.Call(args);
}
catch (Exception e)
{
LogException(e);
return null;
}
}
public void Dispose()
{
if (Lua == null)
return;
GC.SuppressFinalize(this);
Lua.Dispose();
Lua = null;
}
~LuaScriptContext()
{
Dispose();
}
}
}

View File

@@ -0,0 +1,165 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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 System;
using System.Linq;
using LuaInterface;
using OpenRA.Effects;
using OpenRA.FileFormats;
using OpenRA.Traits;
using WorldRenderer = OpenRA.Graphics.WorldRenderer;
namespace OpenRA.Mods.RA.Scripting
{
public class LuaScriptInterfaceInfo : ITraitInfo, Requires<SpawnMapActorsInfo>
{
public readonly string[] LuaScripts = { };
public object Create(ActorInitializer init) { return new LuaScriptInterface(this); }
}
public class LuaScriptInterface : IWorldLoaded, ITick
{
World world;
readonly LuaScriptContext context = new LuaScriptContext();
readonly LuaScriptInterfaceInfo info;
public LuaScriptInterface(LuaScriptInterfaceInfo info)
{
this.info = info;
}
public void WorldLoaded(World w, WorldRenderer wr)
{
world = w;
AddMapActorGlobals();
context.Lua["World"] = w;
context.Lua["WorldRenderer"] = wr;
context.RegisterObject(this, "_OpenRA", false);
context.RegisterType(typeof(WVec), "WVec", true);
context.RegisterType(typeof(WPos), "WPos", true);
context.RegisterType(typeof(CPos), "CPos", true);
context.RegisterType(typeof(WRot), "WRot", true);
context.RegisterType(typeof(WAngle), "WAngle", true);
context.RegisterType(typeof(WRange), "WRange", true);
context.RegisterType(typeof(int2), "int2", true);
context.RegisterType(typeof(float2), "float2", true);
var sharedScripts = Game.modData.Manifest.LuaScripts ?? new string[0];
if (sharedScripts.Any())
context.LoadLuaScripts(f => FileSystem.Open(f).ReadAllText(), sharedScripts);
context.LoadLuaScripts(f => w.Map.Container.GetContent(f).ReadAllText(), info.LuaScripts);
context.InvokeLuaFunction("WorldLoaded");
}
void AddMapActorGlobals()
{
foreach (var kv in world.WorldActor.Trait<SpawnMapActors>().Actors)
context.Lua[kv.Key] = kv.Value;
}
public void Tick(Actor self)
{
context.InvokeLuaFunction("Tick");
}
[LuaGlobal]
public object New(string typeName, LuaTable args)
{
var type = Game.modData.ObjectCreator.FindType(typeName);
if (type == null)
throw new InvalidOperationException("Cannot locate type: {0}".F(typeName));
if (args == null)
return Activator.CreateInstance(type);
var argsArray = ConvertArgs(args);
return Activator.CreateInstance(type, argsArray);
}
object[] ConvertArgs(LuaTable args)
{
var argsArray = new object[args.Keys.Count];
for (var i = 1; i <= args.Keys.Count; i++)
{
var arg = args[i] as LuaTable;
if (arg != null && arg[1] != null && arg[2] != null)
argsArray[i - 1] = Convert.ChangeType(arg[1], Enum<TypeCode>.Parse(arg[2].ToString()));
else
argsArray[i - 1] = args[i];
}
return argsArray;
}
[LuaGlobal]
public void Debug(object obj)
{
if (obj != null)
Game.Debug(obj.ToString());
}
[LuaGlobal]
public object TraitOrDefault(Actor actor, string className)
{
var type = Game.modData.ObjectCreator.FindType(className);
if (type == null)
return null;
var method = typeof(Actor).GetMethod("TraitOrDefault");
var genericMethod = method.MakeGenericMethod(type);
return genericMethod.Invoke(actor, null);
}
[LuaGlobal]
public object Trait(Actor actor, string className)
{
var ret = TraitOrDefault(actor, className);
if (ret == null)
throw new InvalidOperationException("Actor {0} does not have trait of type {1}".F(actor, className));
return ret;
}
[LuaGlobal]
public bool HasTrait(Actor actor, string className)
{
var ret = TraitOrDefault(actor, className);
return ret != null;
}
[LuaGlobal]
public object TraitInfoOrDefault(string actorType, string className)
{
var type = Game.modData.ObjectCreator.FindType(className);
if (type == null || !Rules.Info.ContainsKey(actorType))
return null;
return Rules.Info[actorType].Traits.GetOrDefault(type);
}
[LuaGlobal]
public object TraitInfo(string actorType, string className)
{
var ret = TraitInfoOrDefault(actorType, className);
if (ret == null)
throw new InvalidOperationException("Actor type {0} does not have trait info of type {1}".F(actorType, className));
return ret;
}
[LuaGlobal]
public bool HasTraitInfo(string actorType, string className)
{
var ret = TraitInfoOrDefault(actorType, className);
return ret != null;
}
[LuaGlobal]
public void RunAfterDelay(double delay, Action func)
{
world.AddFrameEndTask(w => w.Add(new DelayedAction((int)delay, func)));
}
}
}