mame/plugins/autofire/autofire_menu.lua
Jack Li 27f66693c5 Autofire plugin: Save/load fixes (#5093)
* Fixed bugs related to reloading roms

Soft resets would reload autofire settings without saving them first, causing the settings to be lost. This commit adds a check to only reload from the settings file if loading a different rom than before.

Hard resets would leave bad references lying around, causing MAME to crash under certain circumstances (i.e. resetting while in the edit menu and entering the menu again). This commit makes sure to properly clean up and reinitialize menu and button states when resetting.

* Used set_folder to avoid hardcoding plugin name in settings path

* Bumped autofire plugin version
2019-05-21 18:44:44 -04:00

304 lines
8.4 KiB
Lua

local lib = {}
-- Set of all menus
local MENU_TYPES = { MAIN = 0, EDIT = 1, ADD = 2, BUTTON = 3 }
-- Set of sections within a menu
local MENU_SECTIONS = { HEADER = 0, CONTENT = 1, FOOTER = 2 }
-- Last index of header items (above main content) in menu
local header_height = 0
-- Last index of content items (below header, above footer) in menu
local content_height = 0
-- Stack of menus (see MENU_TYPES)
local menu_stack = { MENU_TYPES.MAIN }
-- Button being created/edited
local current_button = {}
-- Inputs that can be autofired (to list in BUTTON menu)
local inputs = {}
-- Returns the section (from MENU_SECTIONS) and the index within that section
local function menu_section(index)
if index <= header_height then
return MENU_SECTIONS.HEADER, index
elseif index <= content_height then
return MENU_SECTIONS.CONTENT, index - header_height
else
return MENU_SECTIONS.FOOTER, index - content_height
end
end
local function create_new_button()
return {
on_frames = 1,
off_frames = 1,
counter = 0
}
end
local function is_button_complete(button)
return button.port and button.field and button.key and button.on_frames and button.off_frames and button.button and button.counter
end
local function is_supported_input(ioport_field)
-- IPT_BUTTON1 through IPT_BUTTON16 in ioport_type enum (ioport.h)
return ioport_field.type >= 64 and ioport_field.type <= 79
end
-- Main menu
local function populate_main_menu(buttons)
local menu = {}
menu[#menu + 1] = {_('Autofire buttons'), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
for index, button in ipairs(buttons) do
-- Assume refresh rate of 60 Hz; maybe better to use screen_device refresh()?
local rate = 60 / (button.on_frames + button.off_frames)
-- Round to two decimal places
rate = math.floor(rate * 100) / 100
local text = button.button.name .. ' [' .. rate .. ' Hz]'
local subtext = manager:machine():input():code_name(button.key)
menu[#menu + 1] = {text, subtext, ''}
end
content_height = #menu
menu[#menu + 1] = {'---', '', ''}
menu[#menu + 1] = {_('Add autofire button'), '', ''}
return menu
end
local function handle_main_menu(index, event, buttons)
local section, adjusted_index = menu_section(index)
if section == MENU_SECTIONS.CONTENT then
if event == 'select' then
current_button = buttons[adjusted_index]
table.insert(menu_stack, MENU_TYPES.EDIT)
return true
elseif event == 'clear' then
table.remove(buttons, adjusted_index)
return true
end
elseif section == MENU_SECTIONS.FOOTER then
if event == 'select' then
current_button = create_new_button()
table.insert(menu_stack, MENU_TYPES.ADD)
return true
end
end
return false
end
-- Add/edit menus (mostly identical)
local function populate_configure_menu(menu)
local button_name = current_button.button and current_button.button.name or _('NOT SET')
local key_name = current_button.key and manager:machine():input():code_name(current_button.key) or _('NOT SET')
menu[#menu + 1] = {_('Input'), button_name, ''}
menu[#menu + 1] = {_('Hotkey'), key_name, ''}
menu[#menu + 1] = {_('On frames'), current_button.on_frames, current_button.on_frames > 1 and 'lr' or 'r'}
menu[#menu + 1] = {_('Off frames'), current_button.off_frames, current_button.off_frames > 1 and 'lr' or 'r'}
end
-- Borrowed from the cheat plugin
local function poll_for_hotkey()
local input = manager:machine():input()
manager:machine():popmessage(_('Press button for hotkey or wait to leave unchanged'))
manager:machine():video():frame_update(true)
input:seq_poll_start('switch')
local time = os.clock()
while (not input:seq_poll()) and (os.clock() < time + 1) do end
local tokens = input:seq_to_tokens(input:seq_poll_final())
manager:machine():popmessage()
manager:machine():video():frame_update(true)
local final_token = nil
for token in tokens:gmatch('%S+') do
final_token = token
end
return final_token and input:code_from_token(final_token) or nil
end
local function handle_configure_menu(index, event)
-- Input
if index == 1 then
if event == 'select' then
table.insert(menu_stack, MENU_TYPES.BUTTON)
return true
else
return false
end
-- Hotkey
elseif index == 2 then
if event == 'select' then
local keycode = poll_for_hotkey()
if keycode then
current_button.key = keycode
return true
else
return false
end
else
return false
end
-- On frames
elseif index == 3 then
manager:machine():popmessage(_('Number of frames button will be pressed'))
if event == 'left' then
current_button.on_frames = current_button.on_frames - 1
elseif event == 'right' then
current_button.on_frames = current_button.on_frames + 1
end
-- Off frames
elseif index == 4 then
manager:machine():popmessage(_('Number of frames button will be released'))
if event == 'left' then
current_button.off_frames = current_button.off_frames - 1
elseif event == 'right' then
current_button.off_frames = current_button.off_frames + 1
end
end
return true
end
local function populate_edit_menu()
local menu = {}
menu[#menu + 1] = {_('Edit autofire button'), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
populate_configure_menu(menu)
content_height = #menu
menu[#menu + 1] = {'---', '', ''}
menu[#menu + 1] = {_('Done'), '', ''}
return menu
end
local function handle_edit_menu(index, event, buttons)
local section, adjusted_index = menu_section(index)
if section == MENU_SECTIONS.CONTENT then
return handle_configure_menu(adjusted_index, event)
elseif section == MENU_SECTIONS.FOOTER then
if event == 'select' then
table.remove(menu_stack)
return true
end
end
return false
end
local function populate_add_menu()
local menu = {}
menu[#menu + 1] = {_('Add autofire button'), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
populate_configure_menu(menu)
content_height = #menu
menu[#menu + 1] = {'---', '', ''}
if is_button_complete(current_button) then
menu[#menu + 1] = {_('Create'), '', ''}
else
menu[#menu + 1] = {_('Cancel'), '', ''}
end
return menu
end
local function handle_add_menu(index, event, buttons)
local section, adjusted_index = menu_section(index)
if section == MENU_SECTIONS.CONTENT then
return handle_configure_menu(adjusted_index, event)
elseif section == MENU_SECTIONS.FOOTER then
if event == 'select' then
table.remove(menu_stack)
if is_button_complete(current_button) then
buttons[#buttons + 1] = current_button
end
return true
end
end
return false
end
-- Button selection menu
local function populate_button_menu()
menu = {}
inputs = {}
menu[#menu + 1] = {_('Select an input for autofire'), '', 'off'}
menu[#menu + 1] = {'---', '', ''}
header_height = #menu
for port_key, port in pairs(manager:machine():ioport().ports) do
for field_key, field in pairs(port.fields) do
if is_supported_input(field) then
menu[#menu + 1] = {field.name, '', ''}
inputs[#inputs + 1] = {
port_name = port_key,
field_name = field_key,
ioport_field = field
}
end
end
end
content_height = #menu
return menu
end
local function handle_button_menu(index, event)
local section, adjusted_index = menu_section(index)
if section == MENU_SECTIONS.CONTENT and event == 'select' then
local selected_input = inputs[adjusted_index]
current_button.port = selected_input.port_name
current_button.field = selected_input.field_name
current_button.button = selected_input.ioport_field
table.remove(menu_stack)
return true
end
return false
end
function lib:init_menu(buttons)
header_height = 0
content_height = 0
menu_stack = { MENU_TYPES.MAIN }
current_button = {}
inputs = {}
end
function lib:populate_menu(buttons)
local current_menu = menu_stack[#menu_stack]
if current_menu == MENU_TYPES.MAIN then
return populate_main_menu(buttons)
elseif current_menu == MENU_TYPES.EDIT then
return populate_edit_menu()
elseif current_menu == MENU_TYPES.ADD then
return populate_add_menu()
elseif current_menu == MENU_TYPES.BUTTON then
return populate_button_menu()
end
end
function lib:handle_menu_event(index, event, buttons)
manager:machine():popmessage()
local current_menu = menu_stack[#menu_stack]
if current_menu == MENU_TYPES.MAIN then
return handle_main_menu(index, event, buttons)
elseif current_menu == MENU_TYPES.EDIT then
return handle_edit_menu(index, event, buttons)
elseif current_menu == MENU_TYPES.ADD then
return handle_add_menu(index, event, buttons)
elseif current_menu == MENU_TYPES.BUTTON then
return handle_button_menu(index, event)
end
end
return lib