diff --git a/OpenRA.FileFormats/Manifest.cs b/OpenRA.FileFormats/Manifest.cs index fbc8c0c9d1..22a6fdcf90 100644 --- a/OpenRA.FileFormats/Manifest.cs +++ b/OpenRA.FileFormats/Manifest.cs @@ -22,7 +22,7 @@ namespace OpenRA.FileFormats Folders, MapFolders, Rules, ServerTraits, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Weapons, Voices, Notifications, Music, Movies, Translations, TileSets, - ChromeMetrics, PackageContents; + ChromeMetrics, PackageContents, LuaScripts; public readonly Dictionary Packages; public readonly MiniYaml LoadScreen; @@ -59,6 +59,7 @@ namespace OpenRA.FileFormats TileSets = YamlList(yaml, "TileSets"); ChromeMetrics = YamlList(yaml, "ChromeMetrics"); PackageContents = YamlList(yaml, "PackageContents"); + LuaScripts = YamlList(yaml, "LuaScripts"); LoadScreen = yaml["LoadScreen"]; LobbyDefaults = yaml["LobbyDefaults"]; diff --git a/OpenRA.Game/Map.cs b/OpenRA.Game/Map.cs index fd28aec8c0..8399e50a34 100644 --- a/OpenRA.Game/Map.cs +++ b/OpenRA.Game/Map.cs @@ -54,7 +54,7 @@ namespace OpenRA public class Map { - [FieldLoader.Ignore] IFolder container; + [FieldLoader.Ignore] public IFolder Container; public string Path { get; private set; } // Yaml map data @@ -132,7 +132,7 @@ namespace OpenRA void AssertExists(string filename) { - using (var s = container.GetContent(filename)) + using (var s = Container.GetContent(filename)) if (s == null) throw new InvalidOperationException("Required file {0} not present in this map".F(filename)); } @@ -142,12 +142,12 @@ namespace OpenRA public Map(string path) { Path = path; - container = FileSystem.OpenPackage(path, null, int.MaxValue); + Container = FileSystem.OpenPackage(path, null, int.MaxValue); AssertExists("map.yaml"); AssertExists("map.bin"); - var yaml = new MiniYaml(null, MiniYaml.FromStream(container.GetContent("map.yaml"))); + var yaml = new MiniYaml(null, MiniYaml.FromStream(Container.GetContent("map.yaml"))); FieldLoader.Load(this, yaml); Uid = ComputeHash(); @@ -263,17 +263,17 @@ namespace OpenRA // Create a new map package // TODO: Add other files (custom assets) to the entries list - container = FileSystem.CreatePackage(Path, int.MaxValue, entries); + Container = FileSystem.CreatePackage(Path, int.MaxValue, entries); } // Update existing package - container.Write(entries); + Container.Write(entries); } public TileReference[,] LoadMapTiles() { var tiles = new TileReference[MapSize.X, MapSize.Y]; - using (var dataStream = container.GetContent("map.bin")) + using (var dataStream = Container.GetContent("map.bin")) { if (dataStream.ReadUInt8() != 1) throw new InvalidDataException("Unknown binary map format"); @@ -305,7 +305,7 @@ namespace OpenRA { var resources = new TileReference[MapSize.X, MapSize.Y]; - using (var dataStream = container.GetContent("map.bin")) + using (var dataStream = Container.GetContent("map.bin")) { if (dataStream.ReadUInt8() != 1) throw new InvalidDataException("Unknown binary map format"); @@ -391,8 +391,8 @@ namespace OpenRA { // UID is calculated by taking an SHA1 of the yaml and binary data // Read the relevant data into a buffer - var data = container.GetContent("map.yaml").ReadAllBytes() - .Concat(container.GetContent("map.bin").ReadAllBytes()).ToArray(); + var data = Container.GetContent("map.yaml").ReadAllBytes() + .Concat(Container.GetContent("map.bin").ReadAllBytes()).ToArray(); // Take the SHA1 using (var csp = SHA1.Create()) diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 50fdbc7c7e..0ae49ce679 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -338,8 +338,10 @@ + + @@ -476,6 +478,10 @@ + + {e915a0a4-2641-4f7e-8a88-8f123fa88bf1} + LuaInterface + {BDAEAB25-991E-46A7-AF1E-4F0E03358DAA} OpenRA.FileFormats diff --git a/OpenRA.Mods.RA/Scripting/LuaScriptContext.cs b/OpenRA.Mods.RA/Scripting/LuaScriptContext.cs new file mode 100644 index 0000000000..97fca80e64 --- /dev/null +++ b/OpenRA.Mods.RA/Scripting/LuaScriptContext.cs @@ -0,0 +1,134 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using LuaInterface; + +namespace OpenRA.Mods.RA.Scripting +{ + public class LuaScriptContext : IDisposable + { + public Lua Lua { get; private set; } + + public LuaScriptContext() + { + Log.Write("debug", "Creating Lua script context"); + Lua = new Lua(); + } + + public void RegisterObject(object target, string tableName, bool exposeAllMethods) + { + Log.Write("debug", "Registering object {0}", target); + + if (tableName != null && Lua.GetTable(tableName) == null) + Lua.NewTable(tableName); + + var type = target.GetType(); + + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); + RegisterMethods(tableName, target, methods, exposeAllMethods); + } + + public void RegisterType(Type type, string tableName, bool exposeAllMethods) + { + Log.Write("debug", "Registering type {0}", type); + + if (tableName != null && Lua.GetTable(tableName) == null) + Lua.NewTable(tableName); + + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static); + RegisterMethods(tableName, null, methods, exposeAllMethods); + } + + void RegisterMethods(string tableName, object target, IEnumerable methods, bool allMethods) + { + foreach (var method in methods) + { + string methodName; + + var attr = method.GetCustomAttributes(true).FirstOrDefault(); + if (attr == null) + { + if (allMethods) + methodName = method.Name; + else + continue; + } + else + methodName = attr.Name ?? method.Name; + + var methodTarget = method.IsStatic ? null : target; + + if (tableName != null) + Lua.RegisterFunction(tableName + "." + methodName, methodTarget, method); + else + Lua.RegisterFunction(methodName, methodTarget, method); + } + } + + void LogException(Exception e) + { + Game.Debug("{0}", e.Message); + Game.Debug("See debug.log for details"); + Log.Write("debug", "{0}", e); + } + + public void LoadLuaScripts(Func getFileContents, params string[] files) + { + foreach (var file in files) + { + try + { + Log.Write("debug", "Loading Lua script {0}", file); + var content = getFileContents(file); + Lua.DoString(content, file); + } + catch (Exception e) + { + LogException(e); + } + } + } + + public object[] InvokeLuaFunction(string name, params object[] args) + { + try + { + var function = Lua.GetFunction(name); + if (function == null) + return null; + return function.Call(args); + } + catch (Exception e) + { + LogException(e); + return null; + } + } + + public void Dispose() + { + if (Lua == null) + return; + + GC.SuppressFinalize(this); + Lua.Dispose(); + Lua = null; + } + + ~LuaScriptContext() + { + Dispose(); + } + } +} diff --git a/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs b/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs new file mode 100644 index 0000000000..43f449bb9d --- /dev/null +++ b/OpenRA.Mods.RA/Scripting/LuaScriptInterface.cs @@ -0,0 +1,165 @@ +#region Copyright & License Information +/* + * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; +using LuaInterface; +using OpenRA.Effects; +using OpenRA.FileFormats; +using OpenRA.Traits; +using WorldRenderer = OpenRA.Graphics.WorldRenderer; + +namespace OpenRA.Mods.RA.Scripting +{ + public class LuaScriptInterfaceInfo : ITraitInfo, Requires + { + public readonly string[] LuaScripts = { }; + + public object Create(ActorInitializer init) { return new LuaScriptInterface(this); } + } + + public class LuaScriptInterface : IWorldLoaded, ITick + { + World world; + readonly LuaScriptContext context = new LuaScriptContext(); + readonly LuaScriptInterfaceInfo info; + + public LuaScriptInterface(LuaScriptInterfaceInfo info) + { + this.info = info; + } + + public void WorldLoaded(World w, WorldRenderer wr) + { + world = w; + AddMapActorGlobals(); + context.Lua["World"] = w; + context.Lua["WorldRenderer"] = wr; + context.RegisterObject(this, "_OpenRA", false); + context.RegisterType(typeof(WVec), "WVec", true); + context.RegisterType(typeof(WPos), "WPos", true); + context.RegisterType(typeof(CPos), "CPos", true); + context.RegisterType(typeof(WRot), "WRot", true); + context.RegisterType(typeof(WAngle), "WAngle", true); + context.RegisterType(typeof(WRange), "WRange", true); + context.RegisterType(typeof(int2), "int2", true); + context.RegisterType(typeof(float2), "float2", true); + var sharedScripts = Game.modData.Manifest.LuaScripts ?? new string[0]; + if (sharedScripts.Any()) + context.LoadLuaScripts(f => FileSystem.Open(f).ReadAllText(), sharedScripts); + context.LoadLuaScripts(f => w.Map.Container.GetContent(f).ReadAllText(), info.LuaScripts); + context.InvokeLuaFunction("WorldLoaded"); + } + + void AddMapActorGlobals() + { + foreach (var kv in world.WorldActor.Trait().Actors) + context.Lua[kv.Key] = kv.Value; + } + + public void Tick(Actor self) + { + context.InvokeLuaFunction("Tick"); + } + + [LuaGlobal] + public object New(string typeName, LuaTable args) + { + var type = Game.modData.ObjectCreator.FindType(typeName); + if (type == null) + throw new InvalidOperationException("Cannot locate type: {0}".F(typeName)); + if (args == null) + return Activator.CreateInstance(type); + var argsArray = ConvertArgs(args); + return Activator.CreateInstance(type, argsArray); + } + + object[] ConvertArgs(LuaTable args) + { + var argsArray = new object[args.Keys.Count]; + for (var i = 1; i <= args.Keys.Count; i++) + { + var arg = args[i] as LuaTable; + if (arg != null && arg[1] != null && arg[2] != null) + argsArray[i - 1] = Convert.ChangeType(arg[1], Enum.Parse(arg[2].ToString())); + else + argsArray[i - 1] = args[i]; + } + return argsArray; + } + + [LuaGlobal] + public void Debug(object obj) + { + if (obj != null) + Game.Debug(obj.ToString()); + } + + [LuaGlobal] + public object TraitOrDefault(Actor actor, string className) + { + var type = Game.modData.ObjectCreator.FindType(className); + if (type == null) + return null; + + var method = typeof(Actor).GetMethod("TraitOrDefault"); + var genericMethod = method.MakeGenericMethod(type); + return genericMethod.Invoke(actor, null); + } + + [LuaGlobal] + public object Trait(Actor actor, string className) + { + var ret = TraitOrDefault(actor, className); + if (ret == null) + throw new InvalidOperationException("Actor {0} does not have trait of type {1}".F(actor, className)); + return ret; + } + + [LuaGlobal] + public bool HasTrait(Actor actor, string className) + { + var ret = TraitOrDefault(actor, className); + return ret != null; + } + + [LuaGlobal] + public object TraitInfoOrDefault(string actorType, string className) + { + var type = Game.modData.ObjectCreator.FindType(className); + if (type == null || !Rules.Info.ContainsKey(actorType)) + return null; + + return Rules.Info[actorType].Traits.GetOrDefault(type); + } + + [LuaGlobal] + public object TraitInfo(string actorType, string className) + { + var ret = TraitInfoOrDefault(actorType, className); + if (ret == null) + throw new InvalidOperationException("Actor type {0} does not have trait info of type {1}".F(actorType, className)); + return ret; + } + + [LuaGlobal] + public bool HasTraitInfo(string actorType, string className) + { + var ret = TraitInfoOrDefault(actorType, className); + return ret != null; + } + + [LuaGlobal] + public void RunAfterDelay(double delay, Action func) + { + world.AddFrameEndTask(w => w.Add(new DelayedAction((int)delay, func))); + } + } +}