diff --git a/docs/source/plugins/hiscore.rst b/docs/source/plugins/hiscore.rst index c3ac41d15d4..389dea598ac 100644 --- a/docs/source/plugins/hiscore.rst +++ b/docs/source/plugins/hiscore.rst @@ -13,7 +13,16 @@ The plugin includes a **hiscore.dat** file that contains the information on how to save and restore high scores for supported systems. This file must be kept up-to-date when system definitions change in MAME. -High score data is saved in the **hi** folder in the working directory. A file -with a name corresponding the system short name (or ROM set name) with the -extension ``.hi``. For example, high scores for the game Moon Cresta will be -saved in the file **mooncrst.hi** in the **hi** folder. +High scores can be saved automatically either on exit, or a few seconds after +they’re updated in memory. To change the setting, activate the main menu (press +**Tab** during emulation by default), select **Plugin Options**, and then select +**Hiscore Support**. Change the **Save scores** option by highlighting it and +using the UI Left/Right keys, or clicking the arrows. + +High score data is saved in the **hiscore** folder in the plugin data folder +(see the :ref:`homepath option `). A file with a +name corresponding the system short name (or ROM set name) with the extension +``.hi``. For example, high scores for the game Moon Cresta will be saved in the +file **mooncrst.hi** in the **hiscore** folder in your plugin data folder. The +settings for the hiscore support plugin are stored in the file **plugin.cfg** in +the **hiscore** folder in the plugin data folder (this file is in JSON format). diff --git a/docs/source/plugins/index.rst b/docs/source/plugins/index.rst index 5fbc501eec8..3cb72f3cf92 100644 --- a/docs/source/plugins/index.rst +++ b/docs/source/plugins/index.rst @@ -68,5 +68,6 @@ sample code that you can use as a starting point when writing your own plugins. dummy gdbstub hiscore + inputmacro layout timer diff --git a/docs/source/plugins/inputmacro.rst b/docs/source/plugins/inputmacro.rst new file mode 100644 index 00000000000..99f8a3448fc --- /dev/null +++ b/docs/source/plugins/inputmacro.rst @@ -0,0 +1,230 @@ +.. _plugins-inputmacro: + +Input Macro Plugin +================== + +.. contents:: :local: + + +.. _plugins-inputmacro-intro: + +Introduction +------------ + +The input macro plugin allows you to trigger a sequence of emulated input +actions with a key or button combination. This can help people with +disabilities or injuries that make some input sequences difficult. It can also +be used as a way to cheat in games that require rapid sequences of inputs, like +the running events in Track & Field, or the eating minigame in Daisu-Kiss. + +To configure the input macro plugin, activate the main menu (press **Tab** +during emulation by default), select **Plugin Options**, and then select **Input +Macros**. Configured input macros for the current system are listed, along with +their activation sequences (initially there will be no input macros configured). +Select a macro to edit it, or choose **Add macro** to set up a new input macro. +See :ref:`plugins-inputmacro-settings` for details on editing input macros. You +can delete an input macro by highlighting it in the menu and pressing the UI +Clear key (Del/Delete/Forward Delete on the keyboard by default). + +Input macros are saved in the **inputmacro** folder in the plugin data folder +(see the :ref:`homepath option `). A file is created +for each system with input macros configured, named according to the system’s +short name (or ROM set name), with the extension ``.cfg``. For example, input +macros for Daisu-Kiss will be saved in the file **daiskiss.cfg** in the +**inputmacro** folder in your plugin data folder. The input macros are stored +in JSON format. + + +.. _plugins-inputmacro-settings: + +Editing input macros +-------------------- + +The options for editing input macros are the same whether you’re creating a new +macro or editing an existing macro. Input macros consist of a sequence of +*steps*. Each step optionally waits for a configurable delay, then activates +one or more emulated inputs for a specified duration. You can choose what +should happen if the activation sequence is still held when the final step of +the macro completes: the emulated inputs can be released, the final step can be +prolonged, or the macro can loop back to any step in the sequence. + +The settings in first section of the macro editing menu apply to the macro as a +whole: + +* The **Name** will be used in the list of input macros, so it helps to make it + descriptive. Press the UI Select key (Return/Enter on the keyboard or the + first button on the first joystick by default) to edit the current name, or + press the UI Clear key to type a new name. Press the UI Select key before + moving to another menu item to save the new name; press the UI Cancel key + (Escape/Esc on the keyboard by default) to change discard the new name. +* Select **Activation sequence** to set the key or button combination you want + to use to activate the macro. Keep in mind that regular input settings still + apply, so you probably want to use a combination that isn’t being used for any + other emulated input in the system. +* Set **On release** to specify what should happen if the activation sequence is + released before the macro completes. When set to *Stop immediately*, any + emulated inputs activated by the macro will be released immediately, and no + further steps will be processed; when set to *Complete macro*, the macro will + continue to be processed until the end of the final step. +* Set **When held** to specify what should happen if the activation sequence is + held after the final step of the macro completes. When set to *Release*, any + inputs activated by the macro will be released, and the macro will not be + reactivated until the activation sequence is released and pressed again; when + set to *Prolong step * where ** is the number of the final step of the + macro, the emulated inputs activated by the final step of the macro will + remain active until the activation sequence is released; when set to *Loop to + step * where ** is a step number, macro processing will return to that + step, including its delay, if the activation sequence is held after the final + step completes. + +Each step has delay, duration and input settings: + +* Set the **Delay** to the number of emulated video frame intervals to wait + before activating the inputs for the step. During the delay, no emulated + inputs will be activated by the macro. You can reset the setting to zero by + pressing the UI Clear key. +* Set the **Duration** to the number of emulated video frame intervals to hold + the emulated inputs for the step active before moving to the next step (or + completing the macro in the case of the final step). You can reset the + setting to one frame by pressing the UI Clear key. +* Set the **Input** settings to the emulated inputs to activate for the step. + Only non-toggle digital inputs are supported. Select **Add input** to set + multiple inputs for a step (this option will only appear after you set the + first input for the initially created step when creating a new macro). If + the step has multiple inputs, you can highlight an input on the menu and press + the UI Clear key to delete it (all steps must have at least one input, so you + can’t delete the only input for a step). +* If the macro has multiple steps, you can select **Delete step** to delete a + step (this options does not appear if the macro only has a single step). + Remember to check that the **On release** and **When held** settings are + correct after deleting steps. + +To add a step to the macro, highlight **Add step at position** (below the +existing steps), use the UI Left/Right keys or click the arrows to set the +position where you’d like to insert the new step, and then press the UI Select +key (or double-click the menu item) to add the new step. You will be prompted +to set the first input for the new step. Remember to check the **On release** +and **When held** settings after adding steps. The **Add step at position** +item will only appear after you set the first input for the initially created +step when creating a new macro. + +When creating a new macro, there is a **Cancel** option that changes to +**Create** after you set the activating sequence and the first input for the +initially created step. Select **Create** to finish creating the macro and +return to the list of input macros. The new macro will be added at the end of +the list. Press the UI Cancel key, or select **Cancel** before setting the +activation sequence/input, to return to the previous menu without creating the +new macro. + +When editing an existing macro, select **Done** or press the UI Cancel key to +return to the list of input macros. Changes take effect immediately. + + +.. _plugins-inputmacro-examples: + +Example macros +-------------- + +Raiden autofire +~~~~~~~~~~~~~~~ + +This provides player 1 autofire functionality using the space bar. The same +thing could be achieved using the :ref:`plugins-autofire`, but this demonstrates +a simple looping macro: + +* **Name**: P1 Autofire +* **Activation sequence**: Kbd Space +* **On release**: Stop immediately +* **When held**: Loop to step 2 +* **Step 1**: + + * **Delay (frames)**: 0 + * **Duration (frames)**: 2 + * **Input 1**: P1 Button 1 +* **Step 2**: + + * **Delay (frames)**: 4 + * **Duration (frames)**: 2 + * **Input 1**: P1 Button 1 + +The first step has no delay so that firing begins as soon as the space bar is +pressed. The second step has sufficient delay to ensure the game recognises the +button being pressed and released again. The second step is repeated as long as +the space bar is held down. + +Track & Field sprint cheat +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This allows you to run in Konami Track & Field by holding a single button. This +takes most of the skill (and fun) out of the game: + +* **Name**: P1 Sprint +* **Activation sequence**: Kbd Shift +* **On release**: Stop immediately +* **When held**: Loop to step 2 +* **Step 1**: + + * **Delay (frames)**: 0 + * **Duration (frames)**: 1 + * **Input 1**: P1 Button 1 +* **Step 2**: + + * **Delay (frames)**: 1 + * **Duration (frames)**: 1 + * **Input 1**: P1 Button 3 +* **Step 3**: + + * **Delay (frames)**: 1 + * **Duration (frames)**: 1 + * **Input 1**: P1 Button 1 + +This macro rapidly alternates pressing buttons 1 and 3 – the pattern required to +run in the game. + +Street Fighter II Shoryuken +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This macro allows you to perform a right-facing Shōryūken (Dragon Punch) by +pressing a single key: + +* **Name**: 1P Shoryuken LP +* **Activation sequence**: Kbd M +* **On release**: Complete macro +* **When held**: Prolong step 6 +* **Step 1**: + + * **Delay (frames)**: 0 + * **Duration (frames)**: 1 + * **Input 1**: P1 Right +* **Step 2**: + + * **Delay (frames)**: 1 + * **Duration (frames)**: 1 + * **Input 1**: P1 Down +* **Step 3**: + + * **Delay (frames)**: 0 + * **Duration (frames)**: 1 + * **Input 1**: P1 Down + * **Input 2**: P1 Right +* **Step 4**: + + * **Delay (frames)**: 0 + * **Duration (frames)**: 1 + * **Input 1**: P1 Right +* **Step 5**: + + * **Delay (frames)**: 0 + * **Duration (frames)**: 1 + * **Input 1**: P1 Right + * **Input 2**: P1 Jab Punch +* **Step 6**: + + * **Delay (frames)**: 0 + * **Duration (frames)**: 1 + * **Input 1**: P1 Jab Punch + +This macro has has involves steps that activate multiple inputs. The macro will +complete if the activation sequence is released early, allowing you to tap the +key momentarily to perform the move. Holding the activation sequence holds down +the attack button. diff --git a/plugins/autofire/autofire_save.lua b/plugins/autofire/autofire_save.lua index 3692540cd5b..3c713b4576c 100644 --- a/plugins/autofire/autofire_save.lua +++ b/plugins/autofire/autofire_save.lua @@ -76,7 +76,7 @@ function lib:save_settings(buttons) if not attr then lfs.mkdir(path) elseif attr.mode ~= 'directory' then - emu.print_error(string.format('Error autofire settings macros: "%s" is not a directory\n', path)) + emu.print_error(string.format('Error saving autofire settings: "%s" is not a directory\n', path)) return end if #buttons == 0 then diff --git a/plugins/hiscore/init.lua b/plugins/hiscore/init.lua index 2a9687d1547..0865caa2721 100644 --- a/plugins/hiscore/init.lua +++ b/plugins/hiscore/init.lua @@ -1,16 +1,17 @@ -- hiscore.lua -- by borgar@borgar.net, WTFPL license -- --- This uses MAME's built-in Lua scripting to implment +-- This uses MAME's built-in Lua scripting to implement -- high-score saving with hiscore.dat infom just as older -- builds did in the past. -- -local exports = {} -exports.name = "hiscore" -exports.version = "1.0.0" -exports.description = "Hiscore" -exports.license = "WTFPL license" -exports.author = { name = "borgar@borgar.net" } +local exports = { + name = 'hiscore', + version = '1.0.0', + description = 'Hiscore', + license = 'WTFPL license', + author = { name = 'borgar@borgar.net' } } + local hiscore = exports local hiscore_plugin_path = "" @@ -21,46 +22,97 @@ end function hiscore.startplugin() + local function get_data_path() + return emu.subst_env(manager.machine.options.entries.homepath:value():match('([^;]+)')) .. '/hiscore/' + end + + -- configuration + local config_read = false + local timed_save = true + + -- read configuration file from data directory + local function read_config() + if config_read then + return true + end + local filename = get_data_path() .. 'plugin.cfg' + local file = io.open(filename, 'r') + if file then + local json = require('json') + local parsed_settings = json.parse(file:read('a')) + file:close() + if parsed_settings then + if parsed_settings.only_save_at_exit and (parsed_settings.only_save_at_exit ~= 0) then + timed_save = false + end + -- TODO: other settings? maybe path overrides for hiscore.dat or the hiscore data? + config_read = true + return true + else + emu.print_error(string.format('Error loading hiscore plugin settings: error parsing file "%s" as JSON\n', filename)) + end + end + return false + end + + -- save configuration file + local function save_config() + local path = get_data_path() + local attr = lfs.attributes(path) + if not attr then + lfs.mkdir(path) + elseif attr.mode ~= 'directory' then + emu.print_error(string.format('Error saving hiscore plugin settings: "%s" is not a directory\n', path)) + return + end + local settings = { only_save_at_exit = not timed_save } + -- TODO: other settings? + local filename = path .. 'plugin.cfg' + local json = require('json') + local data = json.stringify(settings, { indent = true }) + local file = io.open(filename, 'w') + if not file then + emu.print_error(string.format('Error saving hiscore plugin settings: error opening file "%s" for writing\n', filename)) + return + end + file:write(data) + file:close() + end + + -- build menu + local function populate_menu() + local items = { } + local setting = timed_save and _p('plugin-hiscore', 'When updated') or _p('plugin-hiscore', 'On exit') + table.insert(items, { _p('plugin-hiscore', 'Hiscore Support Options'), '', 'off' }) + table.insert(items, { '---', '', '' }) + table.insert(items, { _p('plugin-hiscore', 'Save scores'), setting, timed_save and 'l' or 'r' }) + return items + end + + -- handle menu events + local function handle_menu(index, event) + if event == 'left' then + timed_save = false + return true + elseif event == 'right' then + timed_save = true + return true + end + return false + end + local hiscoredata_path = "hiscore.dat"; - local hiscore_path = "hi"; - local config_path = emu.subst_env(manager.options.entries.inipath:value():match("[^;]+") .. "/hiscore.ini"); local current_checksum = 0; local default_checksum = 0; - local config_read = false; local scores_have_been_read = false; local mem_check_passed = false; local found_hiscore_entry = false; - local timed_save = true; local delaytime = 0; - local positions = {}; - -- Configuration file will be searched in the first path defined - -- in mame inipath option. - local function read_config() - if config_read then return true end; - local file = io.open( config_path, "r" ); - if file then - file:close() - emu.print_verbose( "hiscore: config found" ); - local _conf = {} - for line in io.lines(config_path) do - token, spaces, value = string.match(line, '([^ ]+)([ ]+)([^ ]+)'); - if token ~= nil and token ~= '' then - _conf[token] = value; - end - end - hiscore_path = emu.subst_env(_conf["hi_path"] or hiscore_path); - timed_save = _conf["only_save_at_exit"] ~= "1" - -- hiscoredata_path = _conf["dat_path"]; -- don't know if I should do it, but wathever - return true - end - return false - end - local function parse_table ( dsting ) - local _table = {}; + local _table = {} for line in string.gmatch(dsting, '([^\n]+)') do local delay = line:match('^@delay=([.%d]*)') if delay and #delay > 0 then @@ -154,13 +206,13 @@ function hiscore.startplugin() end - local function get_file_name () + local function get_file_name() local r; if emu.softname() ~= "" then local soft = emu.softname():match("([^:]*)$") - r = hiscore_path .. '/' .. emu.romname() .. "_" .. soft .. ".hi"; + r = get_data_path() .. emu.romname() .. "_" .. soft .. ".hi"; else - r = hiscore_path .. '/' .. emu.romname() .. ".hi"; + r = get_data_path() .. emu.romname() .. ".hi"; end return r; end @@ -171,13 +223,13 @@ function hiscore.startplugin() local output = io.open(get_file_name(), "wb"); if not output then -- attempt to create the directory, and try again - lfs.mkdir( hiscore_path ); + lfs.mkdir( get_data_path() ); output = io.open(get_file_name(), "wb"); end emu.print_verbose("hiscore: write_scores output") if output then for ri,row in ipairs(posdata) do - t = {}; + t = {} for i=0,row["size"]-1 do t[i+1] = row["mem"]:read_u8(row["addr"] + i) end @@ -264,16 +316,16 @@ function hiscore.startplugin() end local function reset() - -- the notifier will still be attached even if the running game has no hiscore.dat entry - if mem_check_passed and found_hiscore_entry then - local checksum = check_scores(positions) - if checksum ~= current_checksum and checksum ~= default_checksum then - write_scores(positions) + -- the notifier will still be attached even if the running game has no hiscore.dat entry + if mem_check_passed and found_hiscore_entry then + local checksum = check_scores(positions) + if checksum ~= current_checksum and checksum ~= default_checksum then + write_scores(positions) + end end - end - found_hiscore_entry = false - mem_check_passed = false - scores_have_been_read = false; + found_hiscore_entry = false + mem_check_passed = false + scores_have_been_read = false; end emu.register_start(function() @@ -282,7 +334,7 @@ function hiscore.startplugin() scores_have_been_read = false; last_write_time = -10 emu.print_verbose("Starting " .. emu.gamename()) - config_read = read_config(); + read_config(); local dat = read_hiscore_dat() if dat and dat ~= "" then emu.print_verbose( "hiscore: found hiscore.dat entry for " .. emu.romname() ); @@ -301,17 +353,23 @@ function hiscore.startplugin() found_hiscore_entry = true end end) + emu.register_frame(function() if found_hiscore_entry then tick() end end) + emu.register_stop(function() reset() + save_config() end) + emu.register_prestart(function() reset() end) + + emu.register_menu(handle_menu, populate_menu, _p('plugin-hiscore', 'Hiscore Support')) end return exports diff --git a/plugins/inputmacro/inputmacro_menu.lua b/plugins/inputmacro/inputmacro_menu.lua index 24c8caff29e..afb2071996b 100644 --- a/plugins/inputmacro/inputmacro_menu.lua +++ b/plugins/inputmacro/inputmacro_menu.lua @@ -609,7 +609,7 @@ function populate_macros() end end else - items[#items + 1] = { _p('plugin-inputmacro', 'No macros'), '', 'off' } + items[#items + 1] = { _p('plugin-inputmacro', '[no macros]'), '', 'off' } end macros_start_macro = nil