Add map scripting support

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

View File

@@ -22,7 +22,7 @@ namespace OpenRA.FileFormats
Folders, MapFolders, Rules, ServerTraits, Folders, MapFolders, Rules, ServerTraits,
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
Weapons, Voices, Notifications, Music, Movies, Translations, TileSets, Weapons, Voices, Notifications, Music, Movies, Translations, TileSets,
ChromeMetrics, PackageContents; ChromeMetrics, PackageContents, LuaScripts;
public readonly Dictionary<string, string> Packages; public readonly Dictionary<string, string> Packages;
public readonly MiniYaml LoadScreen; public readonly MiniYaml LoadScreen;
@@ -59,6 +59,7 @@ namespace OpenRA.FileFormats
TileSets = YamlList(yaml, "TileSets"); TileSets = YamlList(yaml, "TileSets");
ChromeMetrics = YamlList(yaml, "ChromeMetrics"); ChromeMetrics = YamlList(yaml, "ChromeMetrics");
PackageContents = YamlList(yaml, "PackageContents"); PackageContents = YamlList(yaml, "PackageContents");
LuaScripts = YamlList(yaml, "LuaScripts");
LoadScreen = yaml["LoadScreen"]; LoadScreen = yaml["LoadScreen"];
LobbyDefaults = yaml["LobbyDefaults"]; LobbyDefaults = yaml["LobbyDefaults"];

View File

@@ -54,7 +54,7 @@ namespace OpenRA
public class Map public class Map
{ {
[FieldLoader.Ignore] IFolder container; [FieldLoader.Ignore] public IFolder Container;
public string Path { get; private set; } public string Path { get; private set; }
// Yaml map data // Yaml map data
@@ -132,7 +132,7 @@ namespace OpenRA
void AssertExists(string filename) void AssertExists(string filename)
{ {
using (var s = container.GetContent(filename)) using (var s = Container.GetContent(filename))
if (s == null) if (s == null)
throw new InvalidOperationException("Required file {0} not present in this map".F(filename)); throw new InvalidOperationException("Required file {0} not present in this map".F(filename));
} }
@@ -142,12 +142,12 @@ namespace OpenRA
public Map(string path) public Map(string path)
{ {
Path = path; Path = path;
container = FileSystem.OpenPackage(path, null, int.MaxValue); Container = FileSystem.OpenPackage(path, null, int.MaxValue);
AssertExists("map.yaml"); AssertExists("map.yaml");
AssertExists("map.bin"); 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); FieldLoader.Load(this, yaml);
Uid = ComputeHash(); Uid = ComputeHash();
@@ -263,17 +263,17 @@ namespace OpenRA
// Create a new map package // Create a new map package
// TODO: Add other files (custom assets) to the entries list // 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 // Update existing package
container.Write(entries); Container.Write(entries);
} }
public TileReference<ushort, byte>[,] LoadMapTiles() public TileReference<ushort, byte>[,] LoadMapTiles()
{ {
var tiles = new TileReference<ushort, byte>[MapSize.X, MapSize.Y]; var tiles = new TileReference<ushort, byte>[MapSize.X, MapSize.Y];
using (var dataStream = container.GetContent("map.bin")) using (var dataStream = Container.GetContent("map.bin"))
{ {
if (dataStream.ReadUInt8() != 1) if (dataStream.ReadUInt8() != 1)
throw new InvalidDataException("Unknown binary map format"); throw new InvalidDataException("Unknown binary map format");
@@ -305,7 +305,7 @@ namespace OpenRA
{ {
var resources = new TileReference<byte, byte>[MapSize.X, MapSize.Y]; var resources = new TileReference<byte, byte>[MapSize.X, MapSize.Y];
using (var dataStream = container.GetContent("map.bin")) using (var dataStream = Container.GetContent("map.bin"))
{ {
if (dataStream.ReadUInt8() != 1) if (dataStream.ReadUInt8() != 1)
throw new InvalidDataException("Unknown binary map format"); 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 // UID is calculated by taking an SHA1 of the yaml and binary data
// Read the relevant data into a buffer // Read the relevant data into a buffer
var data = container.GetContent("map.yaml").ReadAllBytes() var data = Container.GetContent("map.yaml").ReadAllBytes()
.Concat(container.GetContent("map.bin").ReadAllBytes()).ToArray(); .Concat(Container.GetContent("map.bin").ReadAllBytes()).ToArray();
// Take the SHA1 // Take the SHA1
using (var csp = SHA1.Create()) using (var csp = SHA1.Create())

View File

@@ -338,8 +338,10 @@
<Compile Include="RepairsUnits.cs" /> <Compile Include="RepairsUnits.cs" />
<Compile Include="Reservable.cs" /> <Compile Include="Reservable.cs" />
<Compile Include="ScaredyCat.cs" /> <Compile Include="ScaredyCat.cs" />
<Compile Include="Scripting\LuaScriptInterface.cs" />
<Compile Include="Scripting\Media.cs" /> <Compile Include="Scripting\Media.cs" />
<Compile Include="Scripting\RASpecialPowers.cs" /> <Compile Include="Scripting\RASpecialPowers.cs" />
<Compile Include="Scripting\LuaScriptContext.cs" />
<Compile Include="SeedsResource.cs" /> <Compile Include="SeedsResource.cs" />
<Compile Include="SelfHealing.cs" /> <Compile Include="SelfHealing.cs" />
<Compile Include="Sellable.cs" /> <Compile Include="Sellable.cs" />
@@ -476,6 +478,10 @@
<Compile Include="Effects\Rank.cs" /> <Compile Include="Effects\Rank.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\LuaInterface\LuaInterface.csproj">
<Project>{e915a0a4-2641-4f7e-8a88-8f123fa88bf1}</Project>
<Name>LuaInterface</Name>
</ProjectReference>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj"> <ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">
<Project>{BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}</Project> <Project>{BDAEAB25-991E-46A7-AF1E-4F0E03358DAA}</Project>
<Name>OpenRA.FileFormats</Name> <Name>OpenRA.FileFormats</Name>

View File

@@ -0,0 +1,134 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using LuaInterface;
namespace OpenRA.Mods.RA.Scripting
{
public class LuaScriptContext : IDisposable
{
public Lua Lua { get; private set; }
public LuaScriptContext()
{
Log.Write("debug", "Creating Lua script context");
Lua = new Lua();
}
public void RegisterObject(object target, string tableName, bool exposeAllMethods)
{
Log.Write("debug", "Registering object {0}", target);
if (tableName != null && Lua.GetTable(tableName) == null)
Lua.NewTable(tableName);
var type = target.GetType();
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
RegisterMethods(tableName, target, methods, exposeAllMethods);
}
public void RegisterType(Type type, string tableName, bool exposeAllMethods)
{
Log.Write("debug", "Registering type {0}", type);
if (tableName != null && Lua.GetTable(tableName) == null)
Lua.NewTable(tableName);
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Static);
RegisterMethods(tableName, null, methods, exposeAllMethods);
}
void RegisterMethods(string tableName, object target, IEnumerable<MethodInfo> methods, bool allMethods)
{
foreach (var method in methods)
{
string methodName;
var attr = method.GetCustomAttributes<LuaGlobalAttribute>(true).FirstOrDefault();
if (attr == null)
{
if (allMethods)
methodName = method.Name;
else
continue;
}
else
methodName = attr.Name ?? method.Name;
var methodTarget = method.IsStatic ? null : target;
if (tableName != null)
Lua.RegisterFunction(tableName + "." + methodName, methodTarget, method);
else
Lua.RegisterFunction(methodName, methodTarget, method);
}
}
void LogException(Exception e)
{
Game.Debug("{0}", e.Message);
Game.Debug("See debug.log for details");
Log.Write("debug", "{0}", e);
}
public void LoadLuaScripts(Func<string, string> getFileContents, params string[] files)
{
foreach (var file in files)
{
try
{
Log.Write("debug", "Loading Lua script {0}", file);
var content = getFileContents(file);
Lua.DoString(content, file);
}
catch (Exception e)
{
LogException(e);
}
}
}
public object[] InvokeLuaFunction(string name, params object[] args)
{
try
{
var function = Lua.GetFunction(name);
if (function == null)
return null;
return function.Call(args);
}
catch (Exception e)
{
LogException(e);
return null;
}
}
public void Dispose()
{
if (Lua == null)
return;
GC.SuppressFinalize(this);
Lua.Dispose();
Lua = null;
}
~LuaScriptContext()
{
Dispose();
}
}
}

View File

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