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