'OHRRPGCE CUSTOM - General Game Settings editor and most subeditors... also vehicles
'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and the OHRRPGCE Developers
'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability.
'
#include "config.bi"
#include "string.bi"
#include "allmodex.bi"
#include "common.bi"
#include "bcommon.bi"
#include "customsubs.bi"
#include "cglobals.bi"
#include "custom.bi"
#include "thingbrowser.bi"
#include "editorkit.bi"

#include "const.bi"
#include "scrconst.bi"
#include "uiconst.bi"
#include "loading.bi"
#include "slices.bi"

'local subs and functions
DECLARE SUB statcapsmenu ()
DECLARE SUB mark_non_elemental_elementals ()
DECLARE SUB battleoptionsmenu ()
DECLARE SUB equipmergemenu ()
DECLARE SUB update_masterpalette_menu(menu() as string, shaded() as bool, palnum as integer)
DECLARE SUB inputpasw ()
DECLARE SUB nearestui (byval mimicpal as integer, newpal() as RGBcolor, newui() as integer, newbox() as BoxStyle)
DECLARE SUB remappalette (oldmaster() as RGBcolor, oldui() as integer, oldbox() as BoxStyle, newmaster() as RGBcolor, newui() as integer, newbox() as BoxStyle)
DECLARE SUB edit_savegame_options ()
DECLARE SUB edit_button_name_strings()
DECLARE SUB edit_purchase_options_rebuild_menu(menu as MenuDef, st as MenuState, byval purchase_root as NodePtr)
DECLARE SUB edit_purchase_details (byval prod as NodePtr)


SUB vehicle_editor
  DIM menu(15) as string
  DIM veh(39) as integer
  DIM min(39) as integer
  DIM max(39) as integer
  DIM offset(39) as integer
  DIM vehbit() as IntStrPair
  DIM tiletype(8) as string
  DIM vehname as string = ""
  DIM vehicle_id as integer = 0

  DIM state as MenuState
  state.size = 24
  state.last = UBOUND(menu)
  state.need_update = YES

  a_append vehbit(), -1, ""
  a_append vehbit(), -1, " Appearance"
  a_append vehbit(), 4,  "Do not hide leader"
  a_append vehbit(), 5,  "Do not hide party"
  a_append vehbit(), 8,  "Disable flying shadow"
  a_append vehbit(), -1, ""
  a_append vehbit(), -1, " Movement"
  a_append vehbit(), 0,  "Pass through walls"
  a_append vehbit(), 1,  "Pass through NPCs"
  a_append vehbit(), 9,  "Ignore harmtiles"
  a_append vehbit(), 6,  "Dismount one space ahead"
  a_append vehbit(), 7,  "Pass walls while dismounting"
  a_append vehbit(), -1, ""
  a_append vehbit(), -1, " Activation"
  a_append vehbit(), 2,  "Enable NPC activation"
  a_append vehbit(), 3,  "Enable door use"

  tiletype(0) = "default"
  tiletype(1) = "A"
  tiletype(2) = "B"
  tiletype(3) = "A and B"
  tiletype(4) = "A or B"
  tiletype(5) = "not A"
  tiletype(6) = "not B"
  tiletype(7) = "neither A nor B"
  tiletype(8) = "everywhere"

  min(3) = 0: max(3) = 5: offset(3) = 8             'speed
  FOR i as integer = 0 TO 3
    min(5 + i) = 0: max(5 + i) = 8: offset(5 + i) = 17 + i
  NEXT i
  min(9) = -1: max(9) = 255: offset(9) = 11 'battles
  min(10) = -2: max(10) = gen(genMaxRegularScript): offset(10) = 12 'use button
  min(11) = -2: max(11) = gen(genMaxRegularScript): offset(11) = 13 'menu button
  min(12) = -max_tag(): max(12) = max_tag(): offset(12) = 14 'tag
  min(13) = gen(genMaxRegularScript) * -1: max(13) = gen(genMaxTextbox): offset(13) = 15'mount
  min(14) = gen(genMaxRegularScript) * -1: max(14) = gen(genMaxTextbox): offset(14) = 16'dismount
  min(15) = 0: max(15) = 99: offset(15) = 21'dismount

  LoadVehicle game + ".veh", veh(), vehname, vehicle_id

  setkeys YES
  DO
    setwait 55
    setkeys YES
    IF state.need_update THEN
      state.need_update = NO
      menu(0) = "Previous Menu"
      menu(1) = "Vehicle " & vehicle_id
      menu(2) = "Name: " + vehname

      IF veh(offset(3)) = 3 THEN
        menu(3) = "Speed: 10"
      ELSE
        menu(3) = "Speed: " & veh(8)
      END IF

      menu(4) = "Vehicle Bitsets..." '9,10

      menu(5) = "Override walls: "
      menu(6) = "Blocked by: "
      menu(7) = "Mount from: "
      menu(8) = "Dismount to: "
      FOR i as integer = 0 TO 3
        menu(5 + i) = menu(5 + i) + tiletype(bound(veh(offset(5 + i)), 0, 8))
      NEXT i

      DIM tmp as string

      SELECT CASE veh(offset(9))
        CASE -1
          tmp = "disabled"
        CASE 0
          tmp = "enabled"
        CASE ELSE
          tmp = "formation set " & veh(offset(9))
      END SELECT
      menu(9) = "Random Battles: " + tmp '11

      FOR i as integer = 0 TO 1
        SELECT CASE veh(offset(10 + i))
          CASE -2
            tmp = "disabled"
          CASE -1
            tmp = "menu"
          CASE 0
            tmp = "dismount"
          CASE ELSE
            tmp = "script " + scriptname(ABS(veh(offset(10 + i))))
        END SELECT
        IF i = 0 THEN menu(10 + i) = "Use button: " + tmp'12
        IF i = 1 THEN menu(10 + i) = "Menu button: " + tmp'13
      NEXT i

      menu(12) = tag_set_caption(veh(offset(12)), "If riding set tag")

      SELECT CASE veh(offset(13))
        CASE 0
          tmp = "[script/textbox]"
        CASE IS < 0
          tmp = "run script " + scriptname(ABS(veh(offset(13))))
        CASE IS > 0
          tmp = "text box " & veh(offset(13))
      END SELECT
      menu(13) = "On Mount: " + tmp
      SELECT CASE veh(offset(14))
        CASE 0
          tmp = "[script/textbox]"
        CASE IS < 0
          tmp = "run script " + scriptname(ABS(veh(offset(14))))
        CASE IS > 0
          tmp = "text box " & veh(offset(14))
      END SELECT
      menu(14) = "On Dismount: " + tmp
      menu(15) = "Elevation: " & veh(offset(15)) & " pixels"
    END IF
    IF keyval(ccCancel) > 1 THEN EXIT DO
    IF keyval(scF1) > 1 THEN show_help "vehicle_editor"
    usemenu state
    SELECT CASE state.pt
      CASE 0
        IF enter_space_click(state) THEN
          EXIT DO
        END IF
      CASE 1
        DIM savept as integer = vehicle_id
        IF intgrabber_with_addset(vehicle_id, 0, gen(genMaxVehicle), 32767, "vehicle") THEN
          SaveVehicle game + ".veh", veh(), vehname, savept
          IF vehicle_id > gen(genMaxVehicle) THEN  '--adding set
            gen(genMaxVehicle) = vehicle_id
            flusharray veh()
            vehname = ""
          ELSE
            LoadVehicle game + ".veh", veh(), vehname, vehicle_id
          END IF
          state.need_update = YES
        END IF
      CASE 2
        DIM oldname as string = vehname
        strgrabber vehname, 15
        IF oldname <> vehname THEN state.need_update = YES
      CASE 3, 5 TO 9, 15
        IF intgrabber(veh(offset(state.pt)), min(state.pt), max(state.pt)) THEN
          state.need_update = YES
        END IF
      CASE 12 '--tags
        IF tag_set_grabber(veh(offset(state.pt)), state) THEN
          state.need_update = YES
        END IF
      CASE 4
        IF enter_space_click(state) THEN
          editbitset veh(), 9, vehbit(), "vehicle_bitsets"
        END IF
      CASE 10, 11
        IF enter_space_click(state) THEN
          veh(offset(state.pt)) = large(0, veh(offset(state.pt)))
          scriptbrowse veh(offset(state.pt)), plottrigger, "vehicle plotscript"
          state.need_update = YES
        ELSEIF scrintgrabber(veh(offset(state.pt)), min(state.pt), max(state.pt), ccLeft, ccRight, 1, plottrigger) THEN
          state.need_update = YES
        END IF
      CASE 13, 14 '--mount and dismount actions
        IF veh(offset(state.pt)) > 0 ANDALSO textboxgrabber(veh(offset(state.pt)), state) THEN
          state.need_update = YES
        ELSEIF enter_space_click(state) THEN
          DIM temptrig as integer = large(0, -veh(offset(state.pt)))
          scriptbrowse temptrig, plottrigger, "vehicle plotscript"
          veh(offset(state.pt)) = -temptrig
          state.need_update = YES
        ELSEIF scrintgrabber(veh(offset(state.pt)), min(state.pt), max(state.pt), ccLeft, ccRight, -1, plottrigger) THEN
          state.need_update = YES
        END IF
    END SELECT
    clearpage dpage
    standardmenu menu(), state, , , dpage
    SWAP vpage, dpage
    setvispage vpage
    dowait
  LOOP
  SaveVehicle game + ".veh", veh(), vehname, vehicle_id
END SUB

SUB export_master_palette ()
  DIM extension as string = "." & pick_graphics_export_format()
  DIM filename as string
  filename = inputfilename("File to export to?", extension, "", "input_file_export_masterpal")
  IF filename = "" THEN EXIT SUB
  filename &= extension

  DIM outsurf as Surface ptr
  gfx_surfaceCreate(16, 16, SF_32bit, SU_Staging, @outsurf)

  FOR i as integer = 0 TO 255
    outsurf->pColorData[i] = master(i)
  NEXT
  surface_export_image(outsurf, filename)
  gfx_surfaceDestroy(@outsurf)
END SUB

'Position of master palette in master_palette_menu
CONST colgridx = 33, colgridy = 114
CONST cellheight = 5

PRIVATE SUB highlight_col(colidx as integer, state as MenuState)
  drawbox colgridx + (colidx MOD 16) * 16, colgridy + (colidx \ 16) * cellheight, 16, cellheight, uilook(uiHighlight + state.tog), 1, dpage
END SUB

SUB master_palette_menu ()
  DIM menu(11) as string
  DIM menu_display(11) as string
  DIM shaded(11) as bool
  DIM oldpal as integer
  DIM palnum as integer = activepalette
  load_master_and_uicol palnum
  setpal master()

  DIM selectst as SelectTypeState
  DIM state as MenuState
  state.size = 12
  state.last = UBOUND(menu)
  state.pt = 1

  update_masterpalette_menu menu(), shaded(), palnum

  setkeys YES
  DO
    setwait 55
    setkeys YES

    IF keyval(ccCancel) > 1 THEN EXIT DO
    IF keyval(scF1) > 1 THEN show_help "master_palette_menu"
    usemenu state

    oldpal = palnum
    IF keyval(ccRight) > 1 AND palnum = gen(genMaxMasterPal) THEN
      palnum += 1
      IF needaddset(palnum, gen(genMaxMasterPal), "Master Palette") THEN
        IF importmasterpal("", palnum) THEN
          setpal master()
          state.need_update = YES
        ELSE
          palnum -= 1
          gen(genMaxMasterPal) = palnum
        END IF
      END IF
      setkeys
    END IF
    IF state.pt = 1 THEN
      intgrabber(palnum, 0, gen(genMaxMasterPal))
    ELSE
      IF keyval(ccLeft) > 1 THEN palnum += gen(genMaxMasterPal)
      IF keyval(ccRight) > 1 THEN palnum += 1
      palnum = palnum MOD (gen(genMaxMasterPal) + 1)
    END IF
    IF palnum <> oldpal THEN
      load_master_and_uicol palnum
      setpal master()
      state.need_update = YES
    END IF

    IF keyval(scCtrl) > 0 ANDALSO keyval(scD) > 1 THEN
      'Ctrl-D Rescue key
      GuessDefaultUIColors master(), uilook()
      IF yesno("Really reset UI colors to defaults?") THEN
        SaveUIColors uilook(), boxlook(), palnum
      ELSE
        load_master_and_uicol palnum
      END IF
    END IF

    IF enter_space_click(state) THEN
      SELECT CASE state.pt
        CASE 0
          EXIT DO
        CASE 2
          IF importmasterpal("", palnum) THEN
            setpal master()
            state.need_update = YES
          END IF
        CASE 3
          export_master_palette
        CASE 4
          ui_color_editor palnum
        CASE 5
          IF yesno("Really replace all UI colors and box styles?") THEN
            nearestui activepalette, master(), uilook(), boxlook()
            SaveUIColors uilook(), boxlook(), palnum
          END IF
        CASE 6
          notification "Copying UI colors without remapping often leads to bad colors. Normally you want to use ""Nearest-match active palette's UI colors"" instead. Press Ctrl-D to reset UI colors if the menu becomes unreadable."
          IF yesno("Really replace all UI colors and box styles?") THEN
            LoadUIColors uilook(), boxlook(), activepalette, master()
            SaveUIColors uilook(), boxlook(), palnum
          END IF
        CASE 7
          'Reset UI colors
          IF yesno("Really reset UI colors to defaults?") THEN
            GuessDefaultUIColors master(), uilook()
            SaveUIColors uilook(), boxlook(), palnum
          END IF
        CASE 8
          'Reset box styles
          IF yesno("Really reset box styles to defaults?") THEN
            GuessDefaultBoxStyles master(), boxlook(), YES  'colors_only=YES
            SaveUIColors uilook(), boxlook(), palnum
          END IF
        CASE 9
          gen(genMasterPal) = palnum
          ' Keep obsolete .MAS lump up to date
          unconvertpalette
          'Instant live-previewing
          xbsave game + ".gen", gen(), 1000
          state.need_update = YES
        CASE 10
          activepalette = palnum
          state.need_update = YES
        CASE 11
          ui_boxstyle_editor palnum
      END SELECT
    END IF

    IF state.need_update THEN
      state.need_update = NO
      update_masterpalette_menu menu(), shaded(), palnum
    END IF

    IF select_by_typing(selectst) THEN
      select_on_word_boundary menu(), selectst, state
    END IF

    'draw the menu
    clearpage dpage
    highlight_menu_typing_selection menu(), menu_display(), selectst, state
    standardmenu menu_display(), state, shaded(), , , dpage

    FOR i as integer = 0 TO 255
      rectangle colgridx + (i MOD 16) * 16, colgridy + (i \ 16) * cellheight, 16, cellheight, i, dpage
    NEXT
    'Highlight uilook colors
    SELECT CASE state.pt
      CASE 4, 5, 6, 7
        FOR i as integer = 0 TO uiColorLast
          highlight_col uilook(i), state
        NEXT i
    END SELECT
    'Highlight box style colors
    SELECT CASE state.pt
      CASE 5, 6, 8, 11
        FOR i as integer = 0 TO uiBoxLast
          highlight_col boxlook(i).bgcol, state
          highlight_col boxlook(i).edgecol, state
        NEXT i
    END SELECT

    SWAP vpage, dpage
    setvispage vpage
    dowait
  LOOP

  IF activepalette <> palnum THEN
    load_master_and_uicol activepalette
    setpal master()
  END IF
END SUB

SUB update_masterpalette_menu(menu() as string, shaded() as bool, palnum as integer)
  menu(0) = "Previous Menu"
  menu(1) = "<- Master Palette " & palnum & " ->"
  menu(2) = "Replace this Master Palette"
  menu(3) = "Export this palette"
  menu(4) = "Edit User Interface Colors..."
  menu(5) = "Nearest-match active palette's UI data"
  menu(6) = "Copy active palette's UI data"
  menu(7) = "Reset UI Colors to defaults"
  menu(8) = "Reset Box Styles to defaults"
  IF palnum = gen(genMasterPal) THEN
    menu(9) = "Current default in-game Master Palette"
  ELSE
    menu(9) = "Set as in-game Master Palette"
  END IF
  IF palnum = activepalette THEN
    menu(10) = "Current active editing palette"
  ELSE
    menu(10) = "Set as active editing palette"
  END IF
  menu(11) = "Edit Box Styles..."

  FOR i as integer = 0 TO UBOUND(shaded)
    shaded(i) = NO
  NEXT
  IF palnum = activepalette THEN
    shaded(5) = YES
    shaded(6) = YES
    shaded(10) = YES
  END IF
  IF palnum = gen(genMasterPal) THEN
    shaded(9) = YES
  END IF
END SUB

'Import master palette from a file and save it, together with default UI/box colors.
'Note: also modifies activepalette, master(), uilook(), and boxlook()
'to the new palette, but doesn't call setpal.
'Returns true on success
FUNCTION importmasterpal (filename as string = "", palnum as integer) as bool
  STATIC default as string
  IF filename = "" THEN
    filename = browse(browseMasterPal, default, "", "browse_import_master_palette")
    IF filename = "" THEN RETURN NO
  END IF
  IF LCASE(justextension(filename)) = "mas" THEN
    xbload filename, buffer(), "MAS load error"
    convertpalette buffer(), master()
  ELSE
    DIM info as ImageFileInfo = image_read_info(filename)
    IF info.size = XY(16, 16) THEN
      palette_from_16x16_image filename, master()
    ELSE
      image_load_palette filename, master()
    END IF
  END IF
  importmasterpal master(), palnum
  RETURN YES
END FUNCTION

'Save new master palette newmaster(), together with default UI/box colors.
'Note: also modifies activepalette, master(), uilook(), and boxlook()
'to the new palette, but doesn't call setpal.
SUB importmasterpal (newmaster() as RGBcolor, palnum as integer)
  FOR i as integer = 0 TO 255
    master(i) = newmaster(i)
    master(i).a = 255
  NEXT

  'get a default set of ui colours - nearest match to the current
  nearestui activepalette, master(), uilook(), boxlook()

  IF palnum > gen(genMaxMasterPal) THEN gen(genMaxMasterPal) = palnum
  savepalette master(), palnum
  SaveUIColors uilook(), boxlook(), palnum
END SUB

SUB nearestui (byval mimicpal as integer, newmaster() as RGBcolor, newui() as integer, newbox() as BoxStyle)
  'finds the nearest match newui() in newpal() to mimicpal's ui colours
  DIM referencepal(255) as RGBcolor
  DIM referenceui(uiColorLast) as integer
  DIM refboxstyle(uiBoxLast) as BoxStyle
  loadpalette referencepal(), mimicpal
  LoadUIColors referenceui(), refboxstyle(), mimicpal, referencepal()
  remappalette referencepal(), referenceui(), refboxstyle(), newmaster(), newui(), newbox()
END SUB

'Copy UI colours and box styles from one master palette to another, while remapping colours.
SUB remappalette (oldmaster() as RGBcolor, oldui() as integer, oldbox() as BoxStyle, newmaster() as RGBcolor, newui() as integer, newbox() as BoxStyle)
  'first the ui
  FOR i as integer = 0 TO UBOUND(oldui)
    WITH oldmaster(oldui(i))
      newui(i) = nearcolor(newmaster(), .r, .g, .b, , oldui(i))
    END WITH
  NEXT
  'Then the boxstyles
  FOR i as integer = 0 TO UBOUND(oldbox)
    WITH oldmaster(oldbox(i).bgcol)
      newbox(i).bgcol = nearcolor(newmaster(), .r, .g, .b, , oldbox(i).bgcol)
    END WITH
    WITH oldmaster(oldbox(i).edgecol)
      newbox(i).edgecol = nearcolor(newmaster(), .r, .g, .b, , oldbox(i).edgecol)
    END WITH
    newbox(i).border = oldbox(i).border
  NEXT
END SUB

SUB inputpasw()
  DIM tog as integer = 0
  DIM oldpassword as integer = (checkpassword("") = 0)
  DIM pas as string
  setkeys YES
  DO
    setwait 55
    setkeys YES
    tog = tog XOR 1
    IF keyval(ccCancel) > 1 THEN EXIT DO
    IF keyval(scEnter) > 1 THEN
      '  IF oldpassword = NO THEN
      writepassword pas
      EXIT DO
    END IF
    IF keyval(scF1) > 1 THEN show_help "input_password"
    strgrabber pas, 17
    clearpage dpage
    textcolor uilook(uiMenuItem), 0
    printstr "You can require a password for this", 0, 0, dpage
    printstr "game to be opened in " + CUSTOMEXE, 0, 8, dpage
    printstr "This does not encrypt your file, and", 0, 16, dpage
    printstr "should not be considered as any security", 0, 24, dpage
    IF oldpassword THEN
      printstr "PASSWORD SET. NEW PASSWORD:", 30, 64, dpage
      IF LEN(pas) = 0 THEN printstr "(Hit Enter to remove)", 30, 94, dpage
    ELSE
      printstr "NO PASSWORD. NEW PASSWORD:", 30, 64, dpage
    END IF
    IF LEN(pas) THEN
      textcolor uilook(uiSelectedItem + tog), uilook(uiHighlight)
      printstr pas, 30, 74, dpage
    ELSE
      textcolor uilook(uiMenuItem), uilook(uiHighlight)
      printstr "(NONE)", 30, 74, dpage
    END IF
    SWAP vpage, dpage
    setvispage vpage
    dowait
  LOOP
END SUB


TYPE PlatformOptionsEditor EXTENDS EditorKit
  DECLARE CONSTRUCTOR ()
  DECLARE SUB define_items ()
  DECLARE SUB edit_button_scancode (deletable as bool)
  DECLARE SUB button_list (parent as Node ptr)
  DECLARE SUB gamepad_button_menu_items (byval gamepad_node as NodePtr, use_dpad as bool, default_scancodes() as integer)

  gen_root as NodePtr
  vgp as NodePtr
  vgpb as NodePtr

  buttons(11) as string = {"A", "B", "X", "Y", "L1", "R1", "L2", "R2", "UP", "RIGHT", "DOWN", "LEFT"}
  ouya_buttons(11) as string = {"O", "A", "U", "Y", "L1", "R1", "L2", "R2", "UP", "RIGHT", "DOWN", "LEFT"}
  'xperia_buttons(11) as string = {"X?", "O?", "Square", "Triangle", "L1", "R1", "Doesn't Exist", "Doesn't Exist", "UP", "RIGHT", "DOWN", "LEFT"}
  default_scancodes(11) as integer = {scEnter, scESC, scESC, scESC, scPageUp, scPageDown, scHome, scEnd, scUp, scRight, scDown, scLeft}
  default_scancodes_multiplayer(11) as integer = {scEnter, scESC, scESC, scESC, scPageUp, scPageDown, scHome, scEnd, scUp, scRight, scDown, scLeft}
  player_who(3) as string = {"First", "Second", "Third", "Fourth"}
END TYPE

SUB edit_platform_controls ()
  DIM editor as PlatformOptionsEditor
  editor.run()
  write_general_reld()
END SUB

CONSTRUCTOR PlatformOptionsEditor
  helpkey = "general_platform_options"

  gen_root = get_general_reld()
  DIM mobile as Node ptr = GetOrCreateChild(gen_root, "mobile_options")
  GetOrCreateChild(gen_root, "console_options")
  GetOrCreateChild(gen_root, "gamepad")

  vgp = mobile."virtual_gamepad".ptr
  IF vgp = NULL THEN
    vgp = AppendChildNode(mobile, "virtual_gamepad")
    AppendChildNode(vgp, "button", scEnter)
    AppendChildNode(vgp, "button", scESC)
  END IF

  vgpb = mobile."virtual_gamepad_battle".ptr
  IF vgpb = NULL THEN
    vgpb = AppendChildNode(mobile, "virtual_gamepad_battle")
    AppendChildNode(vgpb, "button", scEnter)
    AppendChildNode(vgpb, "button", scESC)
  END IF
END CONSTRUCTOR

' Edit 'value' as a scancode
SUB PlatformOptionsEditor.edit_button_scancode(deletable as bool)
  IF activate THEN
    DIM sc as integer = prompt_for_scancode()
    IF sc >= 0 THEN
      value = sc
      edited = YES
    END IF
  ELSEIF process THEN
    IF deletable ANDALSO (keyval(scDelete) > 1 ORELSE (value = 0 ANDALSO keyval(scBackspace) > 1)) THEN
      delete_node
    ELSE
      edit_int value, 0, scLASTASSIGNED
    END IF
  END IF
END SUB

SUB PlatformOptionsEditor.button_list(parent as Node ptr)
  CONST vbutton_max = 6
  DIM button as Node ptr = FirstChild(parent, "button")
  DIM vbutton_count as integer
  FOR vbutton_count = 0 TO vbutton_max - 1
    IF button = 0 THEN EXIT FOR
    defitem "Button " & vbutton_count & ":"
    val_node_int button
    button = NextSibling(button, "button")  'Before button can be deleted
    edit_button_scancode YES  'Delete/backspace deletes the node
    set_caption rpad(scancodename(value), " ", 13) & "(" & value & ")"
  NEXT

  IF vbutton_count > vbutton_max THEN
    debug "Warning: shouldn't have more than " & vbutton_max & " mobile virtual gamepad buttons"
  ELSEIF vbutton_count < vbutton_max THEN
    IF defitem_act("Add another button") THEN
      AppendChildNode(parent, "button", 0)
    END IF
  END IF
END SUB

SUB PlatformOptionsEditor.gamepad_button_menu_items (byval gamepad_node as NodePtr, use_dpad as bool, default_scancodes() as integer)
  DIM ub as integer = UBOUND(buttons)
  '--always assumes that the dpad is at the end of the buttons() list
  IF NOT use_dpad THEN ub = ub - 4
  FOR bnum as integer = 0 TO ub
    DIM b as string = buttons(bnum)
    defitem "Button " & rpad(b, " ", 2) & ":"
    val_node_int gamepad_node, "/" & b
    DIM padnum as integer = 12 - large(0, LEN(b) - 2)
    edit_button_scancode NO  'Doesn't delete the node
    IF value THEN
      set_caption rpad(scancodename(value), " ", padnum) & "(" & value & ")"
    ELSE
      set_caption rpad(scancodename(default_scancodes(bnum)), " ", padnum) & "(Default)"
    END IF
    set_tooltip fgcol_text("OUYA Controller: " & ouya_buttons(bnum), uilook(uiSelectedDisabled))
  NEXT bnum
END SUB

SUB PlatformOptionsEditor.define_items()
  DIM gen_root as Node ptr = this.gen_root  'Work around reloadbasic errors

  defitem "Touch Textboxes:"
  IF get_gen_bool("/mouse/click_textboxes") THEN
    set_caption "YES [Click to advance textboxes ON]"
  ELSE
    edit_node_bool gen_root, "/mobile_options/touch_textboxes/enabled"
  END IF

  defitem "Phone/Tablet Virtual Gamepad:"
  invert_bool
  edit_node_exists gen_root, "/mobile_options/disable_virtual_gamepad"
  captions_yesno "Enabled", "Disabled"

  IF value THEN  'Virtual Gamepad enabled
    defitem "Hide V.Gamepad when player suspended:"
    edit_node_bool gen_root, "/mobile_options/hide_virtual_gamepad_when_suspendplayer"

    section "Phone/Tablet Virtual Gamepad (Normal)"
    button_list vgp
    section "Phone/Tablet Virtual Gamepad (Battle Mode)"
    button_list vgpb
  END IF

  spacer
  section "Gamepad Buttons (Android only)"
  gamepad_button_menu_items gen_root."gamepad".ptr, NO, default_scancodes()

  section "Multiplayer Gamepad Options"
  IF NOT gen_root."multiplayer_gamepads".exists THEN
    '--Although all SDL2 platforms support multiplayer gamepad, OUYA multiplayer works differently...
    defitem "Enable OUYA multiplayer..."
    edit_node_exists gen_root, "/multiplayer_gamepads"
    'NOTE: once you enable (create) multiplayer_gamepads there's no option to delete them!
  ELSE
    FOR pnum as integer = 1 TO 3
      DIM player as Node ptr = NodeByPath(gen_root, "/multiplayer_gamepads/player[" & pnum & "]", YES) 'create=YES
      subsection player_who(pnum) & " player buttons"
      gamepad_button_menu_items player, YES, default_scancodes_multiplayer()
    NEXT
  END IF

  spacer
  IF defitem_act("Button name strings for text boxes...") THEN edit_button_name_strings
END SUB

FUNCTION prompt_for_scancode () as KBScancode
  'Return a user-specified scancode, or -1 if cancelled.

  DIM result as KBScancode = -1

  DIM instructions as string = "To choose a key, press and hold the desired key until the flashing bar fills. Tap ESC briefly to cancel."
  DIM toomany as string = "That is too many keys, please just press one."
  DIM nowrelease as string = "New key confirmed! Now release it."

  DIM pressed as KBScancode
  DIM presstime as double ' the timestamp when the key was first pressed
  DIM heldfor as double ' the number of seconds the key has been held
  CONST threshold = 0.4  ' seconds to hold to confirm a key
  DIM keylist as string
  DIM keyname as string
  DIM count as integer
  DIM confirmed as KBScancode = 0
  DIM bartog as bool = NO

  DIM root as Slice Ptr
  root = NewSliceOfType(slContainer)
  WITH *root
    .Fill = YES
  END WITH

  DIM box as Slice Ptr
  box = NewSliceOfType(slRectangle, root)
  WITH *box
    .width = 308
    .height = 48
    .AnchorHoriz = alignCenter
    .AnchorVert = alignBottom
    .AlignHoriz = alignCenter
    .AlignVert = alignBottom
    .paddingLeft = 8
    .paddingRight = 8
    .paddingTop = 8
    .paddingBottom = 8
  END WITH
  ChangeRectangleSlice box, 1

  DIM infosl as Slice Ptr
  infosl = NewSliceofType(slText, box)
  infosl->Fill = YES
  ChangeTextSlice infosl, instructions, , , YES

  DIM bar as Slice Ptr
  bar = NewSliceOfType(slRectangle, root)
  WITH *bar
    .width = 0
    .height = 10
    .AnchorHoriz = alignCenter
    .AnchorVert = alignTop
    .AlignHoriz = alignCenter
    .AlignVert = alignTop
  END WITH

  DIM keybox as Slice Ptr
  keybox = NewSliceofType(slContainer, root)
  WITH *keybox
    .AnchorHoriz = alignCenter
    .AnchorVert = alignCenter
    .AlignHoriz = alignCenter
    .AlignVert = alignCenter
  END WITH

  DIM keysl as Slice Ptr
  keysl = NewSliceofType(slText, keybox)

  setkeys
  DO
    setwait 55
    setkeys
    IF getquitflag THEN result = -1: EXIT DO  'Unlike other menus, ESC doesn't quit, so have to check this manually

    IF confirmed THEN
      'A key has been confirmed, wait for it to be released
      ChangeTextSlice infosl, nowrelease
      bartog = NO
      IF keyval(pressed) = 0 THEN
        result = pressed
        EXIT DO
      END IF
    ELSE
      keylist = ""
      count = 0
      FOR i as KBScancode = 0 to scLAST
        SELECT CASE i
          CASE scAnyEnter, scUnfilteredAlt, scAlt, scShift, scCtrl, scMeta
            'These are less-specific keys
            CONTINUE FOR
          CASE scNumLock, scCapsLock, scScrollLock
            'Skip the "lock" family of keys. They can sometimes behave as stuck down,
            'and they don't make much sense for gamepad button remapping.
            'We also ignore them for KeyboardState.anykey() and the "wait for any key" command
            CONTINUE FOR
        END SELECT
        IF keyval(i) > 0 THEN
          keyname = scancodename(i, YES)
          IF keyname <> "" THEN
            count += 1
            IF pressed <> i THEN
              presstime = TIMER
            END IF
            pressed = i
            keylist &= keyname & !"\n"
            IF count = 1 THEN
              keybox->width = LEN(keyname) * 8
            END IF
            keybox->height = count * 10
          END IF
        END IF
      NEXT i
      ChangeTextSlice keysl, keylist
      ChangeTextSlice infosl, instructions
      IF count = 0 THEN
        'No buttons are being held down.
        IF pressed = scESC THEN EXIT DO
        IF pressed = scF1 THEN show_help "prompt_for_scancode"
        pressed = 0
        bartog = NO
        bar->width = 0
      ELSEIF count > 1 THEN
        'Too manu buttons are being held down, so none of them count.
        ChangeTextSlice infosl, toomany
        pressed = 0
        bartog = NO
        bar->width = 0
      ELSE
        'Just one button is being held down, so it might be valid
        heldfor = TIMER - presstime
        bar->width = int(root->width / threshold * heldfor)
        bartog = NOT bartog
        IF heldfor > threshold THEN
          confirmed = pressed
        END IF
      END IF
    END IF

    IF bartog THEN
      ChangeRectangleSlice bar, , uiTimeBar, uiTimeBarFull
    ELSE
      ChangeRectangleSlice bar, , uiTimeBarFull, uiTimeBar
    END IF

    clearpage dpage
    DrawSlice root, dpage

    SWAP vpage, dpage
    setvispage vpage
    dowait
  LOOP

  DeleteSlice @root
  RETURN result
END FUNCTION

CONST buttonnameEXIT as integer = 0
CONST buttonnameADD as integer = 1
CONST buttonnameDATA as integer = 2
CONST buttonnameBROWSE as integer = 3

SUB edit_button_name_strings()
  DIM plat_key(...) as string = {"keyboard", "touchscreen", "ouya", "console"}
  DIM plat_detail(...) as string = {_
      "Windows, Mac and Linux",_
      "Any touchscreen device (Android)",_
      "OUYA Console",_
      "Other Android Consoles"}

  DIM menu as MenuDef

  DIM keynum as integer = 0

  DIM st as MenuState
  st.active = YES
  st.need_update = YES

  DIM gen_root as NodePtr = get_general_reld()

  '-- the buttonnames node will be created with defaults in
  '-- update_general_data() if it does not exist already.
  DIM buttonnames as NodePtr = gen_root."buttonnames".ptr

  DIM codenum as integer
  DIM freenum as integer

  DIM t as integer
  DIM node as NodePtr
  DIM strnode as NodePtr
  DIM curstr as string

  setkeys YES
  DO
    setwait 55
    setkeys YES
    IF keyval(scF1) > 1 THEN show_help "edit_button_name_strings"
    IF keyval(ccCancel) > 1 THEN EXIT DO

    IF st.need_update THEN
      st.need_update = NO

      InitLikeStandardMenu menu

      append_menu_item menu, "Previous menu..."
      menu.last->t = buttonnameEXIT

      append_menu_item menu, CHR(27) & " Platform: " & plat_key(keynum) & " " & CHR(26)
      menu.last->t = buttonnameBROWSE

      append_menu_item menu, " " & plat_detail(keynum)
      menu.last->unselectable = YES
      menu.last->col = uilook(eduiHeading)

      freenum = 0
      READNODE buttonnames
        WITHNODE buttonnames."code" as codenode
          codenum = GetInteger(codenode)
          IF freenum <= codenum THEN freenum = codenum + 1
          curstr = GetChildNodeStr(codenode, plat_key(keynum), default_button_name_for_platform(plat_key(keynum), codenum))
          append_menu_item menu, "${B" & codenum & "} = " & curstr
          menu.last->t = buttonnameDATA
          menu.last->dataptr = codenode
        END WITHNODE
      END READNODE

      append_menu_item menu, "Add a new button name"
      menu.last->t = buttonnameADD

      init_menu_state st, menu
    END IF '--done updating menu

    usemenu st, menu

    t = menu.items[st.pt]->t
    node = menu.items[st.pt]->dataptr

    IF t = buttonnameBROWSE THEN
      IF keyval(ccLeft) > 1 THEN
        loopvar keynum, 0, UBOUND(plat_key), -1
        st.need_update = YES
      END IF
      IF keyval(ccRight) > 1 THEN
        loopvar keynum, 0, UBOUND(plat_key), 1
        st.need_update = YES
      END IF
    ELSEIF t = buttonnameDATA THEN
      curstr = GetChildNodeStr(node, plat_key(keynum), default_button_name_for_platform(plat_key(keynum), GetInteger(node)))
      IF strgrabber(curstr, 16) THEN
        SetChildNode(node, plat_key(keynum), curstr)
        st.need_update = YES
      END IF
    END IF

    IF enter_space_click(st) THEN
      SELECT CASE t
        CASE buttonnameEXIT: EXIT DO
        CASE buttonnameADD:
          DIM newcodenode as NodePtr = AppendChildNode(buttonnames, "code", freenum)
          FOR i as integer = 0 TO UBOUND(plat_key)
            AppendChildNode(newcodenode, plat_key(i), "")
          NEXT i
          st.need_update = YES
      END SELECT
    END IF

    clearpage dpage

    draw_menu menu, st, dpage
    SWAP vpage, dpage
    setvispage vpage

    dowait
  LOOP
END SUB


CONST editpurchEXIT as integer = 1
CONST editpurchSTRING as integer = 2
CONST editpurchTAGSET as integer = 3
CONST editpurchADD as integer = 4
CONST editpurchPROD as integer = 5
CONST editpurchLONGSTRING as integer = 6
CONST editpurchINT as integer = 7
CONST editpurchSTOREDEFAULT as integer = 8
CONST editpurchSTOREOUYA as integer = 9
CONST editpurchTOGGLEREPEATABLE as integer = 10 'sucks that this is so specific
CONST editpurchKEYDER as integer = 11

SUB edit_purchase_options ()

  DIM menu as MenuDef

  DIM st as MenuState
  st.active = YES
  st.need_update = YES

  REDIM enabled(0) as bool

  DIM gen_root as NodePtr = get_general_reld()
  DIM purchase_root as NodePtr = GetOrCreateChild(gen_root, "purchase_root")
  DIM products as NodePtr = GetOrCreateChild(purchase_root, "products")

  DIM t as integer
  DIM sub_t as integer
  DIM node as NodePtr

  DIM default_stores(1) as string = {"disabled", "paypal"}
  DIM ouya_stores(1) as string = {"disabled", "ouya"}

  setkeys YES
  DO
    setwait 55
    setkeys YES
    IF keyval(scF1) > 1 THEN show_help "edit_purchase_options"
    IF keyval(ccCancel) > 1 THEN EXIT DO

    IF st.need_update THEN
      st.need_update = NO
      edit_purchase_options_rebuild_menu menu, st, purchase_root
    END IF '--done updating menu

    usemenu st, menu

    t = menu.items[st.pt]->t
    sub_t = menu.items[st.pt]->sub_t
    node = menu.items[st.pt]->dataptr

    SELECT CASE t
      CASE editpurchSTRING:
        IF node THEN
          DIM curstr as string
          curstr = GetString(node)
          IF strgrabber(curstr, sub_t) THEN
            SetContent(node, curstr)
            st.need_update = YES
          END IF
        END IF
      CASE editpurchSTOREDEFAULT:
        IF edit_purchase_enumgrabber(node, default_stores()) THEN st.need_update = YES
      CASE editpurchSTOREOUYA:
        IF edit_purchase_enumgrabber(node, ouya_stores()) THEN st.need_update = YES
    END SELECT

    IF enter_space_click(st) THEN
      SELECT CASE t
        CASE editpurchEXIT: EXIT DO
        CASE editpurchADD:
          DIM newnode as NodePtr = AppendChildNode(products, "prod")
          st.need_update = YES
        CASE editpurchPROD:
          edit_purchase_details node
          st.need_update = YES
        CASE editpurchSTOREDEFAULT:
          IF edit_purchase_enumbrowse("Store for In-App Purchases", node, default_stores(), "edit_purchase_options") THEN st.need_update = YES
        CASE editpurchSTOREOUYA:
          IF edit_purchase_enumbrowse("Store for In-App Purchases", node, ouya_stores(), "edit_purchase_options") THEN st.need_update = YES
        CASE editpurchKEYDER:
          DIM key_der_file as string
          key_der_file = browse(browseAny, "", "key.der", "edit_purchase_browse_ouya_key_der")
          IF key_der_file <> "" THEN
            IF isfile(key_der_file) THEN
              SetContent(node, string_from_file(key_der_file))
              st.need_update = YES
            ELSE
              pop_warning "Unable to read the file """ & decode_filename(key_der_file) & """"
            END IF
          END IF
      END SELECT
    END IF

    IF keyval(scDelete) > 1 THEN
      IF yesno("Delete the selected product?", NO, NO) THEN
        FreeNode node
        st.need_update = YES
      END IF
    END IF

    clearpage dpage

    draw_menu menu, st, dpage
    wrapprint "Don't enable purchases unless all the content in your game is original!", 0, pBottom, uilook(uiSelectedDisabled), dpage

    SWAP vpage, dpage
    setvispage vpage

    dowait
  LOOP

  write_general_reld()
END SUB

SUB edit_purchase_options_rebuild_menu(menu as MenuDef, st as MenuState, byval purchase_root as NodePtr)

  DIM store_node as NodePtr = GetOrCreateChild(purchase_root, "stores_by_platform")
  DIM ouya_node as NodePtr = GetOrCreateChild(purchase_root, "ouya")
  DIM products as NodePtr = GetOrCreateChild(purchase_root, "products")

  DIM node as NodePtr
  DIM cap as string

  InitLikeStandardMenu menu

  append_menu_item menu, "Previous menu..."
  menu.last->t = editpurchEXIT

  append_menu_item menu, "(This menu is experimental!)"
  menu.last->unselectable = YES
  menu.last->col = uilook(uiMouseHoverItem)

  append_menu_item menu, ""
  menu.last->unselectable = YES
  append_menu_item menu, " Enabled Stores"
  menu.last->unselectable = YES
  menu.last->col = uilook(eduiHeading)

  node = GetOrCreateChild(store_node, "default")
  append_menu_item menu, "Windows+Mac+Linux: " & blank_default(GetString(node), "disabled")
  menu.last->t = editpurchSTOREDEFAULT
  menu.last->dataptr = node

  node = GetOrCreateChild(store_node, "ouya")
  append_menu_item menu, "OUYA Console: " & blank_default(GetString(node), "disabled")
  menu.last->t = editpurchSTOREOUYA
  menu.last->dataptr = node

  append_menu_item menu, ""
  menu.last->unselectable = YES
  append_menu_item menu, " OUYA Developer ID"
  menu.last->unselectable = YES
  menu.last->col = uilook(eduiHeading)

  node = GetOrCreateChild(ouya_node, "developer_id")
  append_menu_item menu, "> " & GetString(node)
  menu.last->t = editpurchSTRING
  menu.last->sub_t = 36
  menu.last->dataptr = node

  node = GetOrCreateChild(ouya_node, "key.der")
  DIM key_der_size as integer = LEN(GetString(node))
  IF key_der_size THEN
    cap = "Replace OUYA key.der file (" & key_der_size & " bytes)"
  ELSE
    cap = "Import OUYA key.der file"
  END IF
  append_menu_item menu, cap
  menu.last->t = editpurchKEYDER
  menu.last->dataptr = node

  append_menu_item menu, ""
  menu.last->unselectable = YES
  append_menu_item menu, " Purchaseable Products"
  menu.last->unselectable = YES
  menu.last->col = uilook(eduiHeading)

  READNODE products
    WITHNODE products."prod" as prod
      DIM n as NodePtr
      n = GetOrCreateChild(prod, "displayname")
      append_menu_item menu, "Product: " & GetString(n)
      menu.last->t = editpurchPROD
      menu.last->dataptr = prod
    END WITHNODE
  END READNODE

  append_menu_item menu, "Add a new product"
  menu.last->t = editpurchADD

  init_menu_state st, menu
END SUB

SUB edit_purchase_details (byval prod as NodePtr)
  DIM menu as MenuDef
  DIM st as MenuState
  st.active = YES
  st.need_update = YES

  DIM t as integer
  DIM sub_t as integer
  DIM node as NodePtr

  DIM n as NodePtr
  DIM store as NodePtr
  DIM buy_action as NodePtr
  DIM cap as string
  DIM num as integer

  setkeys YES
  DO
    setwait 55
    setkeys YES
    IF keyval(scF1) > 1 THEN show_help "edit_purchase_details"
    IF keyval(ccCancel) > 1 THEN EXIT DO

    IF st.need_update THEN
      st.need_update = NO
      InitLikeStandardMenu menu

      append_menu_item menu, "Previous menu..."
      menu.last->t = editpurchEXIT

      n = GetOrCreateChild(prod, "displayname")
      append_menu_item menu, "Name: " & GetString(n)
      menu.last->t = editpurchSTRING
      menu.last->sub_t = 128
      menu.last->dataptr = n

      n = GetOrCreateChild(prod, "description")
      append_menu_item menu, "Description: " & GetString(n)
      menu.last->t = editpurchLONGSTRING
      menu.last->sub_t = 99999
      menu.last->dataptr = n

      append_menu_item menu, "Can be purchased: " & IIF(prod."repeatable".exists, "Repeatedly", "Only Once")
      menu.last->t = editpurchTOGGLEREPEATABLE
      menu.last->dataptr = prod

      n = GetOrCreateChild(prod, "already")
      IF GetString(n) = "" THEN SetContent(n, "Already purchased")
      append_menu_item menu, "Already caption: " & GetString(n)
      menu.last->t = editpurchLONGSTRING
      menu.last->sub_t = 99999
      menu.last->dataptr = n

      append_menu_item menu, ""
      menu.last->unselectable = YES
      append_menu_item menu, " OUYA Store Settings"
      menu.last->unselectable = YES
      menu.last->col = uilook(eduiHeading)

      store = GetOrCreateChild(prod, "ouya")
      n = GetOrCreateChild(store, "identifier")
      append_menu_item menu, "Identifier: " & GetString(n)
      menu.last->t = editpurchSTRING
      menu.last->sub_t = 128
      menu.last->dataptr = n

      append_menu_item menu, ""
      menu.last->unselectable = YES
      append_menu_item menu, " Paypal Settings"
      menu.last->unselectable = YES
      menu.last->col = uilook(eduiHeading)

      store = GetOrCreateChild(prod, "paypal")
      n = GetOrCreateChild(store, "button_id")
      append_menu_item menu, "Button ID: " & GetString(n)
      menu.last->t = editpurchSTRING
      menu.last->sub_t = 128
      menu.last->dataptr = n

      append_menu_item menu, ""
      menu.last->unselectable = YES
      append_menu_item menu, " Purchase Action"
      menu.last->unselectable = YES
      menu.last->col = uilook(eduiHeading)

      buy_action = GetOrCreateChild(prod, "buy_action")

      n = GetOrCreateChild(buy_action, "tag")
      num = GetInteger(n)
      cap = "Turn ON Tag "
      IF num > 1 THEN
        cap &= num & " (" & load_tag_name(num) & ")"
      ELSEIF num = 1 THEN
        cap &= num & " [Cannot be changed!]"
      ELSE
        cap &= "[None selected]"
      END IF
      append_menu_item menu, cap
      menu.last->t = editpurchTAGSET
      menu.last->dataptr = n

      n = GetOrCreateChild(buy_action, "global")
      num = GetInteger(n)
      cap = "+1 to Global Variable ID " & zero_default(num, "[None selected]")
      append_menu_item menu, cap
      menu.last->t = editpurchINT
      menu.last->sub_t = maxScriptGlobals
      menu.last->dataptr = n

      n = GetOrCreateChild(buy_action, "thanks")
      IF GetString(n) = "" THEN SetContent(n, "Thank you!")
      append_menu_item menu, "Thanks Message: " & GetString(n)
      menu.last->t = editpurchLONGSTRING
      menu.last->sub_t = 99999
      menu.last->dataptr = n

      init_menu_state st, menu
    END IF '--done updating menu

    usemenu st, menu

    t = menu.items[st.pt]->t
    sub_t = menu.items[st.pt]->sub_t
    node = menu.items[st.pt]->dataptr

    SELECT CASE t
      CASE editpurchSTRING, editpurchLONGSTRING:
        IF node THEN
          DIM curstr as string
          curstr = GetString(node)
          IF strgrabber(curstr, sub_t) THEN
            SetContent(node, curstr)
            st.need_update = YES
          END IF
        END IF
      CASE editpurchTAGSET:
        IF node THEN
          DIM curint as integer
          curint = GetInteger(node)
          IF intgrabber(curint, 0, 99999) THEN
            SetContent(node, curint)
            st.need_update = YES
          END IF
        END IF
      CASE editpurchINT:
        IF node THEN
          DIM curint as integer
          curint = GetInteger(node)
          IF intgrabber(curint, 0, sub_t) THEN
            SetContent(node, curint)
            st.need_update = YES
          END IF
        END IF
      CASE editpurchTOGGLEREPEATABLE:
        IF enter_space_click(st) ORELSE keyval(ccLeft) > 1 ORELSE keyval(ccRight) > 1 THEN
          ToggleChildNode node, "repeatable"
          st.need_update = YES
        END IF
    END SELECT

    IF enter_space_click(st) THEN
      SELECT CASE t
        CASE editpurchEXIT: EXIT DO
        CASE editpurchTAGSET:
          SetContent(node, tags_menu(GetInteger(node), YES, NO))
          st.need_update = YES
      END SELECT
    END IF

    IF keyval(scAnyEnter) > 1 THEN
      SELECT CASE t
        CASE editpurchLONGSTRING:
          SetContent(node, multiline_string_editor(GetString(node)))
          st.need_update = YES
      END SELECT
    END IF

    clearpage dpage

    draw_menu menu, st, dpage
    SWAP vpage, dpage
    setvispage vpage

    dowait
  LOOP
END SUB

TYPE SavegameOptionsEditor EXTENDS EditorKit
  DECLARE SUB define_items()
END TYPE

SUB edit_savegame_options
  'Set default
  IF gen(genSaveSlotCount) <= 0 THEN gen(genSaveSlotCount) = 4

  DIM editor as SavegameOptionsEditor
  editor.helpkey = "savegame_options"
  editor.run()
  write_general_reld
END SUB

SUB SavegameOptionsEditor.define_items()
  DIM save_options_node as NodePtr = GetOrCreateChild(get_general_reld(), "saved_games")

  defint "Number of save/load slots:", gen(genSaveSlotCount), 1, maxSaveSlotCount
  defitem "Name of saves directory:"
  edit_node_str save_options_node, "/savename", "", 30, Delete_If_Default
  valuestr = fixfilename(valuestr)
  IF LEN(valuestr) = 0 THEN set_caption "(default)"

  defitem "Save script/sprite layer slices:"
  edit_node_exists save_options_node, "/sprite_layer"
  defitem "Save strings:"
  edit_node_exists save_options_node, "/strings"
END SUB


TYPE AchievementsEditor EXTENDS EditorKit
  DECLARE VIRTUAL SUB define_items()

  root as Node ptr
  cur_ach as Node ptr   'When in the subeditor for an achievement, its node, else NULL
  next_id as integer
END TYPE

SUB achievements_editor()
  ' Load and/or create doc
  DIM file_path as string = workingdir & SLASH & "achievements.reld"
  DIM doc as Docptr
  doc = LoadDocument(file_path, LoadOptions.optIgnoreMissing)
  IF doc = NULL THEN
    doc = CreateDocument()
  END IF

  DIM root as Node ptr = DocumentRoot(doc)
  IF root = NULL THEN
    root = CreateNode(doc, "achievements")
    SetRootNode doc, root
  END IF

  DIM editor as AchievementsEditor
  editor.root = root
  editor.next_id = root."next_id".integer.default(1)
  editor.run()

  SetChildNode root, "next_id", editor.next_id
  SerializeBin file_path, doc
  FreeDocument doc
END SUB

SUB AchievementsEditor.define_items()
  IF submenu = "" THEN  '---- Toplevel menu
    cur_ach = NULL
    helpkey = "achievements_list"

    defitem "Achievements are:"
    edit_node_exists root, "/permanent"
    captions_yesno "Global and permanent", "Per saved-game"

    section "Achievements"
    DIM ach as Node ptr = FirstChild(root, "achievement")
    WHILE ach
      DIM nextach as Node ptr = NextSibling(ach, "achievement")
      'IF defitem_act(GetString(ach) & " " & blank_default(ach."name".string, "[unnamed]")) THEN
      IF defitem_act(GetString(ach) & " " & ach."name".string) THEN
        cur_ach = ach
        enter_submenu "detail"
      END IF
      deletable_node ach, "achievement"
      set_tooltip "Enter to edit, Delete to remove"
      ach = nextach
    WEND

    IF defitem_act("Add achievement...") THEN
      cur_ach = AppendChildNode(root, "achievement", next_id)
      next_id += 1
      enter_submenu "detail"
    END IF

  ELSEIF submenu = "detail" then  '---- Editing an achievement
    helpkey = "achievement"

    defitem "Name:"
    edit_node_str cur_ach, "/name", , 80  'Not sure what limit to set

    defitem "Steam ID:"
    edit_node_str cur_ach, "/steam_id"

    defitem "Award condition:"
    val_node_str cur_ach, "/type"
    STATIC options(...) as StringEnumOption = {(@"flag", @"All requirements met"), (@"count", @"Count requirements")}
    edit_str_enum valuestr, options()  'Default to "flag"

    IF valuestr = "count" THEN
      defitem "Required amount:"
      edit_node_int cur_ach, "/max_value", 1, 1, INT_MAX  'Default 1
      DIM max_value as integer = value

      defitem "Progress display interval:"
      edit_node_int cur_ach, "/progress_interval", 0, 0, max_value  'Default 0
      IF value = 0 THEN set_caption "0 (Never)"
    END IF

    section "Tags"
    defitem "Tags are latching:"
    edit_node_exists cur_ach, "/latching"

    DIM taglist as NodePtr = GetOrCreateChild(cur_ach, "tags")
    DIM tagnode as NodePtr = FirstChild(taglist, "tag")
    WHILE tagnode
      defitem ""
      edit_as_check_tag val_node_int(tagnode), , , NO  'allowneg=NO
      tagnode = NextSibling(tagnode, "tag")  'Before it can be deleted
      deletable_node  'Don't show a prompt
      set_tooltip "Delete to remove"
    WEND
    IF defitem_act("Add tag...") THEN
      AppendChildNode taglist, "tag", 0
    END IF
  END IF
END SUB


SUB generate_battlesystem_menu(menu() as string, enabled() as bool, greyout() as bool)
  flusharray enabled(), UBOUND(enabled), YES
  flusharray greyout(), UBOUND(greyout), NO

  menu(0) = "Previous Menu"

  menu(1) = " Mechanics"
  enabled(1) = NO
  greyout(1) = YES

  menu(2) = "Battle Mode: "
  IF gen(genBattleMode) = 0 THEN
    menu(2) &= "Active-time"
  ELSE
    menu(2) &= "Turn-based"
  END IF
  menu(3) = "Battle preference bitsets..."
  menu(4) = "Number of Elements: " & gen(genNumElements)
  menu(5) = "Hero Elemental Resistance Calculation..."
  menu(6) = "Mark non-elemental elementals..."

  enabled(7) = NO
  menu(8) = " Stats and Experience"
  enabled(8) = NO
  greyout(8) = YES
  menu(9) = "Stat Caps..."
  menu(10) = "View Experience Chart..."
  menu(11) = "Experience given to heroes..."
  enabled(11) = NO
  menu(12) = " ...swapped-out and unlocked: " & gen(genUnlockedReserveXP) & "%"
  menu(13) = " ...swapped-out and locked: " & gen(genLockedReserveXP) & "%"
  '--Disabled because it is not ready yet
  'menu() = "Stat Growth Options..."

  menu(14) = "Hero Weak state below: " & gen(genHeroWeakHP) & "% " & statnames(statHP)
  menu(15) = "Enemy Weak state below: " & gen(genEnemyWeakHP) & "% " & statnames(statHP)

  enabled(16) = NO
  menu(17) = " Display"
  enabled(17) = NO
  greyout(17) = YES

  menu(18) = "Poison Indicator: " & gen(genPoisonChar) & " " & CHR(gen(genPoisonChar))
  menu(19) = "Stun Indicator: " & gen(genStunChar) & " " & CHR(gen(genStunChar))
  menu(20) = "Mute Indicator: " & gen(genMuteChar) & " " & CHR(gen(genMuteChar))
  menu(21) = "Regen Indicator: " & gen(genRegenChar) & " " & CHR(gen(genRegenChar))
  menu(22) = "Default Enemy Dissolve: " & dissolve_type_caption(gen(genEnemyDissolve))
  menu(23) = "Damage Display Time: " & gen(genDamageDisplayTicks) & " ticks (" & seconds_estimate(gen(genDamageDisplayTicks)) & " sec)"
  menu(24) = "Damage Display Rises: " & gen(genDamageDisplayRise) & " pixels"
  menu(25) = "Rewards Display: " & IIF(gen(genSkipBattleRewardsTicks) = 0, "Wait for keypress", gen(genSkipBattleRewardsTicks) & " ticks (" & seconds_estimate(gen(genSkipBattleRewardsTicks)) & " secs)")

  enabled(26) = NO
  menu(27) = " Other Defaults"
  enabled(27) = NO
  greyout(27) = YES

  menu(28) = "Attacks Provoke Counterattacks: " & counter_provoke_captions(gen(genDefCounterProvoke))
  menu(29) = "Battle Menu: " & IIF(gen(genDefaultBattleMenu) > 0, "Menu " & (gen(genDefaultBattleMenu) - 1) & " (" & getmenuname(gen(genDefaultBattleMenu) - 1) & ") as template" , "Default")
END SUB

SUB battleoptionsmenu ()
  CONST maxMenu = 29
  DIM menu(maxMenu) as string
  DIM menu_display(maxMenu) as string
  DIM min(maxMenu) as integer
  DIM max(maxMenu) as integer
  DIM index(maxMenu) as integer
  DIM enabled(maxMenu) as bool
  DIM greyout(maxMenu) as bool
  DIM selectst as SelectTypeState
  DIM state as MenuState
  WITH state
    .autosize = YES
    .last = maxMenu
    .need_update = YES
  END WITH
  DIM menuopts as MenuOptions
  menuopts.disabled_col = uilook(eduiHeading)  'For section headings

  'I think these things are here (and not upgrade) because we don't want to  force them on games
  IF gen(genPoisonChar) <= 0 THEN gen(genPoisonChar) = 161
  IF gen(genStunChar) <= 0 THEN gen(genStunChar) = 159
  IF gen(genMuteChar) <= 0 THEN gen(genMuteChar) = 163
  'Regen icon is newer, so let's not default it unexpectedly
  IF gen(genRegenChar) <= 0 THEN gen(genRegenChar) = 32

  IF gen(genBattleMode) < 0 ORELSE gen(genBattleMode) > 1 THEN
    visible_debug "WARNING: invalid gen(genBattleMode) " & gen(genBattleMode) & " resorting to active mode"
    gen(genBattleMode) = 0
  END IF

  DIM preview as MenuDef
  DIM viewport_page as integer = gameres_page()
  DIM prst as MenuState

  index(2) = genBattleMode
  min(2) = 0
  max(2) = 1
  index(4) = genNumElements
  min(4) = 1
  max(4) = 64
  index(12) = genUnlockedReserveXP
  max(12) = 1000
  index(13) = genLockedReserveXP
  max(13) = 1000
  min(14) = 1
  max(14) = 100
  index(14) = genHeroWeakHP
  min(15) = 1
  max(15) = 100
  index(15) = genEnemyWeakHP
  index(18) = genPoisonChar
  index(19) = genStunChar
  index(20) = genMuteChar
  index(21) = genRegenChar
  FOR i as integer = 18 TO 21
    min(i) = 32
    max(i) = 255
  NEXT
  index(22) = genEnemyDissolve
  max(22) = dissolveTypeMax
  index(23) = genDamageDisplayTicks
  max(23) = 1000
  index(24) = genDamageDisplayRise
  max(24) = 1000
  min(24) = -1000
  index(25) = genSkipBattleRewardsTicks
  max(25) = 100000
  min(25) = 0
  index(28) = genDefCounterProvoke
  max(28) = provokeLAST
  min(28) = 1  ' Can't select 'Default'
  index(29) = genDefaultBattleMenu
  max(29) = gen(genMaxMenu)
  min(29) = 0

  generate_battlesystem_menu menu(), enabled(), greyout()

  setkeys YES
  DO
    setwait 55
    setkeys YES

    IF keyval(ccCancel) > 1 THEN EXIT DO
    IF keyval(scF1) > 1 THEN show_help "battle_system_options"
    usemenu state, enabled()
    IF enter_space_click(state) THEN
      IF state.pt = 0 THEN EXIT DO
      IF state.pt = 3 THEN edit_battle_bitsets
      IF state.pt = 5 THEN equipmergemenu
      IF state.pt = 6 THEN mark_non_elemental_elementals
      IF state.pt = 9 THEN statcapsmenu
      IF state.pt = 10 THEN experience_chart
      IF min(state.pt) = 32 AND max(state.pt) = 255 THEN  'Character field
        DIM d as string = charpicker
        IF d <> "" THEN
          gen(index(state.pt)) = ASC(d)
          state.need_update = YES
        END IF
      END IF
    END IF
    IF index(state.pt) THEN
     IF state.pt = 29 THEN
      IF xintgrabber(gen(index(state.pt)), min(state.pt), max(state.pt)) THEN state.need_update = YES
     ELSE
      IF intgrabber(gen(index(state.pt)), min(state.pt), max(state.pt)) THEN state.need_update = YES
     END IF
    END IF

    IF state.need_update THEN
      generate_battlesystem_menu menu(), enabled(), greyout()
      init_battle_menu preview, gen(genDefaultBattleMenu) - 1
      append_menu_item preview, "Fight"
      append_menu_item preview, "Spells"
      append_menu_item preview, "Parry"
      append_menu_item preview, "Item"
      init_menu_state prst, preview
      state.need_update = NO
    END IF

    IF select_by_typing(selectst, NO) THEN
      select_on_word_boundary menu(), selectst, state
    END IF

    clearpage vpage
    IF state.pt = 29 THEN
     preview_menu preview, prst, viewport_page, vpage
    END IF
    highlight_menu_typing_selection menu(), menu_display(), selectst, state
    standardmenu menu_display(), state, greyout(), , , vpage, menuopts
    setvispage vpage
    dowait
  LOOP
END SUB

SUB statcapsmenu
  CONST maxMenu = 15
  DIM m(maxMenu) as string
  DIM menu_display(maxMenu) as string
  DIM max(maxMenu) as integer
  DIM index(maxMenu) as integer
  DIM selectst as SelectTypeState
  DIM state as MenuState
  state.last = maxMenu
  state.size = 24
  state.need_update = YES
  DIM i as integer

  index(1) = genDamageCap
  FOR i as integer = 2 TO 2 + statLast  '2 to 13
    index(i) = genStatCap + (i - 2)
  NEXT
  index(14) = genLevelCap
  index(15) = genMaxLevel

  max(1) = 32767  'Damage
  'Stat caps
  '(note: you can use setherostat to set stats to anything, even above 32767)
  FOR i as integer = 2 TO 2 + statLast
    max(i) = 32767
  NEXT
  max(14) = gen(genMaxLevel)  'Level cap is capped to Max Level
  max(15) = 99  'Max Level is capped to 99 ... FIXME: this could go higher!
  DO
    setwait 55
    setkeys YES

    IF keyval(ccCancel) > 1 OR (state.pt = 0 AND enter_space_click(state)) THEN EXIT DO
    IF keyval(scF1) > 1 THEN show_help "stat_caps_menu"
    usemenu state
    IF state.pt > 0 THEN
      IF intgrabber(gen(index(state.pt)), 0, max(state.pt)) THEN state.need_update = YES
    END IF
    IF state.need_update THEN
      state.need_update = NO
      m(0) = "Previous Menu"
      m(1) = "Damage Cap: "
      IF gen(genDamageCap) = 0 THEN m(1) += "None" ELSE m(1) &= gen(genDamageCap)
      FOR i as integer = 0 TO statLast
        m(2 + i) = statnames(i) + " Cap: " _
            + IIF(gen(genStatCap + i), STR(gen(genStatCap + i)), "None")
      NEXT
      IF gen(genLevelCap) > gen(genMaxLevel) THEN gen(genLevelCap) = gen(genMaxLevel)
      max(14) = gen(genMaxLevel)
      m(14) = "Level Cap: " & gen(genLevelCap)
      m(15) = "Maximum Level: " & gen(genMaxLevel)
    END IF

    IF select_by_typing(selectst, NO) THEN
      select_on_word_boundary m(), selectst, state
    END IF

    clearpage vpage
    highlight_menu_typing_selection m(), menu_display(), selectst, state
    standardmenu menu_display(), state, , , vpage
    setvispage vpage
    dowait
  LOOP
END SUB

SUB mark_non_elemental_elementals ()
  DIM g as NodePtr = get_general_reld()
  DIM elementals as NodePtr = GetOrCreateChild(g, "elementals")
  DIM non_elemental as NodePtr
  DIM eid as integer
  DIM bits(3) as integer

  DIM names() as string
  getelementnames names()
  FOR i as integer = 0 to UBOUND(names)
    names(i) = "Treat " & names(i) & " attacks as non-elemental"
  NEXT i

  READNODE elementals
    WITHNODE elementals."element" as e
      eid = GetInteger(e)
      IF eid >= 0 ANDALSO eid < maxElements THEN
        non_elemental = GetOrCreateChild(e, "non_elemental")
        setbit bits(), 0, eid, GetInteger(non_elemental)
      END IF
    END WITHNODE
  END READNODE

  editbitset bits(), 0, names(), "non_elemental_elementals"

  DIM saved(maxElements - 1) as bool

  READNODE elementals
    WITHNODE elementals."element" as e
      eid = GetInteger(e)
      IF eid >= 0 ANDALSO eid < maxElements THEN
        non_elemental = GetOrCreateChild(e, "non_elemental")
        SetContentBool non_elemental, xreadbit(bits(), eid)
        saved(eid) = YES
      END IF
    END WITHNODE
  END READNODE

  DIM e as NodePtr
  FOR i as integer = 0 TO maxElements - 1
    IF NOT saved(i) THEN
      e = AppendChildNode(elementals, "element", i)
      SetChildNodeBool(e, "non_elemental", xreadbit(bits(), i))
    END IF
  NEXT i

  write_general_reld()
END SUB

FUNCTION merge_elementals_example(byval exampleno as integer, example() as single, byval formula as integer) as string
  DIM ret as string = "Ex" & exampleno
  FOR i as integer = 0 TO 3
    DIM temp as string
    IF i >= 1 AND i <= 2 AND formula = 2 THEN  'Show equipment as additive changes
      temp = format_percent(example(i) - 1.0, 3)
      IF LEFT(temp, 1) <> "-" THEN temp = "+" + temp
    ELSE
      temp = format_percent(example(i), 3)
    END IF
    ret += RIGHT("       " + temp, 9)
  NEXT
  RETURN ret
END FUNCTION

SUB generate_equipmerge_preview(byval formula as integer, menu() as string, greyed_out() as integer, ex9() as single)
  FOR i as integer = 1 TO 3
    greyed_out(i) = YES
  NEXT
  greyed_out(1 + gen(genEquipMergeFormula)) = NO
  FOR i as integer = 4 TO UBOUND(menu)
    menu(i) = ""
  NEXT

  DIM _NaN as single = 0.0f
  _NaN = 0.0f/_NaN

  DIM ex1(3) as single = {1, 1, 3, _NaN}
  DIM ex2(3) as single = {1, 2, 2, _NaN}
  DIM ex3(3) as single = {0, 0, 1, _NaN}
  DIM ex4(3) as single = {0.5, 1, 2, _NaN}
  DIM ex5(3) as single = {-1, 1.5, 2, _NaN}
  DIM ex6(3) as single = {-1, -1, -1, _NaN}
  DIM ex7(3) as single = {2, 0.5, 0.5, _NaN}
  DIM ex8(3) as single = {1, -1.2, -1.2, _NaN}
  ex9(3) = _NaN

  IF formula = -1 THEN
    menu(9) = "Select a formula to see examples"
  ELSE
    ex1(3) = equip_elemental_merge(ex1(), formula)
    ex2(3) = equip_elemental_merge(ex2(), formula)
    ex3(3) = equip_elemental_merge(ex3(), formula)
    ex4(3) = equip_elemental_merge(ex4(), formula)
    ex5(3) = equip_elemental_merge(ex5(), formula)
    ex6(3) = equip_elemental_merge(ex6(), formula)
    ex7(3) = equip_elemental_merge(ex7(), formula)
    ex8(3) = equip_elemental_merge(ex8(), formula)
    ex9(3) = equip_elemental_merge(ex9(), formula)

    menu(9) = "Examples:"
    menu(10) = "        Hero   Equip1   Equip2   Result"
    menu(11) = merge_elementals_example(1, ex1(), formula)
    menu(12) = merge_elementals_example(2, ex2(), formula)
    menu(13) = merge_elementals_example(3, ex3(), formula)
    menu(14) = merge_elementals_example(4, ex4(), formula)
    menu(15) = merge_elementals_example(5, ex5(), formula)
    menu(16) = merge_elementals_example(6, ex6(), formula)
    menu(17) = merge_elementals_example(7, ex7(), formula)
    menu(18) = merge_elementals_example(8, ex8(), formula)
    menu(19) = merge_elementals_example(9, ex9(), formula)

    IF formula = 2 THEN
      menu(21) = "(Equipment values are displayed"
      menu(22) = "differently when this is chosen)"
    END IF
  END IF
END SUB

SUB equipmergemenu
  DIM menu(22) as string
  DIM greyed_out(22) as integer
  DIM st as MenuState
  st.size = 24
  st.last = 3
  st.need_update = YES
  DIM tog as integer

  'Random example which changes on entering the menu
  DIM ex9(3) as single = {rando(), 3*rando()-1.5, 1+rando()}

  menu(0) = "Previous Menu"
  menu(1) = "Old awful formula (multiplication-like)"
  menu(2) = "Combine resistances by multiplication"
  menu(3) = "Combine resistances by addition"
  DO
    setwait 55
    setkeys
    tog XOR= 1
    IF keyval(ccCancel) > 1 THEN EXIT DO
    IF keyval(scF1) > 1 THEN show_help "equip_elemental_formula"
    IF enter_space_click(st) THEN
      IF st.pt = 0 THEN
        EXIT DO
      ELSE
        gen(genEquipMergeFormula) = st.pt - 1
        st.need_update = YES
      END IF
    END IF
    IF usemenu(st) THEN st.need_update = YES

    IF st.need_update THEN
      generate_equipmerge_preview st.pt - 1, menu(), greyed_out(), ex9()
      st.need_update = NO
    END IF

    clearpage vpage
    FOR i as integer = 0 TO UBOUND(menu)
      IF greyed_out(i) THEN
        textcolor uilook(uiDisabledItem), 0
        IF st.pt = i THEN textcolor uilook(uiSelectedDisabled + tog), 0
      ELSE
        textcolor uilook(uiMenuItem), 0
        IF st.pt = i THEN textcolor uilook(uiSelectedItem + tog), 0
      END IF
      printstr menu(i), pMenuX, pMenuY + i * 8, vpage, YES
    NEXT i
    setvispage vpage
    dowait
  LOOP
END SUB

SUB startingdatamenu
  CONST maxMenu = 6
  DIM m(maxMenu) as string
  DIM menu_display(maxMenu) as string
  DIM max(maxMenu) as integer
  DIM index(maxMenu) as integer
  DIM selectst as SelectTypeState
  DIM state as MenuState
  state.last = maxMenu
  state.size = 24
  state.need_update = YES
  DIM as integer lastmap = -1

  index(1) = genStartX
  index(2) = genStartY
  index(3) = genStartMap
  max(3) = gen(genMaxMap)
  index(4) = genStartHero
  max(4) = gen(genMaxHero)
  index(5) = genStartMoney
  max(5) = 32767
  index(6) = genStartTextbox
  max(6) = gen(genMaxTextbox)
  DO
    setwait 55
    setkeys YES

    IF keyval(ccCancel) > 1 OR (state.pt = 0 AND enter_space_click(state)) THEN EXIT DO
    IF keyval(scF1) > 1 THEN show_help "new_game_data"
    usemenu state
    IF state.pt = 6 THEN
      state.need_update OR= textboxgrabber(gen(index(state.pt)), state)
    ELSEIF state.pt > 0 THEN
      IF intgrabber(gen(index(state.pt)), 0, max(state.pt)) THEN state.need_update = YES
    END IF
    IF state.pt = 4 THEN
      IF enter_space_click(state) THEN
        gen(genStartHero) = hero_picker(gen(genStartHero))
        state.need_update = YES
      END IF
    END IF
    IF state.need_update THEN
      state.need_update = NO
      max(3) = gen(genMaxMap)
      max(4) = gen(genMaxHero)
      max(6) = gen(genMaxTextbox)
      IF lastmap <> gen(genStartMap) THEN
        DIM mapinfo as TilemapInfo
        GetTilemapInfo maplumpname(gen(genStartMap), "t"), mapinfo
        max(1) = mapinfo.wide
        max(2) = mapinfo.high
        gen(genStartX) = small(gen(genStartX), max(1))
        gen(genStartY) = small(gen(genStartY), max(2))
        lastmap = gen(genStartMap)
      END IF

      m(0) = "Previous Menu"
      m(1) = "Starting X: " & gen(genStartX)
      m(2) = "Starting Y: " & gen(genStartY)
      m(3) = "Starting Map: " & gen(genStartMap) & " " & getmapname(gen(genStartMap))
      m(4) = "Starting Hero: " & gen(genStartHero) & " " & getheroname(gen(genStartHero))
      m(5) = "Starting Money: " & gen(genStartMoney)
      IF gen(genStartTextbox) = 0 THEN
        m(6) = "Starting Textbox: None"
      ELSE
        m(6) = "Starting Textbox: " & gen(genStartTextbox) & " " & textbox_preview_line(gen(genStartTextbox), vpages(vpage)->w - 60)
      END IF
    END IF

    IF select_by_typing(selectst, NO) THEN
      select_on_word_boundary m(), selectst, state
    END IF

    clearpage vpage
    IF state.pt = 6 THEN edgeprint THINGGRABBER_TOOLTIP, pInfoX, pInfoY, uilook(uiDisabledItem), vpage
    highlight_menu_typing_selection m(), menu_display(), selectst, state
    standardmenu menu_display(), state, , , vpage
    setvispage vpage
    dowait
  LOOP
END SUB

DIM SHARED shown_framerate_warning as bool

TYPE GeneralSettingsMenu EXTENDS EditorKit
  longname as string
  aboutline as string
  titletext as string
  last_update as double  'For game stats

  DECLARE CONSTRUCTOR()
  DECLARE SUB save()
  DECLARE VIRTUAL SUB define_items()
END TYPE

SUB general_data_editor()
  DIM genmenu as GeneralSettingsMenu
  genmenu.run()
  genmenu.save()

  '--Also use the in-game setting for previewing stuff in Custom
  set_music_volume 0.01 * gen(genMusicVolume)
  set_global_sfx_volume 0.01 * gen(genSFXVolume)
END SUB

CONSTRUCTOR GeneralSettingsMenu()
  'make sure genMaxInventory is a multiple of 3 (other values possible in older versions and Fufluns nightlies)
  IF gen(genMaxInventory) THEN gen(genMaxInventory) = last_inv_slot()

  aboutline = load_aboutline()
  longname = load_gamename()
  titletext = load_titletext()

  last_update = TIMER

  prev_menu_text = "Return to Main Menu"
  helpkey = "general_game_data"
END CONSTRUCTOR

SUB GeneralSettingsMenu.save()
  save_gamename longname
  save_aboutline aboutline
  save_titletext titletext
END SUB

SUB GeneralSettingsMenu.define_items()
  section "Game Title & Info"
  defstr "Long name:", longname
  defstr "About line:", aboutline
  IF defitem_act("Title Screen...") THEN
    DIM backdropb as BackdropSpriteBrowser
    gen(genTitle) = backdropb.browse(gen(genTitle))
  END IF
  defstr "Title screen text:", titletext, (gen(genResolutionX) - 16) \ 8

  '-------------------------
  section "Major Settings"
  IF defitem_act("New Games...")               THEN startingdatamenu
  IF defitem_act("Saved Games...")             THEN edit_savegame_options
  IF defitem_act("Battle System...")           THEN battleoptionsmenu
  IF defitem_act("Preference Bitsets...")      THEN edit_general_bitsets
  IF defitem_act("Backwards-Compatibility...") THEN edit_backcompat_bitsets

  '-------------------------
  section "Controls"
  IF defitem_act("Mouse Options...") THEN edit_mouse_options
  IF defitem_act("Platform-Specific Controls...") THEN edit_platform_controls

  '-------------------------
  section "Scripts"
  IF defitem_act("Global Script Triggers...") THEN general_scripts_menu
  IF defitem_act("Error Display...") THEN script_error_mode_menu

  '-------------------------
  section "Graphics"
  IF defitem_act("Master Palettes...")     THEN master_palette_menu
  IF defitem_act("Window-Size Options...") THEN resolution_menu

  defitem "Framerate:"
  IF edit_int(gen(genMillisecPerFrame), 16, 200) THEN
    IF shown_framerate_warning = NO THEN
      show_help "framerate_warning"
      shown_framerate_warning = YES
    END IF
  ELSEIF refresh THEN
    DIM fps as string
    '16ms and 33ms are special-cased to be exactly 60/30fps
    'In this menu you can't select a value less than 16ms, because it interferes
    'badly with gfx_present, but you can in-game with set_speedcontrol.
    IF gen(genMillisecPerFrame) = 8 THEN
      fps = "120"
    ELSEIF gen(genMillisecPerFrame) = 11 THEN
      fps = "90"
    ELSEIF gen(genMillisecPerFrame) = 16 THEN
      fps = "60"
    ELSEIF gen(genMillisecPerFrame) = 33 THEN
      fps = "30"
    ELSE
      fps = FORMAT(small(60., 1000 / gen(genMillisecPerFrame)), ".#")
    END IF
    set_caption fps & " frames/sec (" & gen(genMillisecPerFrame) & "ms/frame)"
  END IF

  defint "Color depth:", gen(gen32bitMode), 0, 1
  captions_yesno "24-bit (true color)", "8-bit (limit to master palette)"

  defint "Walk animation rate:", gen(genTicksPerWalkFrame), 0, 20
  IF refresh THEN
    DIM tmp as string
    IF value = 0 THEN
      value = wtog_ticks()
      tmp &= "default (" & value & " ticks/frame, "
    ELSE
      tmp &= value & " ticks/frame ("
    END IF
    set_caption tmp & (value * gen(genMillisecPerFrame)) & "ms/frame)"
  END IF

  defint "Minimap style:", gen(genMinimapAlgorithm), 0, minimapLAST
  SELECT CASE gen(genMinimapAlgorithm)
    CASE minimapScaled:      set_caption "Smoothly scaled down (true-color)"
    CASE minimapScatter:     set_caption "Pick random color"
    CASE minimapMajority:    set_caption "Pick most common color"
    CASE minimapScaledQuant: set_caption "Smoothly scaled down (256 color)"
  END SELECT

  defint "Camera following a hero/NPC centers on:", gen(genCameraOnWalkaboutFocus), 0, 2
  captions_list("tile", "sprite", "sprite minus Z")

  '-------------------------
  section "Audio"
  IF defitem_act("Global Music and Sound Effects...") then generalmusicsfxmenu
  defint "Initial music volume:", gen(genMusicVolume), 0, 100
  set_caption value & "%"
  defint "Initial sound effects volume:", gen(genSFXVolume), 0, 100
  set_caption value & "%"

  '-------------------------
  section "Inventory"
  defitem "Inventory size:"
  IF refresh THEN
    'Rows should be gen(genMaxInventory) \ 3 + 1 except when defaulting
    DIM rows as integer = (last_inv_slot() \ 3) + 1
    IF gen(genMaxInventory) = 0 THEN
      set_caption "Default (" & rows & " rows)"
    ELSE
      set_caption rows & " rows, " & gen(genMaxInventory) + 1 & " slots"
    END IF
  END IF
  DIM as integer temp = (gen(genMaxInventory) + 1) \ 3
  IF edit_int(temp, 0, (inventoryMax + 1) \ 3) THEN
    gen(genMaxInventory) = IIF(temp, temp * 3 - 1, 0)
  END IF

  defint "Inventory autosort:", gen(genAutosortScheme), 0, 4
  captions_list("by item type/uses", _
                "by whether usable", _
                "alphabetically", _
                "by item ID number", _
                "no reordering")
  defint "Default maximum item stack size:", gen(genItemStackSize), 1, 99
  defint "Display '" & CHR(1) & "1' in inventory:", gen(genInventSlotx1Display), 0, 2  'CHR(1) is the x symbol
  captions_list("always", "never", "only if stackable")

  '-------------------------
  section "Misc"
  IF defitem_act("Achievements... (Steam only)") THEN achievements_editor
  IF defitem_act("In-App Purchases... (experimental)") THEN edit_purchase_options
  IF defitem_act("Password For Editing...") THEN inputpasw
  defitem "Top-level thingbrowsers:"
  edit_config_bool "thingbrowser.enable_top_level", YES
  captions_yesno "YES (recommended)", "NO (for slow computers)"

  '-------------------------
  section "Stats"
  defunselectable "Time spent editing..."
  defunselectable " this session:"
  set_caption format_duration(active_seconds, 0)
  defunselectable " in total:"
  DIM total_text as string
  '"created" was added at the same time as "edit_time", so if it's missing then the total is inaccurate.
  IF GetChildNodeExists(get_general_reld, "created") = NO THEN total_text &= "at least "
  'Round edit_time to integer so it ticks in sync with 'This session'
  DIM total_edit_time as double = INT(GetChildNodeFloat(get_general_reld, "edit_time")) + active_seconds
  set_caption total_text & format_duration(total_edit_time, 0)

  DIM created as double = GetChildNodeFloat(get_general_reld, "created", 0.)
  IF created <> 0. THEN
    defunselectable "Game created " & FORMAT(created, "yyyy mmm dd hh:mm")
  END IF

  'Force the menu to refresh at least once a second to update the editing time
  IF phase = Phases.processing ANDALSO TIMER - last_update > 0.95 THEN
    state.need_update = YES
    last_update += 1.0
  END IF
END SUB