Add a new native-lua implementation.

This commit is contained in:
Paul Chote
2014-03-27 22:40:17 +13:00
parent f6efc9c5bc
commit d73af0190f
29 changed files with 1929 additions and 21 deletions

40
OpenRA.Game/Actor.cs Executable file → Normal file
View File

@@ -12,13 +12,16 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA
{
public class Actor
public class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding
{
public readonly ActorInfo Info;
@@ -240,5 +243,40 @@ namespace OpenRA
health.Value.InflictDamage(this, attacker, health.Value.MaxHP, null, true);
}
#region Scripting interface
Lazy<ScriptActorInterface> luaInterface;
public void OnScriptBind(ScriptContext context)
{
luaInterface = Exts.Lazy(() => new ScriptActorInterface(context, this));
}
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
{
get { return luaInterface.Value[runtime, keyValue]; }
set { luaInterface.Value[runtime, keyValue] = value; }
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
Actor a, b;
if (!left.TryGetClrValue<Actor>(out a) || !right.TryGetClrValue<Actor>(out b))
return false;
return a == b;
}
public LuaValue ToString(LuaRuntime runtime)
{
return "Actor ({0})".F(this);
}
public bool HasScriptProperty(string name)
{
return luaInterface.Value.ContainsKey(name);
}
#endregion
}
}

View File

@@ -10,13 +10,13 @@
using System;
using System.Drawing;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.Scripting;
namespace OpenRA
{
/// <summary>
/// Cell coordinate position in the world (coarse).
/// </summary>
public struct CPos
public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding
{
public readonly int X, Y;
@@ -60,6 +60,56 @@ namespace OpenRA
public override string ToString() { return "{0},{1}".F(X, Y); }
#region Scripting interface
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
CPos a;
CVec b;
if (!left.TryGetClrValue<CPos>(out a) || !right.TryGetClrValue<CVec>(out b))
throw new LuaException("Attempted to call CPos.Add(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b);
}
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
CPos a;
CVec b;
if (!left.TryGetClrValue<CPos>(out a) || !right.TryGetClrValue<CVec>(out b))
throw new LuaException("Attempted to call CPos.Subtract(CPos, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a - b);
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
CPos a, b;
if (!left.TryGetClrValue<CPos>(out a) || !right.TryGetClrValue<CPos>(out b))
return false;
return a == b;
}
public LuaValue this[LuaRuntime runtime, LuaValue key]
{
get
{
switch (key.ToString())
{
case "X": return X;
case "Y": return Y;
default: throw new LuaException("CPos does not define a member '{0}'".F(key));
}
}
set
{
throw new LuaException("CPos is read-only. Use CPos.New to create a new value");
}
}
#endregion
}
public static class RectangleExtensions

View File

@@ -10,13 +10,13 @@
using System;
using System.Drawing;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.Scripting;
namespace OpenRA
{
/// <summary>
/// Cell coordinate vector (coarse).
/// </summary>
public struct CVec
public struct CVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding
{
public readonly int X, Y;
@@ -82,5 +82,60 @@ namespace OpenRA
new CVec(1, 0),
new CVec(1, 1),
};
#region Scripting interface
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
CVec a, b;
if (!left.TryGetClrValue<CVec>(out a) || !right.TryGetClrValue<CVec>(out b))
throw new LuaException("Attempted to call CVec.Add(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b);
}
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
CVec a, b;
if (!left.TryGetClrValue<CVec>(out a) || !right.TryGetClrValue<CVec>(out b))
throw new LuaException("Attempted to call CVec.Subtract(CVec, CVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a - b);
}
public LuaValue Minus(LuaRuntime runtime)
{
return new LuaCustomClrObject(-this);
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
CVec a, b;
if (!left.TryGetClrValue<CVec>(out a) || !right.TryGetClrValue<CVec>(out b))
return false;
return a == b;
}
public LuaValue this[LuaRuntime runtime, LuaValue key]
{
get
{
switch (key.ToString())
{
case "X": return X;
case "Y": return Y;
case "Facing": return Traits.Util.GetFacing(this, 0);
default: throw new LuaException("CVec does not define a member '{0}'".F(key));
}
}
set
{
throw new LuaException("WVec is read-only. Use CVec.New to create a new value");
}
}
#endregion
}
}

View File

@@ -74,6 +74,9 @@
<Package>mono.nat</Package>
<HintPath>..\thirdparty\Mono.Nat.dll</HintPath>
</Reference>
<Reference Include="Eluant">
<HintPath>..\thirdparty\Eluant.dll</HintPath>
</Reference>
<Reference Include="Tao.OpenAl, Version=1.1.0.1, Culture=neutral, PublicKeyToken=a7579dda88828311">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\thirdparty\Tao\Tao.OpenAl.dll</HintPath>
@@ -236,6 +239,13 @@
<Compile Include="Widgets\SpriteWidget.cs" />
<Compile Include="Widgets\SpriteSequenceWidget.cs" />
<Compile Include="Widgets\RGBASpriteWidget.cs" />
<Compile Include="Scripting\ScriptContext.cs" />
<Compile Include="Scripting\ScriptActorInterface.cs" />
<Compile Include="Scripting\ScriptObjectWrapper.cs" />
<Compile Include="Scripting\ScriptTypes.cs" />
<Compile Include="Scripting\ScriptMemberWrapper.cs" />
<Compile Include="Scripting\ScriptMemberExts.cs" />
<Compile Include="Scripting\ScriptPlayerInterface.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileSystem\D2kSoundResources.cs" />
@@ -359,4 +369,7 @@
<Target Name="AfterBuild">
</Target>
-->
<ItemGroup>
<Folder Include="Scripting\" />
</ItemGroup>
</Project>

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* 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,
@@ -8,12 +8,16 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.FileFormats;
using OpenRA.Network;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA
@@ -21,7 +25,7 @@ namespace OpenRA
public enum PowerState { Normal, Low, Critical };
public enum WinState { Won, Lost, Undefined };
public class Player
public class Player : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding
{
public Actor PlayerActor;
public WinState WinState = WinState.Undefined;
@@ -106,5 +110,35 @@ namespace OpenRA
// Observers are considered as allies
return p == null || Stances[p] == Stance.Ally;
}
#region Scripting interface
Lazy<ScriptPlayerInterface> luaInterface;
public void OnScriptBind(ScriptContext context)
{
luaInterface = Exts.Lazy(() => new ScriptPlayerInterface(context, this));
}
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
{
get { return luaInterface.Value[runtime, keyValue]; }
set { luaInterface.Value[runtime, keyValue] = value; }
}
public LuaValue Equals (LuaRuntime runtime, LuaValue left, LuaValue right)
{
Player a, b;
if (!left.TryGetClrValue<Player>(out a) || !right.TryGetClrValue<Player>(out b))
return false;
return a == b;
}
public LuaValue ToString(LuaRuntime runtime)
{
return "Player ({0})".F(PlayerName);
}
#endregion
}
}

View File

@@ -0,0 +1,45 @@
#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 System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
public class ScriptActorInterface : ScriptObjectWrapper
{
readonly Actor actor;
protected override string DuplicateKeyError(string memberName) { return "Actor '{0}' defines the command '{1}' on multiple traits".F(actor.Info.Name, memberName); }
protected override string MemberNotFoundError(string memberName) { return "Actor '{0}' does not define a property '{1}'".F(actor.Info.Name, memberName); }
public ScriptActorInterface(ScriptContext context, Actor actor)
: base(context)
{
this.actor = actor;
var args = new [] { actor };
var objects = context.ActorCommands[actor.Info].Select(cg =>
{
var groupCtor = cg.GetConstructor(new Type[] { typeof(Actor) });
return groupCtor.Invoke(args);
});
Bind(objects);
}
}
}

View File

@@ -0,0 +1,233 @@
#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 System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Eluant;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
// Tag interfaces specifying the type of bindings to create
public interface IScriptBindable { }
// For objects that need the context to create their bindings
public interface IScriptNotifyBind
{
void OnScriptBind(ScriptContext context);
}
// For traitinfos that provide actor / player commands
public class ScriptPropertyGroupAttribute : Attribute
{
public readonly string Category;
public ScriptPropertyGroupAttribute(string category) { Category = category; }
}
public class ScriptActorPropertyActivityAttribute : Attribute { }
public abstract class ScriptActorProperties
{
protected readonly Actor self;
public ScriptActorProperties(Actor self) { this.self = self; }
}
public abstract class ScriptPlayerProperties
{
protected readonly Player player;
public ScriptPlayerProperties(Player player) { this.player = player; }
}
// For global-level bindings
public abstract class ScriptGlobal : ScriptObjectWrapper
{
protected override string DuplicateKeyError(string memberName) { return "Table '{0}' defines multiple members '{1}'".F(Name, memberName); }
protected override string MemberNotFoundError(string memberName) { return "Table '{0}' does not define a property '{1}'".F(Name, memberName); }
public readonly string Name;
public ScriptGlobal(ScriptContext context)
: base(context)
{
// The 'this.' resolves the actual (subclass) type
var type = this.GetType();
var names = type.GetCustomAttributes<ScriptGlobalAttribute>(true);
if (names.Count() != 1)
throw new InvalidOperationException("[LuaGlobal] attribute not found for global table '{0}'".F(type));
Name = names.First().Name;
Bind(new [] { this });
}
}
public class ScriptGlobalAttribute : Attribute
{
public readonly string Name;
public ScriptGlobalAttribute(string name) { Name = name; }
}
public class ScriptContext
{
public World World { get; private set; }
public WorldRenderer WorldRenderer { get; private set; }
readonly MemoryConstrainedLuaRuntime runtime;
readonly LuaFunction tick;
// Restrict user scripts (excluding system libraries) to 50 MB of memory use
const int MaxUserScriptMemory = 50 * 1024 * 1024;
// Restrict the number of instructions that will be run per map function call
const int MaxUserScriptInstructions = 1000000;
readonly Type[] knownActorCommands;
public readonly Cache<ActorInfo, Type[]> ActorCommands;
public readonly Type[] PlayerCommands;
public ScriptContext(World world, WorldRenderer worldRenderer,
IEnumerable<string> scripts)
{
runtime = new MemoryConstrainedLuaRuntime();
World = world;
WorldRenderer = worldRenderer;
knownActorCommands = Game.modData.ObjectCreator
.GetTypesImplementing<ScriptActorProperties>()
.ToArray();
ActorCommands = new Cache<ActorInfo, Type[]>(FilterActorCommands);
PlayerCommands = Game.modData.ObjectCreator
.GetTypesImplementing<ScriptPlayerProperties>()
.ToArray();
runtime.DoBuffer(GlobalFileSystem.Open(Path.Combine("lua", "scriptwrapper.lua")).ReadAllText(), "scriptwrapper.lua").Dispose();
tick = (LuaFunction)runtime.Globals["Tick"];
// Register globals
using (var fn = runtime.CreateFunctionFromDelegate((Action<string>)FatalError))
runtime.Globals["FatalError"] = fn;
runtime.Globals["MaxUserScriptInstructions"] = MaxUserScriptInstructions;
using (var registerGlobal = (LuaFunction)runtime.Globals["RegisterSandboxedGlobal"])
{
using (var fn = runtime.CreateFunctionFromDelegate((Action<string>)Console.WriteLine))
registerGlobal.Call("print", fn).Dispose();
// Register global tables
var bindings = Game.modData.ObjectCreator.GetTypesImplementing<ScriptGlobal>();
foreach (var b in bindings)
{
var ctor = b.GetConstructors(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(c =>
{
var p = c.GetParameters();
return p.Length == 1 && p.First().ParameterType == typeof(ScriptContext);
});
if (ctor == null)
throw new InvalidOperationException("{0} must define a constructor that takes a ScriptContext context parameter".F(b.Name));
var binding = (ScriptGlobal)ctor.Invoke(new [] { this });
using (var obj = binding.ToLuaValue(this))
registerGlobal.Call(binding.Name, obj).Dispose();
}
}
// System functions do not count towards the memory limit
runtime.MaxMemoryUse = runtime.MemoryUse + MaxUserScriptMemory;
using (var loadScript = (LuaFunction)runtime.Globals["ExecuteSandboxedScript"])
{
foreach (var s in scripts)
loadScript.Call(s, GlobalFileSystem.Open(s).ReadAllText()).Dispose();
}
}
bool error;
public void FatalError(string message)
{
Console.WriteLine("Fatal Lua Error: {0}", message);
error = true;
}
public void RegisterMapActor(string name, Actor a)
{
using (var registerGlobal = (LuaFunction)runtime.Globals["RegisterSandboxedGlobal"])
{
if (runtime.Globals.ContainsKey(name))
throw new LuaException("The global name '{0}' is reserved, and may not be used by a map actor".F(name));
using (var obj = a.ToLuaValue(this))
registerGlobal.Call(name, obj).Dispose();
}
}
public void WorldLoaded()
{
if (error)
return;
using (var worldLoaded = (LuaFunction)runtime.Globals["WorldLoaded"])
worldLoaded.Call().Dispose();
}
public void Tick(Actor self)
{
if (error)
return;
using (new PerfSample("tick_lua"))
tick.Call().Dispose();
}
public void Dispose()
{
if (runtime == null)
return;
GC.SuppressFinalize(this);
runtime.Dispose();
}
~ScriptContext()
{
if (runtime != null)
Game.RunAfterTick(Dispose);
}
static Type[] ExtractRequiredTypes(Type t)
{
// Returns the inner types of all the Requires<T> interfaces on this type
var outer = t.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Requires<>));
return outer.SelectMany(i => i.GetGenericArguments()).ToArray();
}
static readonly object[] NoArguments = new object[0];
Type[] FilterActorCommands(ActorInfo ai)
{
var method = typeof(TypeDictionary).GetMethod("Contains");
return knownActorCommands.Where(c => ExtractRequiredTypes(c)
.All(t => (bool)method.MakeGenericMethod(t).Invoke(ai.Traits, NoArguments)))
.ToArray();
}
public LuaTable CreateTable() { return runtime.CreateTable(); }
}
}

View File

@@ -0,0 +1,74 @@
#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 System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
public static class ScriptMemberExts
{
static readonly Dictionary<string, string> LuaTypeNameReplacements = new Dictionary<string, string>()
{
{ "Void", "void" },
{ "Int32", "int" },
{ "String", "string" },
{ "Boolean", "bool" }
};
public static string LuaDocString(this Type t)
{
string ret;
if (!LuaTypeNameReplacements.TryGetValue(t.Name, out ret))
ret = t.Name;
return ret;
}
public static string LuaDocString(this ParameterInfo pi)
{
var ret = "{0} {1}".F(pi.ParameterType.LuaDocString(), pi.Name);
if (pi.IsOptional)
ret += " = {0}".F(pi.DefaultValue);
return ret;
}
public static string LuaDocString(this MemberInfo mi)
{
if (mi is MethodInfo)
{
var methodInfo = mi as MethodInfo;
var parameters = methodInfo.GetParameters().Select(pi => pi.LuaDocString());
return "{0} {1}({2})".F(methodInfo.ReturnType.LuaDocString(), mi.Name, parameters.JoinWith(", "));
}
if (mi is PropertyInfo)
{
var pi = mi as PropertyInfo;
var types = new List<string>();
if (pi.GetGetMethod() != null)
types.Add("get;");
if (pi.GetSetMethod() != null)
types.Add("set;");
return "{0} {1} {{ {2} }}".F(pi.PropertyType.LuaDocString(), mi.Name, types.JoinWith(" "));
}
return "Unknown field: {0}".F(mi.Name);
}
}
}

View File

@@ -0,0 +1,127 @@
#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 System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
public class ScriptMemberWrapper
{
readonly ScriptContext context;
public readonly object Target;
public readonly MemberInfo Member;
public readonly bool IsMethod;
public readonly bool IsGetProperty;
public readonly bool IsSetProperty;
public ScriptMemberWrapper(ScriptContext context, object target, MemberInfo mi)
{
this.context = context;
Target = target;
Member = mi;
var property = mi as PropertyInfo;
if (property != null)
{
IsGetProperty = property.GetGetMethod() != null;
IsSetProperty = property.GetSetMethod() != null;
}
else
IsMethod = true;
}
LuaValue Invoke(LuaVararg args)
{
if (!IsMethod)
throw new LuaException("Trying to invoke a ScriptMemberWrapper that isn't a method!");
var mi = Member as MethodInfo;
var pi = mi.GetParameters();
object[] clrArgs = new object[pi.Length];
var argCount = args.Count;
for (var i = 0; i < pi.Length; i++)
{
if (i >= argCount)
{
if (!pi[i].IsOptional)
throw new LuaException("Argument '{0}' of '{1}' is not optional.".F(pi[i].LuaDocString(), Member.LuaDocString()));
clrArgs[i] = pi[i].DefaultValue;
continue;
}
if (!args[i].TryGetClrValue(pi[i].ParameterType, out clrArgs[i]))
throw new LuaException("Unable to convert parameter {0} to {1}".F(i, pi[i].ParameterType.Name));
}
var ret = (Member as MethodInfo).Invoke(Target, clrArgs);
return ret.ToLuaValue(context);
}
public LuaValue Get(LuaRuntime runtime)
{
if (IsMethod)
return runtime.CreateFunctionFromDelegate((Func<LuaVararg, LuaValue>)Invoke);
if (IsGetProperty)
{
var pi = Member as PropertyInfo;
return pi.GetValue(Target, null).ToLuaValue(context);
}
throw new LuaException("The property '{0}' is write-only".F(Member.Name));
}
public void Set(LuaRuntime runtime, LuaValue value)
{
if (IsSetProperty)
{
var pi = Member as PropertyInfo;
object clrValue;
if (!value.TryGetClrValue(pi.PropertyType, out clrValue))
throw new LuaException("Unable to convert '{0}' to Clr type '{1}'".F(value.WrappedClrType().Name, pi.PropertyType));
pi.SetValue(Target, clrValue, null);
}
else
throw new LuaException("The property '{0}' is read-only".F(Member.Name));
}
public static IEnumerable<MemberInfo> WrappableMembers(Type t)
{
// Only expose defined public non-static methods that were explicitly declared by the author
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
return t.GetMembers(flags).Where(mi =>
{
// Properties are always wrappable
if (mi is PropertyInfo)
return true;
// Methods are allowed if they aren't generic, and aren't generated by the compiler
var method = mi as MethodInfo;
if (method != null && !method.IsGenericMethodDefinition && !method.IsSpecialName)
return true;
// Fields aren't allowed
return false;
});
}
}
}

View File

@@ -0,0 +1,77 @@
#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 System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
public abstract class ScriptObjectWrapper : IScriptBindable, ILuaTableBinding
{
protected abstract string DuplicateKeyError(string memberName);
protected abstract string MemberNotFoundError(string memberName);
protected readonly ScriptContext context;
Dictionary<string, ScriptMemberWrapper> members;
public ScriptObjectWrapper(ScriptContext context)
{
this.context = context;
}
protected void Bind(IEnumerable<object> clrObjects)
{
members = new Dictionary<string, ScriptMemberWrapper>();
foreach (var obj in clrObjects)
{
var wrappable = ScriptMemberWrapper.WrappableMembers(obj.GetType());
foreach (var m in wrappable)
{
if (members.ContainsKey(m.Name))
throw new LuaException(DuplicateKeyError(m.Name));
members.Add(m.Name, new ScriptMemberWrapper(context, obj, m));
}
}
}
public bool ContainsKey(string key) { return members.ContainsKey(key); }
public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
{
get
{
var name = keyValue.ToString();
ScriptMemberWrapper wrapper;
if (!members.TryGetValue(name, out wrapper))
throw new LuaException(MemberNotFoundError(name));
return wrapper.Get(runtime);
}
set
{
var name = keyValue.ToString();
ScriptMemberWrapper wrapper;
if (!members.TryGetValue(name, out wrapper))
throw new LuaException(MemberNotFoundError(name));
wrapper.Set(runtime, value);
}
}
}
}

View File

@@ -0,0 +1,45 @@
#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 System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
public class ScriptPlayerInterface : ScriptObjectWrapper
{
readonly Player player;
protected override string DuplicateKeyError(string memberName) { return "Player '{0}' defines the command '{1}' on multiple traits".F(player.PlayerName, memberName); }
protected override string MemberNotFoundError(string memberName) { return "Player '{0}' does not define a property '{1}'".F(player.PlayerName, memberName); }
public ScriptPlayerInterface(ScriptContext context, Player player)
: base(context)
{
this.player = player;
var args = new [] { player };
var objects = context.PlayerCommands.Select(cg =>
{
var groupCtor = cg.GetConstructor(new Type[] { typeof(Player) });
return groupCtor.Invoke(args);
});
Bind(objects);
}
}
}

View File

@@ -0,0 +1,153 @@
#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 System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Eluant;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
public static class LuaValueExts
{
public static Type WrappedClrType(this LuaValue value)
{
object inner;
if (value.TryGetClrObject(out inner))
return inner.GetType();
return value.GetType();
}
public static bool TryGetClrValue<T>(this LuaValue value, out T clrObject)
{
object temp;
var ret = value.TryGetClrValue(typeof(T), out temp);
clrObject = ret ? (T)temp : default(T);
return ret;
}
public static bool TryGetClrValue(this LuaValue value, Type t, out object clrObject)
{
object temp;
// Value wraps a CLR object
if (value.TryGetClrObject(out temp))
{
if (temp.GetType() == t)
{
clrObject = temp;
return true;
}
}
if (value is LuaNil && !t.IsValueType)
{
clrObject = null;
return true;
}
if (value is LuaBoolean && t.IsAssignableFrom(typeof(bool)))
{
clrObject = value.ToBoolean();
return true;
}
if (value is LuaNumber && t.IsAssignableFrom(typeof(double)))
{
clrObject = value.ToNumber().Value;
return true;
}
// Need an explicit test for double -> int
// TODO: Lua 5.3 will introduce an integer type, so this will be able to go away
if (value is LuaNumber && t.IsAssignableFrom(typeof(int)))
{
clrObject = (int)(value.ToNumber().Value);
return true;
}
if (value is LuaString && t.IsAssignableFrom(typeof(string)))
{
clrObject = value.ToString();
return true;
}
if (value is LuaFunction && t.IsAssignableFrom(typeof(LuaFunction)))
{
clrObject = value;
return true;
}
if (value is LuaTable && t.IsAssignableFrom(typeof(LuaTable)))
{
clrObject = value;
return true;
}
// Value isn't of the requested type.
// Set a default output value and return false
// Value types are assumed to specify a default constructor
clrObject = t.IsValueType ? Activator.CreateInstance(t) : null;
return false;
}
public static LuaValue ToLuaValue(this object obj, ScriptContext context)
{
if (obj is LuaValue)
return (LuaValue)obj;
if (obj == null)
return LuaNil.Instance;
if (obj is double)
return (LuaValue)(double)obj;
if (obj is int)
return (LuaValue)(int)obj;
if (obj is bool)
return (LuaValue)(bool)obj;
if (obj is string)
return (LuaValue)(string)obj;
if (obj is IScriptBindable)
{
// Object needs additional notification / context
var notify = obj as IScriptNotifyBind;
if (notify != null)
notify.OnScriptBind(context);
return new LuaCustomClrObject(obj);
}
throw new InvalidOperationException("Cannot convert type '{0}' to Lua. Class must implement IScriptBindable.".F(obj.GetType()));
}
public static LuaTable ToLuaTable(this IEnumerable collection, ScriptContext context)
{
var i = 1;
var table = context.CreateTable();
foreach (var x in collection)
table.Add(i++, x.ToLuaValue(context));
return table;
}
}
}

View File

@@ -10,13 +10,13 @@
using System.Collections.Generic;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.Scripting;
namespace OpenRA
{
/// <summary>
/// 3d World position - 1024 units = 1 cell.
/// </summary>
public struct WPos
public struct WPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding
{
public readonly int X, Y, Z;
@@ -59,6 +59,71 @@ namespace OpenRA
}
public override string ToString() { return "{0},{1},{2}".F(X, Y, Z); }
#region Scripting interface
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
WPos a;
WVec b;
if (!left.TryGetClrValue<WPos>(out a) || !right.TryGetClrValue<WVec>(out b))
throw new LuaException("Attempted to call WPos.Add(WPos, WVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b);
}
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
WPos a;
var rightType = right.WrappedClrType();
if (!left.TryGetClrValue<WPos>(out a))
throw new LuaException("Attempted to call WPos.Subtract(WPos, WVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType));
if (rightType == typeof(WPos))
{
WPos b;
right.TryGetClrValue<WPos>(out b);
return new LuaCustomClrObject(a - b);
}
else if (rightType == typeof(WVec))
{
WVec b;
right.TryGetClrValue<WVec>(out b);
return new LuaCustomClrObject(a - b);
}
throw new LuaException("Attempted to call WPos.Subtract(WPos, WVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, rightType));
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
WPos a, b;
if (!left.TryGetClrValue<WPos>(out a) || !right.TryGetClrValue<WPos>(out b))
return false;
return a == b;
}
public LuaValue this[LuaRuntime runtime, LuaValue key]
{
get
{
switch (key.ToString())
{
case "X": return X;
case "Y": return Y;
case "Z": return Z;
default: throw new LuaException("WPos does not define a member '{0}'".F(key));
}
}
set
{
throw new LuaException("WPos is read-only. Use WPos.New to create a new value");
}
}
#endregion
}
public static class IEnumerableExtensions

View File

@@ -9,13 +9,13 @@
#endregion
using System;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.Scripting;
namespace OpenRA
{
/// <summary>
/// 3d World vector for describing offsets and distances - 1024 units = 1 cell.
/// </summary>
public struct WVec
public struct WVec : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaUnaryMinusBinding, ILuaEqualityBinding, ILuaTableBinding
{
public readonly int X, Y, Z;
@@ -87,5 +87,61 @@ namespace OpenRA
}
public override string ToString() { return "{0},{1},{2}".F(X, Y, Z); }
#region Scripting interface
public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
{
WVec a, b;
if (!left.TryGetClrValue<WVec>(out a) || !right.TryGetClrValue<WVec>(out b))
throw new LuaException("Attempted to call WVec.Add(WVec, WVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a + b);
}
public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
{
WVec a, b;
if (!left.TryGetClrValue<WVec>(out a) || !right.TryGetClrValue<WVec>(out b))
throw new LuaException("Attempted to call WVec.Subtract(WVec, WVec) with invalid arguments ({0}, {1})".F(left.WrappedClrType().Name, right.WrappedClrType().Name));
return new LuaCustomClrObject(a - b);
}
public LuaValue Minus(LuaRuntime runtime)
{
return new LuaCustomClrObject(-this);
}
public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
{
WVec a, b;
if (!left.TryGetClrValue<WVec>(out a) || !right.TryGetClrValue<WVec>(out b))
return false;
return a == b;
}
public LuaValue this[LuaRuntime runtime, LuaValue key]
{
get
{
switch (key.ToString())
{
case "X": return X;
case "Y": return Y;
case "Z": return Z;
case "Facing": return Traits.Util.GetFacing(this, 0);
default: throw new LuaException("WVec does not define a member '{0}'".F(key));
}
}
set
{
throw new LuaException("WVec is read-only. Use WVec.New to create a new value");
}
}
#endregion
}
}