Merge pull request #5190 from pchote/new-lua

New Lua API.
This commit is contained in:
Matthias Mailänder
2014-05-02 13:29:28 +02:00
81 changed files with 3408 additions and 233 deletions

View File

@@ -37,7 +37,7 @@
CSC = dmcs
CSFLAGS = -nologo -warn:4 -debug:full -optimize- -codepage:utf8 -unsafe -warnaserror
DEFINE = DEBUG;TRACE
COMMON_LIBS = System.dll System.Core.dll System.Drawing.dll System.Xml.dll thirdparty/ICSharpCode.SharpZipLib.dll thirdparty/FuzzyLogicLibrary.dll thirdparty/Mono.Nat.dll thirdparty/MaxMind.Db.dll thirdparty/MaxMind.GeoIP2.dll
COMMON_LIBS = System.dll System.Core.dll System.Drawing.dll System.Xml.dll thirdparty/ICSharpCode.SharpZipLib.dll thirdparty/FuzzyLogicLibrary.dll thirdparty/Mono.Nat.dll thirdparty/MaxMind.Db.dll thirdparty/MaxMind.GeoIP2.dll thirdparty/Eluant.dll
@@ -175,7 +175,7 @@ editor_SRCS := $(shell find OpenRA.Editor/ -iname '*.cs')
editor_TARGET = OpenRA.Editor.exe
editor_KIND = winexe
editor_DEPS = $(game_TARGET)
editor_LIBS = System.Windows.Forms.dll System.Data.dll System.Drawing.dll $(editor_DEPS)
editor_LIBS = System.Windows.Forms.dll System.Data.dll System.Drawing.dll $(editor_DEPS) thirdparty/Eluant.dll
editor_EXTRA = -resource:OpenRA.Editor.Form1.resources -resource:OpenRA.Editor.MapSelect.resources
editor_FLAGS = -win32icon:OpenRA.Editor/OpenRA.Editor.Icon.ico
@@ -279,9 +279,15 @@ clean:
distclean: clean
platformdeps = "linux"
ifeq ($(shell uname),Darwin)
platformdeps = "osx"
endif
dependencies:
@ $(CP_R) thirdparty/*.dl* .
@ $(CP_R) thirdparty/Tao/* .
@ $(CP_R) thirdparty/${platformdeps}/* .
version: mods/ra/mod.yaml mods/cnc/mod.yaml mods/d2k/mod.yaml mods/modchooser/mod.yaml
@for i in $? ; do \
@@ -318,9 +324,11 @@ install-core: default
@$(CP_R) glsl "$(DATA_INSTALL_DIR)"
@$(CP_R) cg "$(DATA_INSTALL_DIR)"
@$(CP_R) lua "$(DATA_INSTALL_DIR)"
@$(CP) *.ttf "$(DATA_INSTALL_DIR)"
@$(CP) thirdparty/Tao/* "$(DATA_INSTALL_DIR)"
@$(CP) thirdparty/SDL2-CS* "$(DATA_INSTALL_DIR)"
@$(CP) thirdparty/Eluant* "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) thirdparty/ICSharpCode.SharpZipLib.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) thirdparty/FuzzyLogicLibrary.dll "$(DATA_INSTALL_DIR)"
@$(INSTALL_PROGRAM) thirdparty/SharpFont.dll "$(DATA_INSTALL_DIR)"

View File

@@ -71,6 +71,9 @@
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="Eluant">
<HintPath>..\thirdparty\Eluant.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ActorPropertiesDialog.cs">

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

@@ -109,9 +109,7 @@ namespace OpenRA.Graphics
{
RefreshPalette();
// workaround for #4965
// if (world.IsShellmap && !Game.Settings.Game.ShowShellmap)
if (world.IsShellmap)
if (world.IsShellmap && !Game.Settings.Game.ShowShellmap)
return;
var renderables = GenerateRenderables();

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
}
}

View File

@@ -217,9 +217,7 @@ namespace OpenRA
public void Tick()
{
// workaround for #4965
// if (!Paused && (!IsShellmap || Game.Settings.Game.ShowShellmap))
if (!Paused && !IsShellmap)
if (!Paused && (!IsShellmap || Game.Settings.Game.ShowShellmap))
{
WorldTick++;

View File

@@ -69,6 +69,9 @@
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Drawing" />
<Reference Include="Eluant">
<HintPath>..\thirdparty\Eluant.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Activities\HarvesterDockSequence.cs" />

View File

@@ -20,15 +20,11 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic
: base(widget, world)
{
var shellmapDecorations = widget.Get("SHELLMAP_DECORATIONS");
// workaround for #4965
// shellmapDecorations.IsVisible = () => menuType != MenuType.None && Game.Settings.Game.ShowShellmap;
shellmapDecorations.IsVisible = () => false;
shellmapDecorations.IsVisible = () => menuType != MenuType.None && Game.Settings.Game.ShowShellmap;
shellmapDecorations.Get<ImageWidget>("RECBLOCK").IsVisible = () => world.WorldTick / 25 % 2 == 0;
var shellmapDisabledDecorations = widget.Get("SHELLMAP_DISABLED_DECORATIONS");
// workaround for #4965
// shellmapDisabledDecorations.IsVisible = () => !Game.Settings.Game.ShowShellmap;
shellmapDisabledDecorations.IsVisible = () => true;
shellmapDisabledDecorations.IsVisible = () => !Game.Settings.Game.ShowShellmap;
}
}
}

View File

@@ -73,6 +73,9 @@
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Drawing" />
<Reference Include="Eluant">
<HintPath>..\thirdparty\Eluant.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ThrowsShrapnel.cs" />

View File

@@ -85,6 +85,9 @@
<Reference Include="MaxMind.GeoIP2">
<HintPath>..\thirdparty\MaxMind.GeoIP2.dll</HintPath>
</Reference>
<Reference Include="Eluant">
<HintPath>..\thirdparty\Eluant.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Activities\CaptureActor.cs" />
@@ -498,6 +501,25 @@
<Compile Include="Render\WithBuildingPlacedAnimation.cs" />
<Compile Include="StartGameNotification.cs" />
<Compile Include="Widgets\ConfirmationDialogs.cs" />
<Compile Include="Scripting\LuaScript.cs" />
<Compile Include="Scripting\CallLuaFunc.cs" />
<Compile Include="Scripting\Global\ActorGlobal.cs" />
<Compile Include="Scripting\Global\CoordinateGlobals.cs" />
<Compile Include="Scripting\Properties\ResourceProperties.cs" />
<Compile Include="Scripting\Properties\ProductionProperties.cs" />
<Compile Include="Scripting\Properties\MobileProperties.cs" />
<Compile Include="Scripting\Properties\GeneralProperties.cs" />
<Compile Include="Scripting\Properties\HealthProperties.cs" />
<Compile Include="Scripting\Properties\CombatProperties.cs" />
<Compile Include="Scripting\Global\MapGlobal.cs" />
<Compile Include="Scripting\Global\PlayerGlobal.cs" />
<Compile Include="Scripting\Global\UtilsGlobal.cs" />
<Compile Include="Scripting\Global\TriggerGlobal.cs" />
<Compile Include="Scripting\ScriptTriggers.cs" />
<Compile Include="Scripting\Properties\TransportProperties.cs" />
<Compile Include="Scripting\Global\CameraGlobal.cs" />
<Compile Include="Scripting\Properties\ChronosphereProperties.cs" />
<Compile Include="Scripting\ScriptInvulnerable.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
@@ -543,4 +565,8 @@ copy "FuzzyLogicLibrary.dll" "$(SolutionDir)"
cd "$(SolutionDir)"</PostBuildEvent>
</PropertyGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Scripting\Global\" />
<Folder Include="Scripting\Properties\" />
</ItemGroup>
</Project>

View File

@@ -56,22 +56,25 @@ namespace OpenRA.Mods.RA
var fi = producee.Traits.Get<IFacingInfo>();
var initialFacing = exitinfo.Facing < 0 ? Util.GetFacing(to - spawn, fi.GetInitialFacing()) : exitinfo.Facing;
var newUnit = self.World.CreateActor(producee.Name, new TypeDictionary
self.World.AddFrameEndTask(w =>
{
new OwnerInit(self.Owner),
new LocationInit(exit),
new CenterPositionInit(spawn),
new FacingInit(initialFacing)
var newUnit = self.World.CreateActor(producee.Name, new TypeDictionary
{
new OwnerInit(self.Owner),
new LocationInit(exit),
new CenterPositionInit(spawn),
new FacingInit(initialFacing)
});
var move = newUnit.Trait<IMove>();
if (exitinfo.MoveIntoWorld)
newUnit.QueueActivity(move.MoveIntoWorld(newUnit, exit));
var target = MoveToRallyPoint(self, newUnit, exit);
newUnit.SetTargetLine(Target.FromCell(target), Color.Green, false);
foreach (var t in self.TraitsImplementing<INotifyProduction>())
t.UnitProduced(self, newUnit, exit);
});
var move = newUnit.Trait<IMove>();
if (exitinfo.MoveIntoWorld)
newUnit.QueueActivity(move.MoveIntoWorld(newUnit, exit));
var target = MoveToRallyPoint(self, newUnit, exit);
newUnit.SetTargetLine(Target.FromCell(target), Color.Green, false);
foreach (var t in self.TraitsImplementing<INotifyProduction>())
t.UnitProduced(self, newUnit, exit);
}
static CPos MoveToRallyPoint(Actor self, Actor newUnit, CPos exitLocation)

View File

@@ -0,0 +1,57 @@
#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 Eluant;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Activities
{
public class CallLuaFunc : Activity
{
LuaFunction function;
public CallLuaFunc(LuaFunction func)
{
function = func.CopyReference() as LuaFunction;
}
public override Activity Tick(Actor self)
{
if (function != null)
function.Call().Dispose();
Dispose();
return NextActivity;
}
public override void Cancel(Actor self)
{
Dispose();
base.Cancel(self);
}
public void Dispose()
{
if (function == null)
return;
GC.SuppressFinalize(this);
function.Dispose();
function = null;
}
~CallLuaFunc()
{
if (function != null)
Game.RunAfterTick(Dispose);
}
}
}

View File

@@ -0,0 +1,87 @@
#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 Eluant;
using OpenRA.FileFormats;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Mods.RA.Air;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
[ScriptGlobal("Actor")]
public class ActorGlobal : ScriptGlobal
{
public ActorGlobal(ScriptContext context) : base(context) { }
[Desc("Create a new actor. initTable specifies a list of key-value pairs that definite initial parameters for the actor's traits.")]
public Actor Create(string type, bool addToWorld, LuaTable initTable)
{
var initDict = new TypeDictionary();
// Convert table entries into ActorInits
foreach (var kv in initTable)
{
// Find the requested type
var typeName = kv.Key.ToString();
var initType = Game.modData.ObjectCreator.FindType(typeName + "Init");
if (initType == null)
throw new LuaException("Unknown initializer type '{0}'".F(typeName));
// Cast it up to an IActorInit<T>
var genericType = initType.GetInterfaces()
.First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IActorInit<>));
var innerType = genericType.GetGenericArguments().First();
// Try and coerce the table value to the required type
object value;
if (!kv.Value.TryGetClrValue(innerType, out value))
throw new LuaException("Invalid data type for '{0}' (expected '{1}')".F(typeName, innerType.Name));
// Construct the ActorInit. Phew!
var test = initType.GetConstructor(new[] { innerType }).Invoke(new[] { value });
initDict.Add(test);
}
// The actor must be added to the world at the end of the tick
var a = context.World.CreateActor(false, type, initDict);
if (addToWorld)
context.World.AddFrameEndTask(w => w.Add(a));
return a;
}
[Desc("Returns the build time (in ticks) of the requested unit type")]
public int BuildTime(string type)
{
ActorInfo ai;
if (!Rules.Info.TryGetValue(type, out ai))
throw new LuaException("Unknown actor type '{0}'".F(type));
return ai.GetBuildTime();
}
[Desc("Returns the cruise altitude of the requested unit type (zero if it ground-based).")]
public int CruiseAltitude(string type)
{
ActorInfo ai;
if (!Rules.Info.TryGetValue(type, out ai))
throw new LuaException("Unknown actor type '{0}'".F(type));
var pi = ai.Traits.GetOrDefault<PlaneInfo>();
return pi != null ? pi.CruiseAltitude.Range : 0;
}
}
}

View File

@@ -0,0 +1,33 @@
#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.Generic;
using System.Linq;
using Eluant;
using OpenRA.Effects;
using OpenRA.Scripting;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptGlobal("Camera")]
public class CameraGlobal : ScriptGlobal
{
public CameraGlobal(ScriptContext context)
: base(context) { }
[Desc("The center of the visible viewport.")]
public WPos Position
{
get { return context.WorldRenderer.Viewport.CenterPosition; }
set { context.WorldRenderer.Viewport.Center(value); }
}
}
}

View File

@@ -0,0 +1,60 @@
#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
namespace OpenRA.Scripting
{
[ScriptGlobal("CPos")]
public class CPosGlobal : ScriptGlobal
{
public CPosGlobal(ScriptContext context) : base(context) { }
[Desc("Create a new CPos with the specified coordinates.")]
public CPos New(int x, int y) { return new CPos(x, y); }
[Desc("The cell coordinate origin.")]
public CPos Zero { get { return CPos.Zero; } }
}
[ScriptGlobal("CVec")]
public class CVecGlobal : ScriptGlobal
{
public CVecGlobal(ScriptContext context) : base(context) { }
[Desc("Create a new CVec with the specified coordinates.")]
public CVec New(int x, int y) { return new CVec(x, y); }
[Desc("The cell zero-vector.")]
public CVec Zero { get { return CVec.Zero; } }
}
[ScriptGlobal("WPos")]
public class WPosGlobal : ScriptGlobal
{
public WPosGlobal(ScriptContext context) : base(context) { }
[Desc("Create a new WPos with the specified coordinates.")]
public WPos New(int x, int y, int z) { return new WPos(x, y, z); }
[Desc("The world coordinate origin.")]
public WPos Zero { get { return WPos.Zero; } }
}
[ScriptGlobal("WVec")]
public class WVecGlobal : ScriptGlobal
{
public WVecGlobal(ScriptContext context) : base(context) { }
[Desc("Create a new WVec with the specified coordinates.")]
public WVec New(int x, int y, int z) { return new WVec(x, y, z); }
[Desc("The world zero-vector.")]
public WVec Zero { get { return WVec.Zero; } }
}
}

View File

@@ -0,0 +1,90 @@
#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.Generic;
using System.Linq;
using Eluant;
using OpenRA.Effects;
using OpenRA.Scripting;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptGlobal("Map")]
public class MapGlobal : ScriptGlobal
{
SpawnMapActors sma;
public MapGlobal(ScriptContext context) : base(context)
{
sma = context.World.WorldActor.Trait<SpawnMapActors>();
// Register map actors as globals (yuck!)
foreach (var kv in sma.Actors)
context.RegisterMapActor(kv.Key, kv.Value);
}
[Desc("Returns a table of all actors within the requested region, filtered using the specified function.")]
public LuaTable ActorsInCircle(WPos location, WRange radius, LuaFunction filter)
{
var actors = context.World.FindActorsInCircle(location, radius)
.Select(a => a.ToLuaValue(context))
.Where(a =>
{
using (var f = filter.Call(a))
return f.First().ToBoolean();
});
return actors.ToLuaTable(context);
}
[Desc("Returns a random cell inside the visible region of the map.")]
public CPos RandomCell()
{
return context.World.ChooseRandomCell(context.World.SharedRandom);
}
[Desc("Returns a random cell on the visible border of the map.")]
public CPos RandomEdgeCell()
{
return context.World.ChooseRandomEdgeCell();
}
[Desc("Returns true if there is only one human player.")]
public bool IsSinglePlayer { get { return context.World.LobbyInfo.IsSinglePlayer; } }
[Desc("Returns the difficulty selected by the player before starting the mission.")]
public string Difficulty { get { return context.World.LobbyInfo.GlobalSettings.Difficulty; } }
[Desc("Returns a table of all the actors that were specified in the map file.")]
public LuaTable NamedActors
{
get
{
return sma.Actors.Values.ToLuaTable(context);
}
}
[Desc("Returns the actor that was specified with a given name in " +
"the map file (or nil, if the actor is dead or not found")]
public Actor NamedActor(string actorName)
{
Actor ret;
if (!sma.Actors.TryGetValue(actorName, out ret))
return null;
return ret;
}
[Desc("Returns true if actor was originally specified in the map file.")]
public bool IsNamedActor(Actor actor)
{
return actor.ActorID <= sma.LastMapActorID && actor.ActorID > sma.LastMapActorID - sma.Actors.Count;
}
}
}

View File

@@ -0,0 +1,46 @@
#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 Eluant;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Scripting
{
[ScriptGlobal("Player")]
public class PlayerGlobal : ScriptGlobal
{
public PlayerGlobal(ScriptContext context) : base(context) { }
[Desc("Returns the player with the specified internal name, or nil if a match is not found.")]
public Player GetPlayer(string name)
{
return context.World.Players.FirstOrDefault(p => p.InternalName == name);
}
[Desc("Returns a table of players filtered by the specified function.")]
public LuaTable GetPlayers(LuaFunction filter)
{
var players = context.World.Players
.Select(p => p.ToLuaValue(context))
.Where(a =>
{
using (var f = filter.Call(a))
return f.First().ToBoolean();
});
return players.ToLuaTable(context);
}
}
}

View File

@@ -0,0 +1,112 @@
#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.Generic;
using System.Linq;
using Eluant;
using OpenRA.Effects;
using OpenRA.Scripting;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptGlobal("Trigger")]
public class TriggerGlobal : ScriptGlobal
{
public TriggerGlobal(ScriptContext context) : base(context) { }
ScriptTriggers GetScriptTriggers(Actor a)
{
var events = a.TraitOrDefault<ScriptTriggers>();
if (events == null)
throw new LuaException("Actor '{0}' requires the ScriptTriggers trait before attaching a trigger".F(a.Info.Name));
return events;
}
[Desc("Call a function after a specified delay. The callback function will be called as func().")]
public void AfterDelay(int delay, LuaFunction func)
{
var f = func.CopyReference() as LuaFunction;
Action doCall = () =>
{
try
{
using (f)
f.Call();
}
catch (LuaException e)
{
context.FatalError(e.Message);
}
};
context.World.AddFrameEndTask(w => w.Add(new DelayedAction(delay, doCall)));
}
[Desc("Call a function each tick that the actor is idle. " +
"The callback function will be called as func(Actor self).")]
public void OnIdle(Actor a, LuaFunction func)
{
GetScriptTriggers(a).RegisterIdleCallback(func, context);
}
[Desc("Call a function when the actor is damaged. The callback " +
"function will be called as func(Actor self, Actor attacker).")]
public void OnDamaged(Actor a, LuaFunction func)
{
GetScriptTriggers(a).RegisterDamagedCallback(func, context);
}
[Desc("Call a function when the actor is killed. The callback " +
"function will be called as func(Actor self, Actor killer).")]
public void OnKilled(Actor a, LuaFunction func)
{
GetScriptTriggers(a).RegisterKilledCallback(func, context);
}
[Desc("Call a function when all of the actors in a group are killed. The callback " +
"function will be called as func().")]
public void OnAllKilled(LuaTable actors, LuaFunction func)
{
List<Actor> group = new List<Actor>();
foreach (var kv in actors)
{
Actor actor;
if (!kv.Value.TryGetClrValue<Actor>(out actor))
throw new LuaException("OnAllKilled requires a table of int,Actor pairs. Recieved {0},{1}".F(kv.Key.GetType().Name, kv.Value.GetType().Name));
group.Add(actor);
}
var copy = (LuaFunction)func.CopyReference();
Action<Actor> OnMemberKilled = m =>
{
group.Remove(m);
if (!group.Any())
{
copy.Call();
copy.Dispose();
}
};
foreach (var a in group)
GetScriptTriggers(a).OnKilledInternal += OnMemberKilled;
}
[Desc("Call a function when this actor produces another actor. " +
"The callback function will be called as func(Actor producer, Actor produced).")]
public void OnProduction(Actor a, LuaFunction func)
{
GetScriptTriggers(a).RegisterProductionCallback(func, context);
}
}
}

View File

@@ -0,0 +1,100 @@
#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.Linq;
using Eluant;
using OpenRA.Effects;
using OpenRA.Scripting;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptGlobal("Utils")]
public class UtilsGlobal : ScriptGlobal
{
public UtilsGlobal(ScriptContext context) : base(context) { }
[Desc("Calls a function on every value in table.")]
public void Do(LuaTable table, LuaFunction func)
{
foreach (var kv in table)
func.Call(kv.Value).Dispose();
}
[Desc("Returns true if func returns true for any value in table.")]
public bool Any(LuaTable table, LuaFunction func)
{
foreach (var kv in table)
{
using (var ret = func.Call(kv.Value))
{
var result = ret.FirstOrDefault();
if (result != null && result.ToBoolean())
return true;
}
}
return false;
}
[Desc("Returns true if func returns true for all values in table.")]
public bool All(LuaTable table, LuaFunction func)
{
foreach (var kv in table)
{
using (var ret = func.Call(kv.Value))
{
var result = ret.FirstOrDefault();
if (result == null || !result.ToBoolean())
return false;
}
}
return true;
}
[Desc("Returns a random value from table.")]
public LuaValue Random(LuaTable table)
{
return table.Values.Random<LuaValue>(context.World.SharedRandom);
}
[Desc("Expands the given footprint one step along the coordinate axes, and (if requested) diagonals")]
public LuaTable ExpandFootprint(LuaTable cells, bool allowDiagonal)
{
var footprint = cells.Values.Select(c =>
{
CPos cell;
if (!c.TryGetClrValue<CPos>(out cell))
throw new LuaException("ExpandFootprint only accepts a table of CPos");
return cell;
});
var expanded = Traits.Util.ExpandFootprint(footprint, allowDiagonal);
return expanded.ToLuaTable(context);
}
[Desc("Returns a random integer x in the range low &lt;= x &lt; high.")]
public int RandomInteger(int low, int high)
{
if (high <= low)
return low;
return context.World.SharedRandom.Next(low, high);
}
[Desc("Returns the center of a cell in world coordinates.")]
public WPos CenterOfCell(CPos cell)
{
return cell.CenterPosition;
}
}
}

View File

@@ -0,0 +1,46 @@
#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 OpenRA.Graphics;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
public class LuaScriptInfo : ITraitInfo, Requires<SpawnMapActorsInfo>
{
public readonly string[] Scripts = { };
public object Create(ActorInitializer init) { return new LuaScript(this); }
}
public class LuaScript : ITick, IWorldLoaded
{
readonly LuaScriptInfo info;
ScriptContext context;
public LuaScript(LuaScriptInfo info)
{
this.info = info;
}
public void WorldLoaded(World world, WorldRenderer worldRenderer)
{
var scripts = info.Scripts ?? new string[0];
context = new ScriptContext(world, worldRenderer, scripts);
context.WorldLoaded();
}
public void Tick(Actor self)
{
context.Tick(self);
}
}
}

View File

@@ -46,6 +46,9 @@ namespace OpenRA.Mods.RA.Scripting
public void WorldLoaded(World w, WorldRenderer wr)
{
Game.Debug("Warning: This map uses the deprecated scripting interface, which will be removed in a future release. " +
"If you are the map author, then please see the OpenRA wiki for instructions on how to migrate to the new API.");
world = w;
sma = world.WorldActor.Trait<SpawnMapActors>();

View File

@@ -0,0 +1,43 @@
#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.Linq;
using Eluant;
using OpenRA;
using OpenRA.Mods.RA;
using OpenRA.Mods.RA.Activities;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("Support Powers")]
public class ChronsphereProperties : ScriptActorProperties, Requires<ChronoshiftPowerInfo>
{
public ChronsphereProperties(Actor self)
: base(self) { }
[Desc("Chronoshift a group of actors. A duration of 0 will teleport the actors permanently.")]
public void Chronoshift(LuaTable unitLocationPairs, int duration = 0, bool killCargo = false)
{
foreach (var kv in unitLocationPairs)
{
Actor actor;
CPos cell;
if (!kv.Key.TryGetClrValue<Actor>(out actor) || !kv.Value.TryGetClrValue<CPos>(out cell))
throw new LuaException("Chronoshift requires a table of Actor,CPos pairs. Received {0},{1}".F(kv.Key.WrappedClrType().Name, kv.Value.WrappedClrType().Name));
var cs = actor.TraitOrDefault<Chronoshiftable>();
if (cs != null && cs.CanChronoshiftTo(actor, cell))
cs.Teleport(actor, cell, duration, killCargo, self);
}
}
}
}

View File

@@ -0,0 +1,42 @@
#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 OpenRA;
using OpenRA.FileFormats;
using OpenRA.Mods.RA;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Move;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("Combat")]
public class CombatProperties : ScriptActorProperties, Requires<AttackBaseInfo>, Requires<IMoveInfo>
{
public CombatProperties(Actor self) : base(self) { }
[ScriptActorPropertyActivity]
[Desc("Seek out and attack nearby targets.")]
public void Hunt()
{
self.QueueActivity(new Hunt(self));
}
[ScriptActorPropertyActivity]
[Desc("Move to a cell, but stop and attack anything within range on the way. " +
"closeEnough defines an optional range (in cells) that will be considered " +
"close enough to complete the activity.")]
public void AttackMove(CPos cell, int closeEnough = 0)
{
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Move.Move(cell, WRange.FromCells(closeEnough))));
}
}
}

View File

@@ -0,0 +1,139 @@
#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 Eluant;
using OpenRA;
using OpenRA.FileFormats;
using OpenRA.Mods.RA;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Move;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("General")]
public class GeneralProperties : ScriptActorProperties
{
readonly IFacing facing;
readonly AutoTarget autotarget;
public GeneralProperties(Actor self) : base(self)
{
facing = self.TraitOrDefault<IFacing>();
autotarget = self.TraitOrDefault<AutoTarget>();
}
[Desc("Specifies whether the actor is in the world.")]
public bool IsInWorld
{
get
{
return self.IsInWorld;
}
set
{
if (value)
self.World.AddFrameEndTask(w => w.Add(self));
else
self.World.AddFrameEndTask(w => w.Remove(self));
}
}
[Desc("Specifies whether the actor is idle (not performing any activities).")]
public bool IsIdle { get { return self.IsIdle; } }
[Desc("The actor position in cell coordinates.")]
public CPos Location { get { return self.Location; } }
[Desc("The actor position in world coordinates.")]
public WPos CenterPosition { get { return self.CenterPosition; } }
[Desc("The player that owns the actor.")]
public Player Owner { get { return self.Owner; } }
[Desc("The direction that the actor is facing.")]
public int Facing
{
get
{
if (facing == null)
throw new LuaException("Actor '{0}' doesn't define a facing".F(self));
return facing.Facing;
}
}
[ScriptActorPropertyActivity]
[Desc("Instantly moves the actor to the specified cell.")]
public void Teleport(CPos cell)
{
self.QueueActivity(new SimpleTeleport(cell));
}
[ScriptActorPropertyActivity]
[Desc("Run an arbitrary lua function.")]
public void CallFunc(LuaFunction func)
{
self.QueueActivity(new CallLuaFunc(func));
}
[ScriptActorPropertyActivity]
[Desc("Wait for a specified number of game ticks (25 ticks = 1 second).")]
public void Wait(int ticks)
{
self.QueueActivity(new Wait(ticks));
}
[ScriptActorPropertyActivity]
[Desc("Remove the actor from the game, without triggering any death notification.")]
public void Destroy()
{
self.QueueActivity(new RemoveSelf());
}
[Desc("Attempt to cancel any active activities.")]
public void Stop()
{
self.CancelActivity();
}
[Desc("Current actor stance. Returns nil if this actor doesn't support stances.")]
public string Stance
{
get
{
if (autotarget == null)
return null;
return autotarget.Stance.ToString();
}
set
{
if (autotarget == null)
return;
UnitStance stance;
if (!Enum<UnitStance>.TryParse(value, true, out stance))
throw new LuaException("Unknown stance type '{0}'".F(value));
autotarget.Stance = stance;
}
}
[Desc("Test whether an actor has a specific property.")]
public bool HasProperty(string name)
{
return self.HasScriptProperty(name);
}
}
}

View File

@@ -0,0 +1,63 @@
#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 Eluant;
using OpenRA;
using OpenRA.FileFormats;
using OpenRA.Mods.RA;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Move;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("General")]
public class HealthProperties : ScriptActorProperties, Requires<HealthInfo>
{
Health health;
public HealthProperties(Actor self)
: base(self)
{
health = self.Trait<Health>();
}
[Desc("Current health of the actor.")]
public int Health
{
get { return health.HP; }
set { health.InflictDamage(self, self, health.HP - value, null, true); }
}
[Desc("Maximum health of the actor.")]
public int MaxHealth { get { return health.MaxHP; } }
[Desc("Specifies whether the actor is alive or dead.")]
public bool IsDead { get { return health.IsDead; } }
}
[ScriptPropertyGroup("General")]
public class InvulnerableProperties : ScriptActorProperties, Requires<ScriptInvulnerableInfo>
{
ScriptInvulnerable invulnerable;
public InvulnerableProperties(Actor self)
: base(self)
{
invulnerable = self.Trait<ScriptInvulnerable>();
}
[Desc("Set or query unit invulnerablility.")]
public bool Invulnerable
{
get { return invulnerable.Invulnerable; }
set { invulnerable.Invulnerable = value; }
}
}
}

View File

@@ -0,0 +1,40 @@
#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 OpenRA;
using OpenRA.FileFormats;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Move;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("Movement")]
public class MobileProperties : ScriptActorProperties, Requires<MobileInfo>
{
public MobileProperties(Actor self) : base(self) { }
[ScriptActorPropertyActivity]
[Desc("Moves within the cell grid. closeEnough defines an optional range " +
"(in cells) that will be considered close enough to complete the activity.")]
public void Move(CPos cell, int closeEnough = 0)
{
self.QueueActivity(new Move.Move(cell, WRange.FromCells(closeEnough)));
}
[ScriptActorPropertyActivity]
[Desc("Moves within the cell grid, ignoring lane biases.")]
public void ScriptedMove(CPos cell)
{
self.QueueActivity(new Move.Move(cell));
}
}
}

View File

@@ -0,0 +1,43 @@
#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.Linq;
using Eluant;
using OpenRA;
using OpenRA.Mods.RA;
using OpenRA.Mods.RA.Activities;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("Production")]
public class ProductionProperties : ScriptActorProperties, Requires<ProductionInfo>
{
readonly Production p;
public ProductionProperties(Actor self)
: base(self)
{
p = self.Trait<Production>();
}
[ScriptActorPropertyActivity]
[Desc("Build a unit, ignoring the production queue. The activity will wait if the exit is blocked")]
public void Produce(string actorType)
{
ActorInfo actorInfo;
if (!Rules.Info.TryGetValue(actorType, out actorInfo))
throw new LuaException("Unknown actor type '{0}'".F(actorType));
self.QueueActivity(new WaitFor(() => p.Produce(self, actorInfo)));
}
}
}

View File

@@ -0,0 +1,46 @@
#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 OpenRA;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("Resources")]
public class ResourceProperties : ScriptPlayerProperties, Requires<PlayerResourcesInfo>
{
readonly PlayerResources pr;
public ResourceProperties(Player player)
: base(player)
{
pr = player.PlayerActor.Trait<PlayerResources>();
}
[Desc("The amount of harvestable resources held by the player.")]
public int Resources
{
get { return pr.Ore; }
set { pr.Ore = value.Clamp(0, pr.OreCapacity); }
}
[Desc("The maximum resource storage of the player.")]
public int ResourceCapacity { get { return pr.OreCapacity; } }
[Desc("The amount of cash held by the player.")]
public int Cash
{
get { return pr.Cash; }
set { pr.Cash = Math.Max(0, value); }
}
}
}

View File

@@ -0,0 +1,66 @@
#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.Linq;
using Eluant;
using OpenRA;
using OpenRA.Mods.RA;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Air;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[ScriptPropertyGroup("Transports")]
public class TransportProperties : ScriptActorProperties, Requires<CargoInfo>
{
readonly Cargo cargo;
public TransportProperties(Actor self)
: base(self)
{
cargo = self.Trait<Cargo>();
}
[Desc("Specifies whether transport has any passengers.")]
public bool HasPassengers { get { return cargo.Passengers.Any(); } }
[Desc("Teleport an existing actor inside this transport.")]
public void LoadPassenger(Actor a) { cargo.Load(self, a); }
[ScriptActorPropertyActivity]
[Desc("Command transport to unload passengers.")]
public void UnloadPassengers()
{
self.QueueActivity(new UnloadCargo(self, true));
}
}
[ScriptPropertyGroup("Transports")]
public class ParadropPowers : ScriptActorProperties, Requires<CargoInfo>, Requires<ParaDropInfo>
{
readonly ParaDrop paradrop;
public ParadropPowers(Actor self)
: base(self)
{
paradrop = self.Trait<ParaDrop>();
}
[ScriptActorPropertyActivity]
[Desc("Command transport to paradrop passengers near the target cell.")]
public void Paradrop(CPos cell)
{
paradrop.SetLZ(cell);
self.QueueActivity(new FlyAttack(Target.FromCell(cell)));
}
}
}

View File

@@ -0,0 +1,28 @@
#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 OpenRA.GameRules;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
[Desc("Allows map scripts to make this actor invulnerable via actor.Invulnerable = true.")]
class ScriptInvulnerableInfo : TraitInfo<ScriptInvulnerable> {}
class ScriptInvulnerable : IDamageModifier
{
public bool Invulnerable = false;
public float GetDamageModifier(Actor attacker, WarheadInfo warhead)
{
return Invulnerable ? 0.0f : 1.0f;
}
}
}

View File

@@ -0,0 +1,121 @@
#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.Generic;
using System.Linq;
using Eluant;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Scripting
{
[Desc("Allows map scripts to attach triggers to this actor via the Triggers global.")]
public class ScriptTriggersInfo : TraitInfo<ScriptTriggers> { }
public class ScriptTriggers : INotifyIdle, INotifyDamage, INotifyKilled, INotifyProduction
{
public event Action<Actor> OnKilledInternal = _ => {};
List<Pair<LuaFunction, ScriptContext>> onIdle = new List<Pair<LuaFunction, ScriptContext>>();
List<Pair<LuaFunction, ScriptContext>> onDamaged = new List<Pair<LuaFunction, ScriptContext>>();
List<Pair<LuaFunction, ScriptContext>> onKilled = new List<Pair<LuaFunction, ScriptContext>>();
List<Pair<LuaFunction, ScriptContext>> onProduction = new List<Pair<LuaFunction, ScriptContext>>();
public void RegisterIdleCallback(LuaFunction func, ScriptContext context)
{
onIdle.Add(Pair.New((LuaFunction)func.CopyReference(), context));
}
public void RegisterDamagedCallback(LuaFunction func, ScriptContext context)
{
onDamaged.Add(Pair.New((LuaFunction)func.CopyReference(), context));
}
public void RegisterKilledCallback(LuaFunction func, ScriptContext context)
{
onKilled.Add(Pair.New((LuaFunction)func.CopyReference(), context));
}
public void RegisterProductionCallback(LuaFunction func, ScriptContext context)
{
onProduction.Add(Pair.New((LuaFunction)func.CopyReference(), context));
}
public void TickIdle(Actor self)
{
foreach (var f in onIdle)
{
var a = self.ToLuaValue(f.Second);
f.First.Call(a).Dispose();
a.Dispose();
}
}
public void Damaged(Actor self, AttackInfo e)
{
foreach (var f in onDamaged)
{
var a = self.ToLuaValue(f.Second);
var b = e.Attacker.ToLuaValue(f.Second);
f.First.Call(a, b).Dispose();
a.Dispose();
b.Dispose();
}
}
public void Killed(Actor self, AttackInfo e)
{
// Run lua callbacks
foreach (var f in onKilled)
{
var a = self.ToLuaValue(f.Second);
var b = e.Attacker.ToLuaValue(f.Second);
f.First.Call(a, b).Dispose();
a.Dispose();
b.Dispose();
}
// Run any internally bound callbacks
OnKilledInternal(self);
}
public void UnitProduced(Actor self, Actor other, CPos exit)
{
foreach (var f in onProduction)
{
var a = self.ToLuaValue(f.Second);
var b = other.ToLuaValue(f.Second);
f.First.Call(a, b).Dispose();
a.Dispose();
b.Dispose();
}
}
bool disposed;
public void Dispose()
{
disposed = true;
var toDispose = new [] { onIdle, onDamaged, onKilled, onProduction };
foreach (var f in toDispose.SelectMany(f => f))
f.First.Dispose();
GC.SuppressFinalize(this);
}
~ScriptTriggers()
{
if (!disposed)
Game.RunAfterTick(Dispose);
}
}
}

View File

@@ -117,16 +117,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic
BindCheckboxPref(panel, "PIXELDOUBLE_CHECKBOX", ds, "PixelDouble");
BindCheckboxPref(panel, "FRAME_LIMIT_CHECKBOX", ds, "CapFramerate");
// workaround for #4965
// BindCheckboxPref(panel, "SHOW_SHELLMAP", gs, "ShowShellmap");
BindCheckboxPref(panel, "SHOW_SHELLMAP", gs, "ShowShellmap");
BindCheckboxPref(panel, "ALWAYS_SHOW_STATUS_BARS_CHECKBOX", gs, "AlwaysShowStatusBars");
BindCheckboxPref(panel, "TEAM_HEALTH_COLORS_CHECKBOX", gs, "TeamHealthColors");
// workaround for #4965
var shellmapCheckbox = panel.Get<CheckboxWidget>("SHOW_SHELLMAP");
shellmapCheckbox.IsDisabled = () => true;
shellmapCheckbox.IsChecked = () => false;
var languageDropDownButton = panel.Get<DropDownButtonWidget>("LANGUAGE_DROPDOWNBUTTON");
languageDropDownButton.OnMouseDown = _ => ShowLanguageDropdown(languageDropDownButton);
languageDropDownButton.GetText = () => FieldLoader.Translate(ds.Language);

View File

@@ -36,6 +36,9 @@
<Reference Include="System.Data" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="Eluant">
<HintPath>..\thirdparty\Eluant.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

View File

@@ -17,10 +17,14 @@ using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Scripting;
using OpenRA.Traits;
namespace OpenRA.Utility
@@ -340,6 +344,157 @@ namespace OpenRA.Utility
Console.Write(doc.ToString());
}
static string[] RequiredTraitNames(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<>));
// Get the inner types
var inner = outer.SelectMany(i => i.GetGenericArguments()).ToArray();
// Remove the namespace and the trailing "Info"
return inner.Select(i => i.Name.Split(new [] { '.' }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault())
.Select(s => s.EndsWith("Info") ? s.Remove(s.Length - 4, 4) : s)
.ToArray();
}
[Desc("MOD", "Generate Lua API documentation in MarkDown format.")]
public static void ExtractLuaDocs(string[] args)
{
Game.modData = new ModData(args[1]);
Rules.LoadRules(Game.modData.Manifest, new Map());
Console.WriteLine("This is an automatically generated lising of the new Lua map scripting API, generated for {0} of OpenRA.", Game.modData.Manifest.Mod.Version);
Console.WriteLine();
Console.WriteLine("OpenRA allows custom maps and missions to be scripted using Lua 5.1.\n" +
"These scripts run in a sandbox that prevents access to unsafe functions (e.g. OS or file access), " +
"and limits the memory and CPU usage of the scripts.");
Console.WriteLine();
Console.WriteLine("You can access this interface by adding the [LuaScript](Traits#luascript) trait to the world actor in your map rules (note, you must replace the spaces in the snippet below with a single tab for each level of indentation):");
Console.WriteLine("```\nRules:\n\tWorld:\n\t\tLuaScript:\n\t\t\tScripts: myscript.lua\n```");
Console.WriteLine();
Console.WriteLine("Map scripts can interact with the game engine in three ways:\n" +
"* Global tables provide functions for interacting with the global world state, or performing general helper tasks.\n" +
"They exist in the global namespace, and can be called directly using ```<table name>.<function name>```.\n" +
"* Individual actors expose a collection of properties and commands that query information of modify their state.\n" +
" * Some commands, marked as <em>queued activity</em>, are asynchronous. Activities are queued on the actor, and will run in " +
"sequence until the queue is empty or the Stop command is called. Actors that are not performing an activity are Idle " +
"(actor.IsIdle will return true). The properties and commands available on each actor depends on the traits that the actor " +
"specifies in its rule definitions.\n" +
"* Individual players explose a collection of properties and commands that query information of modify their state.\n" +
"The properties and commands available on each actor depends on the traits that the actor specifies in its rule definitions.\n");
Console.WriteLine();
var tables = Game.modData.ObjectCreator.GetTypesImplementing<ScriptGlobal>()
.OrderBy(t => t.Name);
Console.WriteLine("<h3>Global Tables</h3>");
foreach (var t in tables)
{
var name = t.GetCustomAttributes<ScriptGlobalAttribute>(true).First().Name;
var members = ScriptMemberWrapper.WrappableMembers(t);
Console.WriteLine("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", name);
foreach (var m in members.OrderBy(m => m.Name))
{
string desc = m.HasAttribute<DescAttribute>() ? m.GetCustomAttributes<DescAttribute>(true).First().Lines.JoinWith("\n") : "";
Console.WriteLine("<tr><td align=\"right\" width=\"50%\"><strong>{0}</strong></td><td>{1}</td></tr>".F(m.LuaDocString(), desc));
}
Console.WriteLine("</table>");
}
Console.WriteLine("<h3>Actor Properties / Commands</h3>");
var actorCategories = Game.modData.ObjectCreator.GetTypesImplementing<ScriptActorProperties>().SelectMany(cg =>
{
var catAttr = cg.GetCustomAttributes<ScriptPropertyGroupAttribute>(false).FirstOrDefault();
var category = catAttr != null ? catAttr.Category : "Unsorted";
var required = RequiredTraitNames(cg);
return ScriptMemberWrapper.WrappableMembers(cg).Select(mi => Tuple.Create(category, mi, required));
}).GroupBy(g => g.Item1).OrderBy(g => g.Key);
foreach (var kv in actorCategories)
{
Console.WriteLine("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", kv.Key);
foreach (var property in kv.OrderBy(p => p.Item2.Name))
{
var mi = property.Item2;
var required = property.Item3;
var hasDesc = mi.HasAttribute<DescAttribute>();
var hasRequires = required.Any();
var isActivity = mi.HasAttribute<ScriptActorPropertyActivityAttribute>();
Console.WriteLine("<tr><td width=\"50%\" align=\"right\"><strong>{0}</strong>", mi.LuaDocString());
if (isActivity)
Console.WriteLine("<br /><em>Queued Activity</em>");
Console.WriteLine("</td><td>");
if (hasDesc)
Console.WriteLine(mi.GetCustomAttributes<DescAttribute>(false).First().Lines.JoinWith("\n"));
if (hasDesc && hasRequires)
Console.WriteLine("<br />");
if (hasRequires)
Console.WriteLine("<b>Requires {1}:</b> {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits"));
Console.WriteLine("</td></tr>");
}
Console.WriteLine("</table>");
}
Console.WriteLine("<h3>Player Properties / Commands</h3>");
var playerCategories = Game.modData.ObjectCreator.GetTypesImplementing<ScriptPlayerProperties>().SelectMany(cg =>
{
var catAttr = cg.GetCustomAttributes<ScriptPropertyGroupAttribute>(false).FirstOrDefault();
var category = catAttr != null ? catAttr.Category : "Unsorted";
var required = RequiredTraitNames(cg);
return ScriptMemberWrapper.WrappableMembers(cg).Select(mi => Tuple.Create(category, mi, required));
}).GroupBy(g => g.Item1).OrderBy(g => g.Key);
foreach (var kv in playerCategories)
{
Console.WriteLine("<table align=\"center\" width=\"1024\"><tr><th colspan=\"2\" width=\"1024\">{0}</th></tr>", kv.Key);
foreach (var property in kv.OrderBy(p => p.Item2.Name))
{
var mi = property.Item2;
var required = property.Item3;
var hasDesc = mi.HasAttribute<DescAttribute>();
var hasRequires = required.Any();
var isActivity = mi.HasAttribute<ScriptActorPropertyActivityAttribute>();
Console.WriteLine("<tr><td width=\"50%\" align=\"right\"><strong>{0}</strong>", mi.LuaDocString());
if (isActivity)
Console.WriteLine("<br /><em>Queued Activity</em>");
Console.WriteLine("</td><td>");
if (hasDesc)
Console.WriteLine(mi.GetCustomAttributes<DescAttribute>(false).First().Lines.JoinWith("\n"));
if (hasDesc && hasRequires)
Console.WriteLine("<br />");
if (hasRequires)
Console.WriteLine("<b>Requires {1}:</b> {0}".F(required.JoinWith(", "), required.Length == 1 ? "Trait" : "Traits"));
Console.WriteLine("</td></tr>");
}
Console.WriteLine("</table>");
}
}
[Desc("MAPFILE", "Generate hash of specified oramap file.")]
public static void GetMapHash(string[] args)
{

View File

@@ -71,6 +71,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\thirdparty\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="Eluant">
<HintPath>..\thirdparty\Eluant.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Command.cs" />

View File

@@ -27,6 +27,7 @@ namespace OpenRA.Utility
{ "--remap", Command.RemapShp },
{ "--transpose", Command.TransposeShp },
{ "--docs", Command.ExtractTraitDocs },
{ "--lua-docs", Command.ExtractLuaDocs },
{ "--map-hash", Command.GetMapHash },
{ "--map-preview", Command.GenerateMinimap },
{ "--map-upgrade-v5", Command.UpgradeV5Map },

View File

@@ -242,6 +242,13 @@ namespace OpenRA.Utility
node.Key = "MaxDistance";
}
// Added new Lua API
if (engineVersion < 20140421)
{
if (depth == 0 && node.Value.Nodes.Any(n => n.Key == "LuaScriptEvents"))
node.Value.Nodes.Add(new MiniYamlNode("ScriptTriggers", ""));
}
UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1);
}
}

163
lua/sandbox.lua Normal file
View File

@@ -0,0 +1,163 @@
local sandbox = {
_VERSION = "sandbox 0.5",
_DESCRIPTION = "A pure-lua solution for running untrusted Lua code.",
_URL = "https://github.com/kikito/sandbox.lua",
_LICENSE = [[
MIT LICENSE
Copyright (c) 2013 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
-- The base environment is merged with the given env option (or an empty table, if no env provided)
--
local BASE_ENV = {}
-- List of non-safe packages/functions:
--
-- * string.rep: can be used to allocate millions of bytes in 1 operation
-- * {set|get}metatable: can be used to modify the metatable of global objects (strings, integers)
-- * collectgarbage: can affect performance of other systems
-- * dofile: can access the server filesystem
-- * _G: It has access to everything. It can be mocked to other things though.
-- * load{file|string}: All unsafe because they can grant acces to global env
-- * raw{get|set|equal}: Potentially unsafe
-- * module|require|module: Can modify the host settings
-- * string.dump: Can display confidential server info (implementation of functions)
-- * string.rep: Can allocate millions of bytes in one go
-- * math.randomseed: Can affect the host sytem
-- * io.*, os.*: Most stuff there is non-save
-- Safe packages/functions below
([[
_VERSION assert error ipairs next pairs
pcall select tonumber tostring type unpack xpcall
coroutine.create coroutine.resume coroutine.running coroutine.status
coroutine.wrap coroutine.yield
math.abs math.acos math.asin math.atan math.atan2 math.ceil
math.cos math.cosh math.deg math.exp math.fmod math.floor
math.frexp math.huge math.ldexp math.log math.log10 math.max
math.min math.modf math.pi math.pow math.rad
math.sin math.sinh math.sqrt math.tan math.tanh
os.clock os.difftime os.time
string.byte string.char string.find string.format string.gmatch
string.gsub string.len string.lower string.match string.reverse
string.sub string.upper
table.insert table.maxn table.remove table.sort
]]):gsub('%S+', function(id)
local module, method = id:match('([^%.]+)%.([^%.]+)')
if module then
BASE_ENV[module] = BASE_ENV[module] or {}
BASE_ENV[module][method] = _G[module][method]
else
BASE_ENV[id] = _G[id]
end
end)
local function protect_module(module, module_name)
return setmetatable({}, {
__index = module,
__newindex = function(_, attr_name, _)
error('Can not modify ' .. module_name .. '.' .. attr_name .. '. Protected by the sandbox.')
end
})
end
('coroutine math os string table'):gsub('%S+', function(module_name)
BASE_ENV[module_name] = protect_module(BASE_ENV[module_name], module_name)
end)
-- auxiliary functions/variables
local string_rep = string.rep
local function merge(dest, source)
for k,v in pairs(source) do
dest[k] = dest[k] or v
end
return dest
end
local function sethook(f, key, quota)
if type(debug) ~= 'table' or type(debug.sethook) ~= 'function' then return end
debug.sethook(f, key, quota)
end
local function cleanup()
sethook()
string.rep = string_rep
end
-- Public interface: sandbox.protect
function sandbox.protect(f, options)
if type(f) == 'string' then f = assert(loadstring(f)) end
options = options or {}
local quota = false
if options.quota ~= false then
quota = options.quota or 500000
end
local env = merge(options.env or {}, BASE_ENV)
env._G = env._G or env
setfenv(f, env)
return function(...)
if quota then
local timeout = function()
cleanup()
error('Quota exceeded: ' .. tostring(quota))
end
sethook(timeout, "", quota)
end
string.rep = nil
local ok, result = pcall(f, ...)
cleanup()
if not ok then error(result) end
return result
end
end
-- Public interface: sandbox.run
function sandbox.run(f, options, ...)
return sandbox.protect(f, options)(...)
end
-- make sandbox(f) == sandbox.protect(f)
setmetatable(sandbox, {__call = function(_,f,o) return sandbox.protect(f,o) end})
return sandbox

44
lua/scriptwrapper.lua Normal file
View File

@@ -0,0 +1,44 @@
environment = {}
-- Reset package path
package.path = "./lua/?.lua;./mods/common/lua/?.lua"
-- Note: sandbox has been customized to remove math.random
local sandbox = require('sandbox')
local stp = require('stacktraceplus')
local PrintStackTrace = function(msg)
return stp.stacktrace("", 2) .. "\nError message\n===============\n" .. msg .. "\n==============="
end
local TryRunSandboxed = function(fn)
local success, err = xpcall(function() sandbox.run(fn, {env = environment, quota = MaxUserScriptInstructions}) end, PrintStackTrace)
if not success then
FatalError(err)
end
end
WorldLoaded = function()
if environment.WorldLoaded ~= nil then
TryRunSandboxed(environment.WorldLoaded)
end
end
Tick = function()
if environment.Tick ~= nil then
TryRunSandboxed(environment.Tick)
end
end
ExecuteSandboxedScript = function(file, contents)
local script = loadstring(contents, file)
if (script == nil) then
FatalError("Error parsing " .. file)
else
TryRunSandboxed(script)
end
end
RegisterSandboxedGlobal = function(key, value)
environment[key] = value
end

411
lua/stacktraceplus.lua Normal file
View File

@@ -0,0 +1,411 @@
-- tables
local _G = _G
local string, io, debug, coroutine = string, io, debug, coroutine
-- functions
local tostring, print, require = tostring, print, require
local next, assert = next, assert
local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs
local error = error
assert(debug, "debug table must be available at this point")
local io_open = io.open
local string_gmatch = string.gmatch
local string_sub = string.sub
local table_concat = table.concat
local _M = {
max_tb_output_len = 70 -- controls the maximum length of the 'stringified' table before cutting with ' (more...)'
}
-- this tables should be weak so the elements in them won't become uncollectable
local m_known_tables = { [_G] = "_G (global table)" }
local function add_known_module(name, desc)
local ok, mod = pcall(require, name)
if ok then
m_known_tables[mod] = desc
end
end
add_known_module("string", "string module")
add_known_module("io", "io module")
add_known_module("os", "os module")
add_known_module("table", "table module")
add_known_module("math", "math module")
add_known_module("package", "package module")
add_known_module("debug", "debug module")
add_known_module("coroutine", "coroutine module")
-- lua5.2
add_known_module("bit32", "bit32 module")
-- luajit
add_known_module("bit", "bit module")
add_known_module("jit", "jit module")
local m_user_known_tables = {}
local m_known_functions = {}
for _, name in ipairs{
-- Lua 5.2, 5.1
"assert",
"collectgarbage",
"dofile",
"error",
"getmetatable",
"ipairs",
"load",
"loadfile",
"next",
"pairs",
"pcall",
"print",
"rawequal",
"rawget",
"rawlen",
"rawset",
"require",
"select",
"setmetatable",
"tonumber",
"tostring",
"type",
"xpcall",
-- Lua 5.1
"gcinfo",
"getfenv",
"loadstring",
"module",
"newproxy",
"setfenv",
"unpack",
-- TODO: add table.* etc functions
} do
if _G[name] then
m_known_functions[_G[name]] = name
end
end
local m_user_known_functions = {}
local function safe_tostring (value)
local ok, err = pcall(tostring, value)
if ok then return err else return ("<failed to get printable value>: '%s'"):format(err) end
end
-- Private:
-- Parses a line, looking for possible function definitions (in a very na<6E>ve way)
-- Returns '(anonymous)' if no function name was found in the line
local function ParseLine(line)
assert(type(line) == "string")
--print(line)
local match = line:match("^%s*function%s+(%w+)")
if match then
--print("+++++++++++++function", match)
return match
end
match = line:match("^%s*local%s+function%s+(%w+)")
if match then
--print("++++++++++++local", match)
return match
end
match = line:match("^%s*local%s+(%w+)%s+=%s+function")
if match then
--print("++++++++++++local func", match)
return match
end
match = line:match("%s*function%s*%(") -- this is an anonymous function
if match then
--print("+++++++++++++function2", match)
return "(anonymous)"
end
return "(anonymous)"
end
-- Private:
-- Tries to guess a function's name when the debug info structure does not have it.
-- It parses either the file or the string where the function is defined.
-- Returns '?' if the line where the function is defined is not found
local function GuessFunctionName(info)
--print("guessing function name")
if type(info.source) == "string" and info.source:sub(1,1) == "@" then
local file, err = io_open(info.source:sub(2), "r")
if not file then
print("file not found: "..tostring(err)) -- whoops!
return "?"
end
local line
for i = 1, info.linedefined do
line = file:read("*l")
end
if not line then
print("line not found") -- whoops!
return "?"
end
return ParseLine(line)
else
local line
local lineNumber = 0
for l in string_gmatch(info.source, "([^\n]+)\n-") do
lineNumber = lineNumber + 1
if lineNumber == info.linedefined then
line = l
break
end
end
if not line then
print("line not found") -- whoops!
return "?"
end
return ParseLine(line)
end
end
---
-- Dumper instances are used to analyze stacks and collect its information.
--
local Dumper = {}
Dumper.new = function(thread)
local t = { lines = {} }
for k,v in pairs(Dumper) do t[k] = v end
t.dumping_same_thread = (thread == coroutine.running())
-- if a thread was supplied, bind it to debug.info and debug.get
-- we also need to skip this additional level we are introducing in the callstack (only if we are running
-- in the same thread we're inspecting)
if type(thread) == "thread" then
t.getinfo = function(level, what)
if t.dumping_same_thread and type(level) == "number" then
level = level + 1
end
return debug.getinfo(thread, level, what)
end
t.getlocal = function(level, loc)
if t.dumping_same_thread then
level = level + 1
end
return debug.getlocal(thread, level, loc)
end
else
t.getinfo = debug.getinfo
t.getlocal = debug.getlocal
end
return t
end
-- helpers for collecting strings to be used when assembling the final trace
function Dumper:add (text)
self.lines[#self.lines + 1] = text
end
function Dumper:add_f (fmt, ...)
self:add(fmt:format(...))
end
function Dumper:concat_lines ()
return table_concat(self.lines)
end
---
-- Private:
-- Iterates over the local variables of a given function.
--
-- @param level The stack level where the function is.
--
function Dumper:DumpLocals (level)
local prefix = "\t "
local i = 1
if self.dumping_same_thread then
level = level + 1
end
local name, value = self.getlocal(level, i)
if not name then
return
end
self:add("\tLocal variables:\r\n")
while name do
if type(value) == "number" then
self:add_f("%s%s = number: %g\r\n", prefix, name, value)
elseif type(value) == "boolean" then
self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value))
elseif type(value) == "string" then
self:add_f("%s%s = string: %q\r\n", prefix, name, value)
elseif type(value) == "userdata" then
self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value))
elseif type(value) == "nil" then
self:add_f("%s%s = nil\r\n", prefix, name)
elseif type(value) == "table" then
if m_known_tables[value] then
self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value])
elseif m_user_known_tables[value] then
self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value])
else
local txt = "{"
for k,v in pairs(value) do
txt = txt..safe_tostring(k)..":"..safe_tostring(v)
if #txt > _M.max_tb_output_len then
txt = txt.." (more...)"
break
end
if next(value, k) then txt = txt..", " end
end
self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}")
end
elseif type(value) == "function" then
local info = self.getinfo(value, "nS")
local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value]
if info.what == "C" then
self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value)))
else
local source = info.short_src
if source:sub(2,7) == "string" then
source = source:sub(9)
end
--for k,v in pairs(info) do print(k,v) end
fun_name = fun_name or GuessFunctionName(info)
self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source)
end
elseif type(value) == "thread" then
self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value))
end
i = i + 1
name, value = self.getlocal(level, i)
end
end
---
-- Public:
-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc.
-- This function is suitable to be used as an error handler with pcall or xpcall
--
-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread)
-- @param message An optional error string or object.
-- @param level An optional number telling at which level to start the traceback (default is 1)
--
-- Returns a string with the stack trace and a string with the original error.
--
function _M.stacktrace(thread, message, level)
if type(thread) ~= "thread" then
-- shift parameters left
thread, message, level = nil, thread, message
end
thread = thread or coroutine.running()
level = level or 1
local dumper = Dumper.new(thread)
local original_error
if type(message) == "table" then
dumper:add("an error object {\r\n")
local first = true
for k,v in pairs(message) do
if first then
dumper:add(" ")
first = false
else
dumper:add(",\r\n ")
end
dumper:add(safe_tostring(k))
dumper:add(": ")
dumper:add(safe_tostring(v))
end
dumper:add("\r\n}")
original_error = dumper:concat_lines()
elseif type(message) == "string" then
dumper:add(message)
original_error = message
end
dumper:add("\r\n")
dumper:add[[
Stack Traceback
===============
]]
--print(error_message)
local level_to_show = level
if dumper.dumping_same_thread then level = level + 1 end
local info = dumper.getinfo(level, "nSlf")
while info do
if info.what == "main" then
if string_sub(info.source, 1, 1) == "@" then
dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, string_sub(info.source, 2), info.currentline)
else
dumper:add_f("(%d) main chunk of %s at line %d\r\n", level_to_show, info.short_src, info.currentline)
end
elseif info.what == "C" then
--print(info.namewhat, info.name)
--for k,v in pairs(info) do print(k,v, type(v)) end
local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func)
dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name)
--dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value)))
elseif info.what == "tail" then
--print("tail")
--for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name)
dumper:add_f("(%d) tail call\r\n", level_to_show)
dumper:DumpLocals(level)
elseif info.what == "Lua" then
local source = info.short_src
local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name
if source:sub(2, 7) == "string" then
source = source:sub(9)
end
local was_guessed = false
if not function_name or function_name == "?" then
--for k,v in pairs(info) do print(k,v, type(v)) end
function_name = GuessFunctionName(info)
was_guessed = true
end
-- test if we have a file name
local function_type = (info.namewhat == "") and "function" or info.namewhat
if info.source and info.source:sub(1, 1) == "@" then
dumper:add_f("(%d) Lua %s '%s' at file '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "")
elseif info.source and info.source:sub(1,1) == '#' then
dumper:add_f("(%d) Lua %s '%s' at template '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "")
else
dumper:add_f("(%d) Lua %s '%s' at line %d of chunk '%s'\r\n", level_to_show, function_type, function_name, info.currentline, source)
end
dumper:DumpLocals(level)
else
dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what)
end
level = level + 1
level_to_show = level_to_show + 1
info = dumper.getinfo(level, "nSlf")
end
return dumper:concat_lines(), original_error
end
--
-- Adds a table to the list of known tables
function _M.add_known_table(tab, description)
if m_known_tables[tab] then
error("Cannot override an already known table")
end
m_user_known_tables[tab] = description
end
--
-- Adds a function to the list of known functions
function _M.add_known_function(fun, description)
if m_known_functions[fun] then
error("Cannot override an already known function")
end
m_user_known_functions[fun] = description
end
return _M

View File

@@ -77,7 +77,7 @@ elseif ($command -eq "dependencies")
{
cp thirdparty/*.dll .
cp thirdparty/Tao/*.dll .
cp packaging/windows/*.dll .
cp thirdparty/windows/*.dll .
echo "Dependencies copied."
}
else

View File

@@ -520,10 +520,10 @@ Actors:
Location: 11,52
Owner: Neutral
GDIReinforcementsEntry: waypoint
Location: 7, 50
Location: 7,50
Owner: Neutral
Auto1Trigger: waypoint
Location: 52, 47
Location: 52,47
Owner: Neutral
NodHeliEntry: waypoint
Location: 41,23
@@ -561,6 +561,7 @@ Rules:
-GiveCashCrateAction:
-ExplodeCrateAction@fire:
-CloakCrateAction:
ScriptTriggers:
Sequences:

View File

@@ -993,8 +993,8 @@ Rules:
PlayMusicOnMapLoad:
Music: map1
Loop: true
LuaScriptInterface:
LuaScripts: shellmap.lua
LuaScript:
Scripts: shellmap.lua
LoadWidgetAtGameStart:
Widget: MENU_BACKGROUND
LST:

View File

@@ -5,15 +5,14 @@ speed = 5
Tick = function()
ticks = ticks + 1
local t = (ticks + 45) % (360 * speed) * (math.pi / 180) / speed;
OpenRA.SetViewportCenterPosition(WPos.op_Addition(viewportOrigin, WVec.New(-15360 * math.sin(t), 4096 * math.cos(t))))
Camera.Position = viewportOrigin + WVec.New(-15360 * math.sin(t), 4096 * math.cos(t), 0)
end
WorldLoaded = function()
viewportOrigin = OpenRA.GetViewportCenterPosition()
CreateUnitsInTransport(lst1, { "htnk" });
CreateUnitsInTransport(lst2, { "mcv" });
CreateUnitsInTransport(lst3, { "htnk" });
viewportOrigin = Camera.Position
LoadTransport(lst1, "htnk")
LoadTransport(lst2, "mcv")
LoadTransport(lst3, "htnk")
local units = { boat1, boat2, boat3, boat4, lst1, lst2, lst3}
for i, unit in ipairs(units) do
LoopTrack(unit, CPos.New(8, unit.Location.Y), CPos.New(87, unit.Location.Y))
@@ -21,17 +20,11 @@ WorldLoaded = function()
end
LoopTrack = function(actor, left, right)
Actor.ScriptedMove(actor, left)
Actor.Teleport(actor, right)
Actor.CallFunc(actor, function() LoopTrack(actor, left, right) end)
actor.ScriptedMove(left)
actor.Teleport(right)
actor.CallFunc(function() LoopTrack(actor, left, right) end)
end
CreateUnitsInTransport = function(transport, passengerNames)
local cargo = Actor.Trait(transport, "Cargo")
local owner = Actor.Owner(transport)
local facing = Actor.Facing(transport)
for i, passengerName in ipairs(passengerNames) do
cargo:Load(transport, Actor.Create(passengerName, { AddToWorld = false, Owner = owner, Facing = { facing, "Int32" } }))
end
LoadTransport = function(transport, passenger)
transport.LoadPassenger(Actor.Create(passenger, false, { Owner = transport.Owner, Facing = transport.Facing }))
end

View File

@@ -42,6 +42,7 @@
UncloakSound: trans1.aud
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Tank:
AppearsOnRadar:
@@ -90,6 +91,7 @@
UncloakSound: trans1.aud
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Helicopter:
AppearsOnRadar:
@@ -122,6 +124,7 @@
UpdatesPlayerStatistics:
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Infantry:
AppearsOnRadar:
@@ -183,6 +186,7 @@
LuaScriptEvents:
DetectCloaked:
Range: 1
ScriptTriggers:
^CivInfantry:
Inherits: ^Infantry
@@ -261,6 +265,7 @@
UpdatesPlayerStatistics:
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Plane:
AppearsOnRadar:
@@ -280,6 +285,7 @@
Huntable:
AttackMove:
LuaScriptEvents:
ScriptTriggers:
^Ship:
AppearsOnRadar:
@@ -305,6 +311,7 @@
UpdatesPlayerStatistics:
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Building:
AppearsOnRadar:
@@ -348,6 +355,7 @@
Huntable:
LuaScriptEvents:
Demolishable:
ScriptTriggers:
^BaseBuilding:
Inherits: ^Building
@@ -389,6 +397,7 @@
FrozenUnderFog:
StartsRevealed: true
LuaScriptEvents:
ScriptTriggers:
^TechBuilding:
Inherits: ^CivBuilding
@@ -429,6 +438,7 @@
FrozenUnderFog:
StartsRevealed: true
LuaScriptEvents:
ScriptTriggers:
^Wall:
AppearsOnRadar:
@@ -460,6 +470,7 @@
BodyOrientation:
FrozenUnderFog:
LuaScriptEvents:
ScriptTriggers:
^Tree:
Tooltip:
@@ -484,6 +495,7 @@
FrozenUnderFog:
StartsRevealed: true
LuaScriptEvents:
ScriptTriggers:
^TibTree:
Tooltip:
@@ -520,6 +532,7 @@
FrozenUnderFog:
StartsRevealed: true
LuaScriptEvents:
ScriptTriggers:
^Husk:
Health:
@@ -545,6 +558,7 @@
BodyOrientation:
LuaScriptEvents:
DisabledOverlay:
ScriptTriggers:
^HelicopterHusk:
Inherits: ^Husk
@@ -573,4 +587,5 @@
DestroyedSound: xplobig4.aud
BodyOrientation:
LuaScriptEvents:
ScriptTriggers:

View File

@@ -38,6 +38,7 @@
Huntable:
LuaScriptEvents:
Demolishable:
ScriptTriggers:
^Tank:
AppearsOnRadar:
@@ -79,6 +80,7 @@
Huntable:
LuaScriptEvents:
Demolishable:
ScriptTriggers:
^Husk:
Health:
@@ -107,6 +109,7 @@
TransformOnCapture:
ForceHealthPercentage: 25
DisabledOverlay:
ScriptTriggers:
^TowerHusk:
Health:
@@ -126,6 +129,7 @@
Types: Husk
BodyOrientation:
LuaScriptEvents:
ScriptTriggers:
^AircraftHusk:
Inherits: ^Husk
@@ -190,6 +194,7 @@
UpdatesPlayerStatistics:
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Plane:
AppearsOnRadar:
@@ -214,6 +219,7 @@
Huntable:
AttackMove:
LuaScriptEvents:
ScriptTriggers:
^Helicopter:
Inherits: ^Plane
@@ -270,3 +276,5 @@
Weapons: shrapnel
Pieces: 3, 7
Range: 2c0, 5c0
ScriptTriggers:

View File

@@ -1,4 +1,4 @@
local ants = OpenRA.GetRandomInteger(0, 51) == 0
local ants = Utils.RandomInteger(0, 51) == 0
if ants then
UnitTypes = { "ant", "ant", "ant" }
@@ -29,85 +29,110 @@ else
{ SovietWarFactory1, { "3tnk", "4tnk", "v2rl", "ttnk", "apc" } }
}
end
ParadropPlaneType = "badr"
ParadropWaypointCount = 8
SendSovietUnits = function(entryCell, unitTypes, interval)
local units = Reinforcements.Reinforce(soviets, unitTypes, entryCell, entryCell, interval)
local team = Team.New(units)
Team.AddEventHandler(team.OnAllKilled, function()
SendSovietUnits(entryCell, unitTypes, interval)
end)
Team.Do(team, function(a)
Actor.OnDamaged(a, function()
if not Actor.CargoIsEmpty(a) then
Actor.Stop(a)
Actor.UnloadCargo(a, true)
ParadropWaypoints = { Paradrop1, Paradrop2, Paradrop3, Paradrop4, Paradrop5, Paradrop6, Paradrop7, Paradrop8 }
BindActorTriggers = function(a)
if a.HasProperty("Hunt") then
if a.Owner == allies then
Trigger.OnIdle(a, a.Hunt)
else
Trigger.OnIdle(a, function(a) a.AttackMove(AlliedTechnologyCenter.Location) end)
end
end
if a.HasProperty("HasPassengers") then
Trigger.OnDamaged(a, function()
if a.HasPassengers then
a.Stop()
a.UnloadPassengers()
end
end)
Actor.OnIdle(a, function() Actor.AttackMove(a, AlliedTechnologyCenter.Location) end)
end
end
SendSovietUnits = function(entryCell, unitTypes, interval)
local i = 0
team = {}
Utils.Do(unitTypes, function(type)
local a = Actor.Create(type, false, { Owner = soviets, Location = entryCell })
BindActorTriggers(a)
Trigger.AfterDelay(i * interval, function() a.IsInWorld = true end)
table.insert(team, a)
i = i + 1
end)
Trigger.OnAllKilled(team, function() SendSovietUnits(entryCell, unitTypes, interval) end)
end
ShipAlliedUnits = function()
local transport, reinforcements = Reinforcements.Insert(allies, "lst", { "1tnk", "1tnk", "jeep", "2tnk", "2tnk" }, { LstEntry.Location, LstUnload.Location }, { LstEntry.Location })
Utils.Do(reinforcements, function(a) Actor.OnIdle(a, Actor.Hunt) end)
OpenRA.RunAfterDelay(60 * 25, ShipAlliedUnits)
local transport = Actor.Create("lst", true, { Location = LstEntry.Location, Owner = allies })
Utils.Do({ "1tnk", "1tnk", "jeep", "2tnk", "2tnk" }, function(type)
local a = Actor.Create(type, false, { Owner = allies })
BindActorTriggers(a)
transport.LoadPassenger(a)
end)
transport.Move(LstUnload.Location)
transport.UnloadPassengers()
transport.Wait(50)
transport.Move(LstEntry.Location)
transport.Destroy()
Trigger.AfterDelay(60 * 25, ShipAlliedUnits)
end
ParadropSovietUnits = function()
local lz = Map.GetNamedActor("Paradrop" .. OpenRA.GetRandomInteger(1, ParadropWaypointCount - 1)).Location
local plane, passengers = SupportPowers.Paradrop(soviets, ParadropPlaneType, ParadropUnitTypes, Map.GetRandomEdgeCell(), lz)
Utils.Do(passengers, function(a) Actor.OnIdle(a, Actor.Hunt) end)
OpenRA.RunAfterDelay(35 * 25, ParadropSovietUnits)
local lz = Utils.Random(ParadropWaypoints).Location
local start = Utils.CenterOfCell(Map.RandomEdgeCell()) + WVec.New(0, 0, Actor.CruiseAltitude("badr"))
local transport = Actor.Create("badr", true, { CenterPosition = start, Owner = soviets, Facing = (Utils.CenterOfCell(lz) - start).Facing })
Utils.Do(ParadropUnitTypes, function(type)
local a = Actor.Create(type, false, { Owner = soviets })
BindActorTriggers(a)
transport.LoadPassenger(a)
end)
transport.Paradrop(lz)
Trigger.AfterDelay(35 * 25, ParadropSovietUnits)
end
ProduceUnits = function()
Utils.Do(ProducedUnitTypes, function(t)
local factory = t[1]
if not Actor.IsDead(factory) and not Production.PerFactoryQueueIsBusy(factory) then
local unitType = t[2][OpenRA.GetRandomInteger(1, #t[2] + 1)]
Production.BuildWithPerFactoryQueue(factory, unitType)
end
end)
OpenRA.RunAfterDelay(15, ProduceUnits)
ProduceUnits = function(t)
local factory = t[1]
if not factory.IsDead then
local unitType = t[2][Utils.RandomInteger(1, #t[2] + 1)]
factory.Wait(Actor.BuildTime(unitType))
factory.Produce(unitType)
factory.CallFunc(function() ProduceUnits(t) end)
end
end
SetupAlliedUnits = function()
for a in Utils.Enumerate(Map.GetNamedActors()) do
if Actor.Owner(a) == allies then
if Actor.HasTrait(a, "LuaScriptEvents") then
a:AddTrait(OpenRA.New("Invulnerable")) -- todo: replace
end
Actor.SetStance(a, "Defend")
Utils.Do(Map.NamedActors, function(a)
if a.Owner == allies and a.HasProperty("Invulnerable") then
a.Invulnerable = true
a.Stance = "Defend"
end
end
end
SetupFactories = function()
Utils.Do(ProducedUnitTypes, function(pair)
Actor.OnProduced(pair[1], function(self, other, ex)
Actor.Hunt(other)
Actor.OnDamaged(other, function()
if not Actor.CargoIsEmpty(other) then
Actor.Stop(other)
Actor.UnloadCargo(other, true)
end
end)
end)
end)
end
SetupFactories = function()
Utils.Do(ProducedUnitTypes, function(pair)
Trigger.OnProduction(pair[1], function(_, a) BindActorTriggers(a) end)
end)
end
ChronoshiftAlliedUnits = function()
local cells = Map.ExpandFootprint({ ChronoshiftLocation.Location }, false)
local cells = Utils.ExpandFootprint({ ChronoshiftLocation.Location }, false)
local units = { }
for i = 1, #cells do
local unit = Actor.Create("2tnk", { Owner = allies, Facing = { 0, "Int32" } })
Actor.OnIdle(unit, Actor.Hunt)
table.insert(units, { unit, cells[i] })
end
SupportPowers.Chronoshift(units, Chronosphere)
OpenRA.RunAfterDelay(60 * 25, ChronoshiftAlliedUnits)
local unit = Actor.Create("2tnk", true, { Owner = allies, Facing = 0 })
BindActorTriggers(unit)
units[unit] = cells[i]
end
Chronosphere.Chronoshift(units)
Trigger.AfterDelay(60 * 25, ChronoshiftAlliedUnits)
end
ticks = 0
@@ -117,33 +142,21 @@ Tick = function()
ticks = ticks + 1
local t = (ticks + 45) % (360 * speed) * (math.pi / 180) / speed;
OpenRA.SetViewportCenterPosition(WPos.op_Addition(viewportOrigin, WVec.New(19200 * math.sin(t), 20480 * math.cos(t))))
if ticks % 150 == 0 then
Utils.Do(Actor.ActorsWithTrait("AttackBase"), function(a)
if Actor.IsIdle(a) and not Map.IsNamedActor(a) and not Actor.IsDead(a) and Actor.IsInWorld(a) and (Actor.Owner(a) == soviets or Actor.Owner(a) == allies) then
Actor.Hunt(a)
end
end)
end
Camera.Position = viewportOrigin + WVec.New(19200 * math.sin(t), 20480 * math.cos(t), 0)
end
WorldLoaded = function()
allies = OpenRA.GetPlayer("Allies")
soviets = OpenRA.GetPlayer("Soviets")
viewportOrigin = OpenRA.GetViewportCenterPosition()
allies = Player.GetPlayer("Allies")
soviets = Player.GetPlayer("Soviets")
viewportOrigin = Camera.Position
SetupAlliedUnits()
SetupFactories()
ProduceUnits()
ShipAlliedUnits()
ParadropSovietUnits()
OpenRA.RunAfterDelay(5 * 25, ChronoshiftAlliedUnits)
OpenRA.GiveCash(allies, 1000000)
OpenRA.GiveCash(soviets, 1000000)
Trigger.AfterDelay(5 * 25, ChronoshiftAlliedUnits)
Utils.Do(ProducedUnitTypes, ProduceUnits)
SendSovietUnits(Entry1.Location, UnitTypes, 50)
SendSovietUnits(Entry2.Location, UnitTypes, 50)
SendSovietUnits(Entry3.Location, UnitTypes, 50)

View File

@@ -315,9 +315,6 @@ Actors:
Actor110: fcom
Location: 106,44
Owner: Soviets
Actor111: silo
Location: 96,28
Owner: Soviets
Actor106: fact
Location: 114,43
Owner: Soviets
@@ -477,9 +474,6 @@ Actors:
Actor105: brik
Location: 94,70
Owner: Allies
Actor154: silo
Location: 82,86
Owner: Allies
SovietBarracks1: barr
Location: 109,48
Owner: Soviets
@@ -956,30 +950,6 @@ Actors:
Actor195: hpad
Location: 70,75
Owner: Allies
Actor75: oilb
Location: 4,126
Owner: Allies
Actor334: oilb
Location: 6,126
Owner: Allies
Actor335: oilb
Location: 8,126
Owner: Allies
Actor336: oilb
Location: 10,126
Owner: Allies
Actor337: oilb
Location: 12,126
Owner: Allies
Actor338: oilb
Location: 14,126
Owner: Allies
Actor339: oilb
Location: 2,126
Owner: Allies
Actor340: oilb
Location: 0,126
Owner: Allies
Actor341: dome
Location: 63,73
Owner: Allies
@@ -1034,15 +1004,6 @@ Actors:
Actor361: pbox.e1
Location: 71,96
Owner: Allies
Actor55: silo
Location: 81,85
Owner: Allies
Actor76: silo
Location: 81,86
Owner: Allies
Actor159: silo
Location: 82,85
Owner: Allies
Actor365: hpad
Location: 64,78
Owner: Allies
@@ -1303,11 +1264,16 @@ Rules:
-CrateSpawner:
-SpawnMPUnits:
-MPStartLocations:
LuaScriptInterface:
LuaScripts: desert-shellmap.lua
ResourceType@ore:
ValuePerUnit: 0
LuaScript:
Scripts: desert-shellmap.lua
LoadWidgetAtGameStart:
Widget: MAINMENU
-StartGameNotification:
OILB:
CashTrickler:
ShowTicks: false
TRAN.Husk2:
Burns:
Damage: 0
@@ -1316,24 +1282,6 @@ Rules:
APC:
Cargo:
InitialUnits: e1, e1, e2, e3, e4
TENT:
ProductionQueue:
Type: Infantry
Group: Infantry
BuildSpeed: .4
LowPowerSlowdown: 3
BARR:
ProductionQueue:
Type: Infantry
Group: Infantry
BuildSpeed: .4
LowPowerSlowdown: 3
WEAP:
ProductionQueue:
Type: Vehicle
Group: Vehicle
BuildSpeed: .4
LowPowerSlowdown: 3
Ant:
Buildable:
Owner: soviet
@@ -1341,6 +1289,7 @@ Rules:
Health:
HP: 200
^Vehicle:
ScriptInvulnerable:
GivesBounty:
Percentage: 0
GainsExperience:
@@ -1349,6 +1298,7 @@ Rules:
ArmorModifier:
SpeedModifier:
^Tank:
ScriptInvulnerable:
GivesBounty:
Percentage: 0
GainsExperience:
@@ -1357,6 +1307,7 @@ Rules:
ArmorModifier:
SpeedModifier:
^Infantry:
ScriptInvulnerable:
-Selectable: # short-term hack to make infantry not play die sounds until we fix RenderInfantry
GivesBounty:
Percentage: 0
@@ -1366,12 +1317,15 @@ Rules:
ArmorModifier:
SpeedModifier:
^Ship:
ScriptInvulnerable:
GivesBounty:
Percentage: 0
^Plane:
ScriptInvulnerable:
GivesBounty:
Percentage: 0
^Building:
ScriptInvulnerable:
GivesBounty:
Percentage: 0

View File

@@ -52,6 +52,7 @@
CancelActivity: True
CaptureNotification:
Notification: UnitStolen
ScriptTriggers:
^Tank:
AppearsOnRadar:
@@ -107,6 +108,7 @@
CancelActivity: True
CaptureNotification:
Notification: UnitStolen
ScriptTriggers:
^Infantry:
AppearsOnRadar:
@@ -165,6 +167,7 @@
RequiresTech: InfantryHealing
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Ship:
AppearsOnRadar:
@@ -197,6 +200,7 @@
BodyOrientation:
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Plane:
AppearsOnRadar:
@@ -231,6 +235,7 @@
BodyOrientation:
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Helicopter:
Inherits: ^Plane
@@ -283,6 +288,7 @@
Huntable:
LuaScriptEvents:
Demolishable:
ScriptTriggers:
^Wall:
AppearsOnRadar:
@@ -319,6 +325,7 @@
BodyOrientation:
FrozenUnderFog:
LuaScriptEvents:
ScriptTriggers:
^TechBuilding:
Inherits: ^Building
@@ -426,6 +433,7 @@
FrozenUnderFog:
StartsRevealed: true
LuaScriptEvents:
ScriptTriggers:
^Husk:
Husk:
@@ -455,6 +463,7 @@
TransformOnCapture:
ForceHealthPercentage: 25
DisabledOverlay:
ScriptTriggers:
^HelicopterHusk:
Inherits: ^Husk
@@ -499,6 +508,7 @@
AutoTargetIgnore:
BodyOrientation:
LuaScriptEvents:
ScriptTriggers:
^Rock:
Tooltip:
@@ -520,6 +530,7 @@
FrozenUnderFog:
StartsRevealed: true
LuaScriptEvents:
ScriptTriggers:
^DesertCivBuilding:
Inherits: ^CivBuilding

View File

@@ -39,6 +39,7 @@
Huntable:
LuaScriptEvents:
Demolishable:
ScriptTriggers:
^Wall:
AppearsOnRadar:
@@ -77,6 +78,7 @@
BodyOrientation:
LuaScriptEvents:
Demolishable:
ScriptTriggers:
^Infantry:
AppearsOnRadar:
@@ -129,6 +131,7 @@
BodyOrientation:
Huntable:
LuaScriptEvents:
ScriptTriggers:
^CivilianInfantry:
Inherits: ^Infantry
@@ -199,6 +202,7 @@
CameraPitch: 90
Huntable:
LuaScriptEvents:
ScriptTriggers:
^Helicopter:
AppearsOnRadar:
@@ -232,4 +236,5 @@
CameraPitch: 90
Huntable:
LuaScriptEvents:
ScriptTriggers:

11
packaging/linux/buildpackage.sh Normal file → Executable file
View File

@@ -1,24 +1,29 @@
#!/bin/bash
# OpenRA packaging master script for linux packages
if [ $# -ne "3" ]; then
echo "Usage: `basename $0` version files-dir outputdir"
if [ $# -ne "4" ]; then
echo "Usage: `basename $0` tag files-dir platform-files-dir outputdir"
exit 1
fi
TAG=$1
VERSION=`echo $TAG | grep -o "[0-9]\\+-\\?[0-9]\\?"`
BUILTDIR=$2
PACKAGEDIR=$3
DEPSDIR=$3
PACKAGEDIR=$4
ROOTDIR=root
# Clean up
rm -rf $ROOTDIR
cd ../..
# Copy files for OpenRA.Game.exe and OpenRA.Editor.exe as well as all dependencies.
make install-all prefix="/usr" DESTDIR="$PWD/packaging/linux/$ROOTDIR"
# Native library dependencies
cp "$DEPSDIR"/* "$PWD/packaging/linux/$ROOTDIR/usr/lib/openra/" || exit 3
# Launch scripts (executed by Desura)
cp *.sh "$PWD/packaging/linux/$ROOTDIR/usr/lib/openra/" || exit 3

View File

@@ -3,7 +3,7 @@ Version: {VERSION}
Architecture: all
Maintainer: Chris Forbes <chrisf@ijw.co.nz>
Installed-Size: {SIZE}
Depends: libopenal1, mono-runtime (>= 2.10), libmono-system-drawing4.0-cil, libmono-system-windows-forms4.0-cil, libfreetype6, libsdl1.2debian, libgl1-mesa-glx, libgl1-mesa-dri
Depends: libopenal1, mono-runtime (>= 2.10), libmono-system-core4.0-cil, libmono-system-drawing4.0-cil, libmono-system-windows-forms4.0-cil, libfreetype6, libsdl1.2debian, libgl1-mesa-glx, libgl1-mesa-dri
Section: games
Priority: extra
Homepage: http://www.open-ra.org/

View File

@@ -1,8 +1,8 @@
#!/bin/bash
# OpenRA packaging script for Mac OSX
if [ $# -ne "3" ]; then
echo "Usage: `basename $0` tag files-dir outputdir"
if [ $# -ne "4" ]; then
echo "Usage: `basename $0` tag files-dir platform-files-dir outputdir"
exit 1
fi
@@ -13,9 +13,8 @@ if [ -e "OpenRA.app" ]; then
fi
# Copy the template to build the game package
# Assumes it is layed out with the correct directory structure
cp -rv template.app OpenRA.app
cp -rv $2/* "OpenRA.app/Contents/Resources/" || exit 3
cp -rv $2/* $3/* "OpenRA.app/Contents/Resources/" || exit 3
# Icon isn't used, and editor doesn't work.
rm OpenRA.app/Contents/Resources/OpenRA.ico
@@ -34,5 +33,5 @@ rm temp
# Package app bundle into a zip and clean up
zip OpenRA-$1 -r -9 OpenRA.app
mv OpenRA-$1.zip $3
mv OpenRA-$1.zip $4
rm -rf OpenRA.app

View File

@@ -30,7 +30,7 @@ markdown DOCUMENTATION.md > DOCUMENTATION.html
# List of files that are packaged on all platforms
FILES=('OpenRA.Game.exe' 'OpenRA.Editor.exe' 'OpenRA.Utility.exe' \
'OpenRA.Renderer.SdlCommon.dll' 'OpenRA.Renderer.Sdl2.dll' 'OpenRA.Renderer.Cg.dll' 'OpenRA.Renderer.Gl.dll' 'OpenRA.Renderer.Null.dll' 'OpenRA.Irc.dll' \
'FreeSans.ttf' 'FreeSansBold.ttf' \
'FreeSans.ttf' 'FreeSansBold.ttf' 'lua' \
'cg' 'glsl' 'mods/common' 'mods/ra' 'mods/cnc' 'mods/d2k' 'mods/modchooser' \
'AUTHORS' 'COPYING' \
'README.html' 'CONTRIBUTING.html' 'DOCUMENTATION.html' 'CHANGELOG.html' \
@@ -59,10 +59,13 @@ cp thirdparty/SDL2-CS* packaging/built
# Mono.NAT for UPnP support
cp thirdparty/Mono.Nat.dll packaging/built
# Lua
# (legacy) Lua
cp thirdparty/KopiLua.dll packaging/built
cp thirdparty/NLua.dll packaging/built
# Eluant (new lua)
cp thirdparty/Eluant* packaging/built
# GeoIP database access
cp thirdparty/MaxMind.Db.dll packaging/built
cp thirdparty/MaxMind.GeoIP2.dll packaging/built
@@ -81,7 +84,7 @@ echo "Creating packages..."
(
cd windows
makensis -DSRCDIR="$BUILTDIR" OpenRA.nsi &> package.log
makensis -DSRCDIR="$BUILTDIR" -DDEPSDIR="${SRCDIR}/thirdparty/windows" OpenRA.nsi &> package.log
if [ $? -eq 0 ]; then
mv OpenRA.exe "$OUTPUTDIR"/OpenRA-$TAG.exe
else
@@ -91,7 +94,7 @@ echo "Creating packages..."
(
cd osx
sh buildpackage.sh "$TAG" "$BUILTDIR" "$OUTPUTDIR" &> package.log
sh buildpackage.sh "$TAG" "$BUILTDIR" "${SRCDIR}/thirdparty/osx" "$OUTPUTDIR" &> package.log
if [ $? -ne 0 ]; then
echo "OS X package build failed, refer to osx/package.log."
fi
@@ -99,7 +102,7 @@ echo "Creating packages..."
(
cd linux
sh buildpackage.sh "$TAG" "$BUILTDIR" "$OUTPUTDIR" &> package.log
sh buildpackage.sh "$TAG" "$BUILTDIR" "${SRCDIR}/thirdparty/linux" "$OUTPUTDIR" &> package.log
if [ $? -ne 0 ]; then
echo "Linux package build failed, refer to linux/package.log."
fi

View File

@@ -2,7 +2,14 @@ echo "Updating https://github.com/OpenRA/OpenRA/wiki/Traits"
rm -rf openra-wiki
git clone git@github.com:OpenRA/OpenRA.wiki.git openra-wiki
cp -fr ../DOCUMENTATION.md openra-wiki/Traits.md
pushd .. &> /dev/null
# d2k depends on all mod libraries
mono --debug OpenRA.Utility.exe --lua-docs d2k > packaging/openra-wiki/New-Lua-API.md
popd &> /dev/null
cd openra-wiki
git add Traits.md
git commit -m "Update trait documentation"
git add New-Lua-API.md
git commit -m "Update trait and scripting documentation"
git push origin master

View File

@@ -92,10 +92,14 @@ Section "Game" GAME
File "${SRCDIR}\GeoLite2-Country.mmdb"
File "${SRCDIR}\KopiLua.dll"
File "${SRCDIR}\NLua.dll"
File OpenAL32.dll
File SDL.dll
File freetype6.dll
File zlib1.dll
File "${SRCDIR}\eluant.dll"
File "${DEPSDIR}\OpenAL32.dll"
File "${DEPSDIR}\SDL.dll"
File "${DEPSDIR}\freetype6.dll"
File "${DEPSDIR}\zlib1.dll"
File "${DEPSDIR}\lua51.dll"
SetOutPath "$INSTDIR\lua"
File "${SRCDIR}\lua\*.lua"
!insertmacro MUI_STARTMENU_WRITE_BEGIN Application
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
@@ -182,6 +186,7 @@ Function ${UN}Clean
RMDir /r $INSTDIR\maps
RMDir /r $INSTDIR\cg
RMDir /r $INSTDIR\glsl
RMDir /r $INSTDIR\lua
Delete $INSTDIR\OpenRA.Launcher.exe
Delete $INSTDIR\OpenRA.Game.exe
Delete $INSTDIR\OpenRA.Utility.exe
@@ -214,6 +219,8 @@ Function ${UN}Clean
Delete $INSTDIR\NLua.dll
Delete $INSTDIR\OpenAL32.dll
Delete $INSTDIR\SDL.dll
Delete $INSTDIR\lua51.dll
Delete $INSTDIR\eluant.dll
Delete $INSTDIR\freetype6.dll
Delete $INSTDIR\zlib1.dll
RMDir /r $INSTDIR\Support

BIN
thirdparty/Eluant.dll vendored Executable file

Binary file not shown.

5
thirdparty/Eluant.dll.config vendored Normal file
View File

@@ -0,0 +1,5 @@
<configuration>
<dllmap os="linux" dll="lua51.dll" cpu="x86" target="liblua32.5.1.5.so" />
<dllmap os="linux" dll="lua51.dll" cpu="x86-64" target="liblua64.5.1.5.so" />
<dllmap os="osx" dll="lua51.dll" target="liblua.5.1.dylib" />
</configuration>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<dllmap dll="SDL2.dll" os="windows" target="SDL2.dll"/>
<dllmap dll="SDL2.dll" os="osx" target="/Library/Frameworks/SDL2.framework/SDL2"/>
<dllmap dll="SDL2.dll" os="osx" target="libSDL2.dylib"/>
<dllmap dll="SDL2.dll" os="linux" target="libSDL2-2.0.so.0"/>
<dllmap dll="SDL2_image.dll" os="windows" target="SDL2_image.dll"/>

BIN
thirdparty/linux/liblua32.5.1.5.so vendored Normal file

Binary file not shown.

BIN
thirdparty/linux/liblua64.5.1.5.so vendored Normal file

Binary file not shown.

BIN
thirdparty/osx/libSDL2.dylib vendored Executable file

Binary file not shown.

BIN
thirdparty/osx/liblua.5.1.dylib vendored Normal file

Binary file not shown.

BIN
thirdparty/windows/lua51.dll vendored Normal file

Binary file not shown.