diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml
index 6478bc444b..131b9bdfd3 100644
--- a/Docs/Luadoc/Lua.xml
+++ b/Docs/Luadoc/Lua.xml
@@ -210,6 +210,7 @@
+
diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml
index cf2d54ec93..fb257e6a8c 100644
--- a/Docs/Luadoc/LuaDocumentation.xml
+++ b/Docs/Luadoc/LuaDocumentation.xml
@@ -121,6 +121,11 @@ save yourself some time, copy this for undocumented things:
Tries to connect to the server at sAddress.
+
+ Recursively searches dir for xml files of actors and
+ converts them to equivalent lua files. See Docs/Themerdocs/XmlToLua.txt
+ for details.
+
Creates a CubicSplineN for you to use. Make sure you destroy the CubicSplineN when you're done with it, or you will have a memory leak.
diff --git a/Docs/Themerdocs/XmlToLua.txt b/Docs/Themerdocs/XmlToLua.txt
new file mode 100644
index 0000000000..1dae2f1d4f
--- /dev/null
+++ b/Docs/Themerdocs/XmlToLua.txt
@@ -0,0 +1,158 @@
+StepMania 5 has an xml converter inside it intended to assist authors who want
+to convert the xml scripted background and foreground animations written for
+StepMania 3.95 or OpenITG to work with StepMania 5.
+
+The primary way to invoke the converter is to use the debug menu.
+While on the Select Music screen, navigate to the song with xml that needs to
+be converted. Hold F3, press F6 to change to the theme section, then press 0
+to convert the xml in the folder of the current song.
+
+The other way is to use a theme that provides some interface to the
+convert_xml_bgs lua function.
+
+
+Since the converter recursively searches for all xml files in the folder
+given, if you have foo/default.xml, it will be converted to foo/default.lua.
+If foo/bar/default.xml exists, foo/bar/default.lua will be created.
+
+When the converter finds an xml file, it parses it and attempts to create lua
+that results in the same actors. Some things are converted without problems,
+others are beyond the scope of the converter and are not handled.
+
+Do not run the converter on a song that you have already converted. When it
+finds the xml files, it will overwrite the lua files with new ones, which
+means any fixes applied after conversion will be wiped out.
+
+The converter does not delete the xml files after conversion because you
+probably need them to double check things.
+
+
+
+# Known working conversions
+
+## Naming
+Actors and conditions are assigned autogenerated unique names with the prefix
+"xtl_actor_" or "xtl_cond_". This is so that if some error message tells you
+the name of an actor, you can find it in the code. If an actor has a Name
+field, that will be used instead of the autogenerated unique name.
+
+## Type guessing
+
+Typically, the Type or Class field is used, unless the File field points to
+something that has to be a certain type.
+
+* ActorFrames with children are handled.
+
+* File field conditions:
+ * Images and .sprite files are converted to Sprites.
+ * .txt and .model files are converted to Models.
+ * Folders are converted to use LoadActor, which has its own rules for
+ guessing the actor type.
+ * Sound files are converted to ActorSound.
+
+* If there is a Text field, the actor is assumed to be a BitmapText.
+
+
+## Field conversion
+
+Any field that ends with "Command" is recognized as a command and passes
+through command conversion.
+Anything else passes through unchanged, unless it's known to be a string
+field. String fields will be strings after conversion.
+This may cause syntax errors if a field is meant to be a string and it's not
+a string after conversion. This happens because in XML, all fields are
+stored in strings and the only way to know which ones are supposed to be
+strings after conversion is to keep a list of field names.
+String fields:
+* AltText
+* Bones
+* File
+* Font
+* Materials
+* Meshes
+* Text
+* Texture
+
+
+## Command conversion
+
+Commands that use the normal style are converted to use SM5's cmd syntax.
+These commands are passed through a parser that looks for certain things that
+require special handling:
+* x coords are changed to use (SCREEN_CENTER_X + (x - 320)), to put them in
+ the same position relative to the center. This is only applied if the
+ coord was a simple number, expressions are not changed.
+* y coords are changed similarly to x coords.
+* queuecommand, playcommand, and effectclock all have their arguments changed
+ to strings.
+* EffectMagnitude, ZoomToWidth, and ZoomToHeight are renamed to the lowercase
+ names SM5 uses.
+* blend and cullmode have their args converted to SM5's enum values.
+* hidden is converted to visible, and its arg is converted to a bool.
+* diffuse, effectcolor1, and effectcolor2 have their args converted to use
+ the color function.
+
+
+Commands that use lua functions are converted with the lua mostly unchanged.
+A very short list of things in lua are recognized and replaced with their
+SM5 equivalents.
+* "hidden(0)" becomes "visible(true)"
+* "hidden(1)" becomes "visible(false)"
+* "effectdelay" becomes "effect_hold_at_full"
+* "IsPlayerEnabled(0)" becomes "IsPlayerEnabled(PLAYER_1)"
+* "IsPlayerEnabled(1)" becomes "IsPlayerEnabled(PLAYER_2)"
+
+The list is short and the strings must match exactly because symbolically
+parsing the lua into tokens to recognize other ways of doing the same thing
+is beyond the scope of the converter.
+
+
+## Condition conversion
+
+Because conditions are lua code to execute, they are passed through the same
+converter as lua commands.
+It is common practice for many actors to have identical conditions, so all
+conditions are collected in a list, so they can be evaluated once when the
+file is loaded, and then the actors can just use the result.
+So after conversion you might see something like this:
+```
+xtl_cond_a_result= GAMESTATE:IsPlayerEnabled(PLAYER_1)
+local function optional_actor(cond, actor)
+ if cond then return actor end
+ return Def.Actor{}
+end
+```
+The optional_actor function is a convenient way to insert a blank, unrendered
+actor instead of one that actually does something.
+You'll see it used like this:
+```
+ optional_actor(xtl_cond_a_result,
+ Def.Sprite{
+ Name= "xtl_actor_g",
+ InitCommand= cmd(x,0;y,0),
+ Texture= "foo 3x1.png",
+ }),
+```
+
+
+
+# Known unhandled things
+
+Some things like difficulty use strings as enum values in SM5. Lua code that
+uses GetDifficulty or other things that now return enum strings must be
+edited after conversion to use the enum strings.
+
+There are probably renamed functions that the converter does not handle.
+
+GAMESTATE:ApplyGameCommand applies mods instantly, ignoring the tween
+speeds. Lua code that changes mods should use the PlayerOptions API instead.
+This means fetching a PlayerOptions object and converting mod strings to
+function names and args, which isn't hard with an editor with regex find and
+replace.
+
+Actor sizes are not changed, so if an actor is meant to be the size of the
+screen, it will probably not cover the screen when Stepmania isn't a 4:3
+aspect ratio. Use SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_CENTER_X,
+SCREEN_CENTER_Y, _screen.w, _screen.h, _screen.cx, or _screen.cy for
+positioning and sizing. Actor:FullScreen exists to stretch an actor to cover
+the screen.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2d51514f89..9ed3a1a5af 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -118,6 +118,7 @@ list(APPEND SMDATA_FILE_TYPES_HPP
"IniFile.h"
"MsdFile.h"
"XmlFile.h"
+ "XmlToLua.h"
"XmlFileUtil.h"
)
diff --git a/src/ScreenDebugOverlay.cpp b/src/ScreenDebugOverlay.cpp
index f145f654d1..f69abe43f6 100644
--- a/src/ScreenDebugOverlay.cpp
+++ b/src/ScreenDebugOverlay.cpp
@@ -29,6 +29,7 @@
#include "Song.h"
#include "ScreenSyncOverlay.h"
#include "ThemeMetric.h"
+#include "XmlToLua.h"
static bool g_bIsDisplayed = false;
static bool g_bIsSlow = false;
@@ -1116,8 +1117,7 @@ class DebugLineClearErrors : public IDebugLine
}
};
-void convert_xmls_in_dir(RString const& dirname);
-class DebugLineConvrtXML : public IDebugLine
+class DebugLineConvertXML : public IDebugLine
{
virtual RString GetDisplayTitle() { return CONVERT_XML.GetValue(); }
virtual RString GetDisplayValue() { return RString(); }
@@ -1319,7 +1319,7 @@ DECLARE_ONE( DebugLineReloadTheme );
DECLARE_ONE( DebugLineReloadOverlayScreens );
DECLARE_ONE( DebugLineToggleErrors );
DECLARE_ONE( DebugLineClearErrors );
-DECLARE_ONE( DebugLineConvrtXML );
+DECLARE_ONE( DebugLineConvertXML );
DECLARE_ONE( DebugLineWriteProfiles );
DECLARE_ONE( DebugLineWritePreferences );
DECLARE_ONE( DebugLineMenuTimer );
diff --git a/src/XmlToLua.cpp b/src/XmlToLua.cpp
index d9a1cf405c..d7e6103d3d 100644
--- a/src/XmlToLua.cpp
+++ b/src/XmlToLua.cpp
@@ -245,6 +245,15 @@ void init_parser_helpers()
chunks_to_replace["IsPlayerEnabled(1)"]= "IsPlayerEnabled(PLAYER_2)";
}
+void convert_lua_chunk(RString& chunk_text)
+{
+ for(map::iterator chunk= chunks_to_replace.begin();
+ chunk != chunks_to_replace.end(); ++chunk)
+ {
+ chunk_text.Replace(chunk->first, chunk->second);
+ }
+}
+
// Conditions are mapped by condition string.
// So condition_set_t::iterator->first is the lua to execute for the
// condition, and condition_set_t::iterator->second is the name of the
@@ -293,11 +302,7 @@ void actor_template_t::store_cmd(RString const& cmd_name, RString const& full_cm
if(full_cmd.Left(1) == "%")
{
RString cmd_text= full_cmd.Right(full_cmd.size()-1);
- for(map::iterator chunk= chunks_to_replace.begin();
- chunk != chunks_to_replace.end(); ++chunk)
- {
- cmd_text.Replace(chunk->first, chunk->second);
- }
+ convert_lua_chunk(cmd_text);
fields[cmd_name]= cmd_text;
return;
}
@@ -739,7 +744,9 @@ void convert_xml_file(RString const& fname, RString const& dirname)
for(condition_set_t::iterator cond= conditions.begin();
cond != conditions.end(); ++cond)
{
- file->Write("local " + cond->second + "_result= " + cond->first + "\n\n");
+ RString cond_text= cond->first;
+ convert_lua_chunk(cond_text);
+ file->Write("local " + cond->second + "_result= " + cond_text + "\n\n");
}
if(!conditions.empty())
{
diff --git a/src/XmlToLua.h b/src/XmlToLua.h
new file mode 100644
index 0000000000..02ba9219be
--- /dev/null
+++ b/src/XmlToLua.h
@@ -0,0 +1,4 @@
+#ifndef XmlToLua_H
+#define XmlToLua_H
+void convert_xmls_in_dir(RString const& dirname);
+#endif