diff --git a/docs/luaengine.md b/docs/luaengine.md new file mode 100644 index 00000000000..c2f2b961492 --- /dev/null +++ b/docs/luaengine.md @@ -0,0 +1,154 @@ +# Scripting MAME via LUA + +## Introduction + +It is now possible to externally drive MAME via LUA scripts. +This feature initially appeared in version 0.148, when a minimal `luaengine` +was implemented. Nowadays, the LUA interface is rich enough +to let you inspect and manipulate devices state, access CPU +registers, read and write memory, and draw a custom HUD on screen. + +Internally, MAME makes extensive use of `luabridge` to implement +this feature: the idea is to transparently expose as many of +the useful internals as possible. + +Finally, a warning: LUA API is not yet declared stable and may +suddenly change without prior notice. +However, we expose methods to let you know at runtime which API +version you are running against, and you can introspect most of the +objects at runtime. + +## Features + +The API is not yet complete, but this is a partial list of capabilities +currently available to LUA scripts: + + * machine metadata (app version, current rom, rom details) + * machine control (starting, pausing, resetting, stopping) + * machine hooks (on frame painting and on user events) + * devices introspection (device tree listing, memory and register enumeration) + * screens introspection (screens listing, screen details, frames counting) + * screen HUD drawing (text, lines, boxes on multiple screens) + * memory read/write (8/16/32/64 bits, signed and unsigned) + * registers and states control (states enumeration, get and set) + +## Usage + +MAME supports external scripting via LUA (>= 5.3) scripts, either +written on the interactive console or loaded as a file. +To reach the console, just run MAME with `-console`; you will be +greeted by a naked `>` prompt where you can input your script. + +To load a whole script at once, store it in a plaintext file and +pass it via the `-autoboot_script`. Please note that script +loading may be delayed (few seconds by default), but you can +override the default with the `-autoboot_delay` argument. + +To control the execution of your code, you can use a loop-based or +an event-based approach. The former is not encouraged as it is +resource-intensive and makes control flow unnecessarily complex. +Instead, we suggest to register custom hooks to be invoked on specific +events (eg. at each frame rendering). + +## Walktrough + +Let's first run MAME in a terminal to reach the LUA console: +``` +$ mame -console YOUR_ROM +M.A.M.E. v0.158 (Feb 5 2015) - Multiple Arcade Machine Emulator +Copyright Nicola Salmoria and the MAME team +Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio + +> +``` + +At this point, your game is probably running in demo mode, let's pause it: +``` +> emu.pause() +> +``` +Even without textual feedback on the console, you'll notice the game is now paused. +In general, commands are quiet and only print back error messages. + +You can check at runtime which version of MAME you are running, with: +``` +> print(emu.app_name() .. " " .. emu.app_version()) +mame 0.158 +``` + +We now start exploring screen related methods. First, let's enumerate available screens: +``` +> for i,v in pairs(manager:machine().screens) do print(i) end +:screen +``` + +`manager:machine()` is the root object of your currently running machine: +we will be using this often. `screens` is a table with all available screens; +most machines only have one main screen. +In our case, the main and only screen is tagged as `:screen`, and we can further +inspect it: +``` +> -- let's define a shorthand for the main screen +> s = manager:machine().screens[":screen"] +> print(s:width() .. "x" .. s:height()) +320x224 +``` + +We have several methods to draw on the screen a HUD composed of lines, boxes and text: +``` +> -- we define a HUD-drawing function, and then call it +> function draw_hud() +>> s:draw_text(40, 40, "foo"); -- (x0, y0, msg) +>> s:draw_box(20, 20, 80, 80, 0, 0xff00ffff); -- (x0, y0, x1, y1, fill-color, line-color) +>> s:draw_line(20, 20, 80, 80, 0xff00ffff); -- (x0, y0, x1, y1, line-color) +>> end +> draw_hud(); +``` + +This will draw some useless art on the screen. However, when unpausing the game, your HUD +needs to be refreshed otherwise it will just disappear. In order to do this, you have to register +your hook to be called on every frame repaint: +``` +> emu.sethook(draw_hud, "frame") +``` + +Similarly to screens, you can inspect all the devices attached to a +machine: +``` +> for k,v in pairs(manager:machine().devices) do print(k) end +:audiocpu +:maincpu +:saveram +:screen +:palette +[...] +``` + +On some of them, you can also inspect and manipulate memory and state: +``` +> cpu = manager:machine().devices[":maincpu"] +> -- enumerate, read and write state registers +> for k,v in pairs(cpu.state) do print(k) end +D5 +SP +A4 +A3 +D0 +PC +[...] +> print(cpu.state["D0"].value) +303 +> cpu.state["D0"].value = 255 +> print(cpu.state["D0"].value) +255 +``` + +``` +> -- inspect memory +> for k,v in pairs(cpu.spaces) do print(k) end +program +> mem = cpu.spaces["program"] +> print(mem:read_i8(0xC000)) +41 +``` + diff --git a/src/emu/luaengine.c b/src/emu/luaengine.c index 256fa460be6..c8bb21eedc3 100644 --- a/src/emu/luaengine.c +++ b/src/emu/luaengine.c @@ -639,11 +639,13 @@ int lua_engine::lua_screen::l_draw_box(lua_State *L) luaL_argcheck(L, lua_isnumber(L, 7), 7, "outline color (integer) expected"); // retrieve all parameters + int sc_width = sc->visible_area().width(); + int sc_height = sc->visible_area().height(); float x1, y1, x2, y2; - x1 = MIN(lua_tounsigned(L, 2) / static_cast(sc->visible_area().width()) , 1.0f); - y1 = MIN(lua_tounsigned(L, 3) / static_cast(sc->visible_area().height()), 1.0f); - x2 = MIN(lua_tounsigned(L, 4) / static_cast(sc->visible_area().width()) , 1.0f); - y2 = MIN(lua_tounsigned(L, 5) / static_cast(sc->visible_area().height()), 1.0f); + x1 = MIN(MAX(0, lua_tointeger(L, 2)), sc_width-1) / static_cast(sc_width); + y1 = MIN(MAX(0, lua_tointeger(L, 3)), sc_height-1) / static_cast(sc_height); + x2 = MIN(MAX(0, lua_tointeger(L, 4)), sc_width-1) / static_cast(sc_width); + y2 = MIN(MAX(0, lua_tointeger(L, 5)), sc_height-1) / static_cast(sc_height); UINT32 bgcolor = lua_tounsigned(L, 6); UINT32 fgcolor = lua_tounsigned(L, 7); @@ -675,11 +677,13 @@ int lua_engine::lua_screen::l_draw_line(lua_State *L) luaL_argcheck(L, lua_isnumber(L, 6), 6, "color (integer) expected"); // retrieve all parameters + int sc_width = sc->visible_area().width(); + int sc_height = sc->visible_area().height(); float x1, y1, x2, y2; - x1 = MIN(lua_tounsigned(L, 2) / static_cast(sc->visible_area().width()) , 1.0f); - y1 = MIN(lua_tounsigned(L, 3) / static_cast(sc->visible_area().height()), 1.0f); - x2 = MIN(lua_tounsigned(L, 4) / static_cast(sc->visible_area().width()) , 1.0f); - y2 = MIN(lua_tounsigned(L, 5) / static_cast(sc->visible_area().height()), 1.0f); + x1 = MIN(MAX(0, lua_tointeger(L, 2)), sc_width-1) / static_cast(sc_width); + y1 = MIN(MAX(0, lua_tointeger(L, 3)), sc_height-1) / static_cast(sc_height); + x2 = MIN(MAX(0, lua_tointeger(L, 4)), sc_width-1) / static_cast(sc_width); + y2 = MIN(MAX(0, lua_tointeger(L, 5)), sc_height-1) / static_cast(sc_height); UINT32 color = lua_tounsigned(L, 6); // draw the line @@ -705,8 +709,10 @@ int lua_engine::lua_screen::l_draw_text(lua_State *L) luaL_argcheck(L, lua_isstring(L, 4), 4, "message (string) expected"); // retrieve all parameters - float x = MIN(lua_tounsigned(L, 2) / static_cast(sc->visible_area().width()) , 1.0f); - float y = MIN(lua_tounsigned(L, 3) / static_cast(sc->visible_area().height()), 1.0f); + int sc_width = sc->visible_area().width(); + int sc_height = sc->visible_area().height(); + float x = MIN(MAX(0, lua_tointeger(L, 2)), sc_width-1) / static_cast(sc_width); + float y = MIN(MAX(0, lua_tointeger(L, 3)), sc_height-1) / static_cast(sc_height); const char *msg = luaL_checkstring(L,4); // TODO: add optional parameters (colors, etc.) diff --git a/src/emu/render.c b/src/emu/render.c index 2eed9176550..46ae77e8909 100644 --- a/src/emu/render.c +++ b/src/emu/render.c @@ -2483,26 +2483,41 @@ render_target *render_manager::target_by_index(int index) const // fonts //------------------------------------------------- -float render_manager::ui_aspect() +float render_manager::ui_aspect(render_container *rc) { - int orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation()); + int orient = 0; + float aspect = 1.0f; - // based on the orientation of the target, compute height/width or width/height - float aspect; - if (!(orient & ORIENTATION_SWAP_XY)) - aspect = (float)m_ui_target->height() / (float)m_ui_target->width(); - else - aspect = (float)m_ui_target->width() / (float)m_ui_target->height(); + if (rc == m_ui_container || rc == NULL) { + // ui container, aggregated multi-screen target - // if we have a valid pixel aspect, apply that and return - if (m_ui_target->pixel_aspect() != 0.0f) - return aspect / m_ui_target->pixel_aspect(); + orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation()); + // based on the orientation of the target, compute height/width or width/height + if (!(orient & ORIENTATION_SWAP_XY)) + aspect = (float)m_ui_target->height() / (float)m_ui_target->width(); + else + aspect = (float)m_ui_target->width() / (float)m_ui_target->height(); - // if not, clamp for extreme proportions + // if we have a valid pixel aspect, apply that and return + if (m_ui_target->pixel_aspect() != 0.0f) + return (aspect / m_ui_target->pixel_aspect()); + } else { + // single screen container + + orient = rc->orientation(); + // based on the orientation of the target, compute height/width or width/height + if (!(orient & ORIENTATION_SWAP_XY)) + aspect = (float)rc->screen()->visible_area().height() / (float)rc->screen()->visible_area().width(); + else + aspect = (float)rc->screen()->visible_area().width() / (float)rc->screen()->visible_area().height(); + } + + // clamp for extreme proportions if (aspect < 0.66f) aspect = 0.66f; if (aspect > 1.5f) aspect = 1.5f; + return aspect; } diff --git a/src/emu/render.h b/src/emu/render.h index 2ee8e108a09..bf058258577 100644 --- a/src/emu/render.h +++ b/src/emu/render.h @@ -744,7 +744,7 @@ public: // UI targets render_target &ui_target() const { assert(m_ui_target != NULL); return *m_ui_target; } void set_ui_target(render_target &target) { m_ui_target = ⌖ } - float ui_aspect(); + float ui_aspect(render_container *rc = NULL); // UI containers render_container &ui_container() const { assert(m_ui_container != NULL); return *m_ui_container; } diff --git a/src/emu/ui/ui.c b/src/emu/ui/ui.c index 664775f2b1b..b968937cbea 100644 --- a/src/emu/ui/ui.c +++ b/src/emu/ui/ui.c @@ -456,7 +456,7 @@ void ui_manager::update_and_render(render_container *container) { float mouse_y=-1,mouse_x=-1; if (mouse_target->map_point_container(mouse_target_x, mouse_target_y, *container, mouse_x, mouse_y)) { - container->add_quad(mouse_x,mouse_y,mouse_x + 0.05*container->manager().ui_aspect(),mouse_y + 0.05,UI_TEXT_COLOR,m_mouse_arrow_texture,PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); + container->add_quad(mouse_x,mouse_y,mouse_x + 0.05*container->manager().ui_aspect(container),mouse_y + 0.05,UI_TEXT_COLOR,m_mouse_arrow_texture,PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); } } } @@ -600,7 +600,7 @@ void ui_manager::draw_text_full(render_container *container, const char *origs, const char *linestart; float cury = y; float maxwidth = 0; - float aspect = machine().render().ui_aspect(); + float aspect = machine().render().ui_aspect(container); // if we don't want wrapping, guarantee a huge wrapwidth if (wrap == WRAP_NEVER)