'OHRRPGCE CUSTOM - Hero Editor
'(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 "string.bi"
#include "config.bi"
#include "const.bi"
#include "udts.bi"
#include "custom.bi"
#include "allmodex.bi"
#include "common.bi"
#include "loading.bi"
#include "customsubs.bi"
#include "slices.bi"
#include "sliceedit.bi"
#include "thingbrowser.bi"
#include "cglobals.bi"
#include "specialslices.bi"

'--Local SUBs
DECLARE SUB enforce_hero_data_limits(her as HeroDef)
DECLARE SUB update_hero_appearance_menu(byref st as HeroEditState, menu() as string, her as HeroDef)
DECLARE SUB update_hero_preview_pics(byref st as HeroEditState, her as HeroDef)
DECLARE SUB animate_hero_preview(byref st as HeroEditState)
DECLARE SUB clear_hero_preview_pics(byref st as HeroEditState)
DECLARE SUB draw_hero_preview(st as HeroEditState, her as HeroDef)

DECLARE FUNCTION hero_editor_add_new (st as HeroEditState, her as HeroDef, byref hero_id as integer) as bool
DECLARE SUB hero_editor_load_hero (st as HeroEditState, her as HeroDef, byval hero_id as integer)
DECLARE SUB hero_editor_stats_menu (her as HeroDef)
DECLARE SUB hero_editor_spell_lists_toplevel (her as HeroDef)
DECLARE SUB hero_editor_edit_spell_list (her as HeroDef, byval listnum as integer)
DECLARE SUB hero_editor_spell_list_names (her as HeroDef)
DECLARE SUB hero_editor_tags (byval hero_id as integer, byref hero as HeroDef)
DECLARE SUB hero_editor_appearance (byref st as HeroEditState, byref her as HeroDef)
DECLARE SUB hero_editor_equipment_list (byval hero_id as integer, byref her as HeroDef)
DECLARE SUB hero_editor_equipbits (byval hero_id as integer, byval equip_type as integer, heroname as string)
DECLARE SUB hero_editor_elementals(byref her as HeroDef)
DECLARE SUB hero_editor_edit_spell_list_slot (slot as SpellList)
DECLARE SUB hero_editor_battle_menu (hero_id as integer, her as HeroDef)
DECLARE SUB hero_editor_counterattacks (hero as HeroDef)
DECLARE SUB hero_editor_counterattacks_update_menu(hero as HeroDef, menu as MenuDef)

DECLARE SUB edit_battle_menu_item (hero as HeroDef, byval edit_node as NodePtr)
DECLARE SUB browse_battle_menu_kinds (byval menu_node as NodePtr)
DECLARE SUB change_battle_menu_kind (byval menu_node as NodePtr, new_kind as string, init_integer as bool=NO)
DECLARE SUB hero_editor_stat_visibility (hero as HeroDef)
DECLARE FUNCTION tag_range_check_string(byval check as TagRangeCheck) as string
DECLARE SUB edit_tag_range_check (byref check as TagRangeCheck)

SUB hero_editor_main ()
 IF read_config_bool("thingbrowser.enable_top_level", YES) THEN
  DIM b as HeroBrowser
  b.browse(-1, , @hero_editor)
 ELSE
  hero_editor 0
 END IF
END SUB

FUNCTION hero_picker (recindex as integer = -1) as integer
 DIM b as HeroBrowser
 RETURN b.browse(recindex, , @hero_editor, NO)
END FUNCTION

'0 is None, > 0 is ID + 1
FUNCTION hero_picker_or_none (recindex as integer = -1) as integer
 DIM b as HeroBrowser
 RETURN b.browse(recindex - 1, YES , @hero_editor, NO) + 1
END FUNCTION

SUB hero_editor_load_hero (st as HeroEditState, her as HeroDef, byval hero_id as integer)
 loadherodata her, hero_id
 enforce_hero_data_limits her
 update_hero_preview_pics st, her
END SUB

FUNCTION hero_editor (hero_id as integer) as integer
 'return value is the hero_id for thingbrowser
 DIM her as HeroDef
 DIM st as HeroEditState
 WITH st
  .preview_walk_direction = 1
  .preview_steps = 0
  .preview_walk_pos.x = 0
  .preview_walk_pos.y = 0
  .previewframe = -1
 END WITH

 'Add new hero if requested with hero_id = -1
 IF hero_id > maxMaxHero THEN
  visible_debug "Can't edit hero id > " & maxMaxHero
  RETURN -1
 END IF
 IF hero_id > gen(genMaxHero) THEN
  IF hero_editor_add_new(st, her, hero_id) THEN
   saveherodata her, hero_id
  ELSE
   RETURN -1
  END IF
 END IF

 DIM menu(11) as string
 menu(0) = "Return to Main Menu"
 menu(1) = CHR(27) & "Hero " & hero_id & CHR(26)
 menu(2) = "Name:"
 menu(3) = "Appearance and Misc..."
 menu(4) = "Edit Stats..."
 menu(5) = "Edit Spell Lists..."
 menu(6) = "Edit Battle Menu..."
 menu(7) = "Bitsets..."
 menu(8) = "Elemental Resistances..."
 menu(9) = "Hero Tags..."
 menu(10) = "Equipment..."
 menu(11) =  "Counterattacks..."

 DIM mstate as MenuState
 WITH mstate
  .pt = 1
  .size = 22
  .last = UBOUND(menu)
 END WITH

 DIM elementnames() as string
 getelementnames elementnames()

 DIM hbit(47) as string
 'Bits 0-23 are obsolete elemental strong/weak/absorb bits. They aren't saved in
 'heroes.reld, but might be set if we loaded from .dt0 instead of heroes.reld
 '(happens first time we edit the hero only).
 hbit(24) = "Rename when added to party"
 hbit(25) = "Permit renaming on status screen"
 hbit(26) = "Do not show spell lists if empty"

 hero_editor_load_hero st, her, hero_id

 setkeys YES
 DO
  setwait gen(genMillisecPerFrame)
  setkeys YES
  animate_hero_preview st
  IF keyval(ccCancel) > 1 THEN EXIT DO
  IF keyval(scF1) > 1 THEN show_help "hero_editor"
  IF cropafter_keycombo(mstate.pt = 1) THEN
   'FIXME: this only clears the old .dt0 file, not the new heroes.reld file
   IF isfile(game + ".dt0") THEN cropafter hero_id, gen(genMaxHero), game + ".dt0", getbinsize(binDT0)
   load_special_tag_caches
  END IF
  usemenu mstate
  IF enter_space_click(mstate) THEN
   SELECT CASE mstate.pt
    CASE 0: EXIT DO
    CASE 3: hero_editor_appearance st, her
    CASE 4: hero_editor_stats_menu her
    CASE 5: hero_editor_spell_lists_toplevel her
    CASE 6: hero_editor_battle_menu hero_id, her
    CASE 7: editbitset her.bits(), 0, hbit()
    CASE 8: hero_editor_elementals her
    CASE 9: hero_editor_tags hero_id, her
    CASE 10: hero_editor_equipment_list hero_id, her
    CASE 11: hero_editor_counterattacks her
   END SELECT
   ' For the benefit of live-previewing
   saveherodata her, hero_id
  END IF

  IF mstate.pt = 1 THEN
   DIM remem_hero_id as integer = hero_id
   IF intgrabber(hero_id, 0, gen(genMaxHero), scLeftCaret, scRightCaret) THEN
    saveherodata her, remem_hero_id
    hero_editor_load_hero st, her, hero_id
   END IF
   IF keyval(ccLeft) > 1 AND hero_id > 0 THEN
    saveherodata her, hero_id
    hero_id -= 1
    hero_editor_load_hero st, her, hero_id
   END IF
   IF keyval(ccRight) > 1 AND hero_id < maxMaxHero THEN
    saveherodata her, hero_id
    hero_id += 1
    IF needaddset(hero_id, gen(genMaxHero), "hero") THEN
     gen(genMaxHero) -= 1  'Incremented by both needaddset and hero_editor_add_new
     IF hero_editor_add_new(st, her, hero_id) THEN
      saveherodata her, hero_id
     END IF
    END IF
    hero_editor_load_hero st, her, hero_id
   END IF
  END IF
  IF mstate.pt = 2 THEN
   strgrabber her.name, 16
  END IF

  menu(1) = CHR(27) & "Hero " & hero_id & CHR(26)
  menu(2) = "Name: " & her.name

  '--Draw screen
  clearpage dpage
  standardmenu menu(), mstate, , , dpage

  draw_hero_preview st, her
  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP
 saveherodata her, hero_id
 clear_hero_preview_pics st
 RETURN hero_id
END FUNCTION

'TODO: convert to generic_add_new
FUNCTION hero_editor_add_new (st as HeroEditState, her as HeroDef, byref recindex as integer) as bool
 'Returns YES if a new hero was added, or NO if the add was cancelled
  DIM menu(2) as string
  DIM herotocopy as integer = 0
  DIM state as MenuState
  state.last = UBOUND(menu)
  state.size = 24
  state.pt = 1

  state.need_update = YES
  setkeys
  DO
    setwait 55
    animate_hero_preview st
    setkeys
    IF keyval(ccCancel) > 1 THEN  'cancel
      recindex -= 1
      EXIT DO
    END IF
    IF keyval(scF1) > 1 THEN show_help "hero_new"
    usemenu state
    IF state.pt = 2 THEN
      IF intgrabber(herotocopy, 0, gen(genMaxHero)) THEN state.need_update = YES
    END IF
    IF state.need_update THEN
      state.need_update = NO
      loadherodata her, herotocopy
      update_hero_preview_pics st, her
      menu(0) = "Cancel"
      menu(1) = "New Blank Hero"
      menu(2) = "Copy of Hero " & herotocopy & " " & her.name
    END IF
    IF enter_space_click(state) THEN
      SELECT CASE state.pt
        CASE 0 ' cancel
          recindex -= 1
          RETURN NO
        CASE 1 ' blank
          gen(genMaxHero) += 1
          ClearHeroData her, gen(genMaxHero)
          RETURN YES
        CASE 2 ' copy
          gen(genMaxHero) += 1
          RETURN YES
      END SELECT
    END IF

    clearpage vpage
    standardmenu menu(), state, , , vpage
    IF state.pt = 2 THEN
      draw_hero_preview st, her
    END IF
    setvispage vpage
    dowait
  LOOP
END FUNCTION

SUB hero_editor_stat_visibility (hero as HeroDef)
 DIM tmpbits(0) as integer
 REDIM menu(statLast) as string
 getstatnames menu()
 FOR i as integer = 0 TO statLast
  menu(i) = "Hide " & menu(i)
 NEXT i
 
 DIM hero_node as NodePtr
 hero_node = hero.reld
 READNODE hero_node."stat_options" as opt
  WITHNODE opt."stat" as stat
   DIM i as integer = GetInteger(stat)
   IF stat."hide".exists THEN setbit tmpbits(), 0, i, YES
  END WITHNODE
 END READNODE

 editbitset tmpbits(), 0, menu(), "hero_stat_visibility", , , hero.name & "'s Status screen visible stats"
 
 DIM stat_options as NodePtr
 stat_options = hero_node."stat_options".ptr
 IF stat_options = 0 THEN stat_options = SetChildNode(hero_node, "stat_options")

 DIM stat as NodePtr
 FOR i as integer = 0 TO statLast
  stat = NodeByPath(stat_options, "/stat[" & i & "]")
  IF stat = 0 THEN
   'Node does not exist yet
   IF xreadbit(tmpbits(), i) THEN
    stat = AppendChildNode(stat_options, "stat", i)
    SetChildNode(stat, "hide")
   END IF
  ELSE
   'Node already exists
   IF xreadbit(tmpbits(), i) THEN
    SetChildNode(stat, "hide")
   ELSE
    IF stat."hide".exists THEN FreeNode stat."hide".ptr
   END IF
  END IF
 NEXT i
 
END SUB

SUB hero_editor_stats_menu (her as HeroDef)
 DIM i as integer
 DIM activemenu as integer = 0  '0 or 1 indicating left or rightmenu
 DIM twomenus as bool = (gen(genMaxLevel) > 0)  'whether to draw the second menu
 DIM as integer n0, nMax

 '.unselectable attribute only used from leftmenu
 DIM as BasicMenuItem vector leftmenu, rightmenu, middlemenu
 v_new leftmenu, 4 + statLast + 1
 v_new rightmenu, 4 + statLast + 1
 v_new middlemenu, 4 + statLast + 1
 leftmenu[0].text = "Previous Menu"
 leftmenu[1].text = "Stat Visibility..."
 leftmenu[2].unselectable = YES
 leftmenu[3].text = "LEVEL ZERO"
 leftmenu[3].unselectable = YES
 leftmenu[3].col = uilook(uiDescription)
 'Middlemenu is not editable, it just shows experience at Max level, if the max is not 99
 middlemenu[3].text = "LEVEL " & gen(genMaxLevel)
 middlemenu[3].col = uilook(uiDescription)
 ' FIXME: the rightmost column is always the level 99 stats rather than gen(genMaxLevel)
 rightmenu[3].text = "LEVEL 99"
 rightmenu[3].col = uilook(uiDescription)

 DIM rootsl as Slice Ptr
 rootsl = NewSliceOfType(slContainer)
 rootsl->Fill = YES

 DIM graph as GraphSlice ptr = NEW GraphSlice()
 graph->int_x_labels = YES
 graph->int_y_labels = YES
 graph->default_maxy = 20
 DIM graph_sl as Slice ptr
 graph_sl = NewClassSlice(rootsl, graph)
 'Position set below

 DIM mstate as MenuState
 WITH mstate
  .last = v_len(leftmenu) - 1
  .size = 22
  .need_update = YES
  .active = YES
 END WITH
 DIM inactive_state as MenuState
 WITH inactive_state
  .last = mstate.last
  .size = mstate.size
  .active = NO
 END WITH

 DIM min(statLast) as integer, max(statLast) as integer
 FOR i as integer = 0 TO UBOUND(min)
  min(i) = 0
  max(i) = 32767
 NEXT

 setkeys
 DO
  setwait 55
  setkeys
  IF keyval(ccCancel) > 1 THEN EXIT DO
  IF keyval(scF1) > 1 THEN show_help "hero_stats"
  IF keyval(scF6) > 1 THEN slice_editor rootsl, SL_COLLECT_EDITOR

  usemenu mstate, leftmenu
  DIM statnum as integer = mstate.pt - 4

  IF readmouse.clicks AND (mouseLeft OR mouseRight) ANDALSO readmouse.y < graph_sl->Y THEN
   IF readmouse.x > 160 AND twomenus THEN
    activemenu = 1
   ELSE
    activemenu = 0
   END IF
  END IF

  IF enter_space_click(mstate) THEN
   IF mstate.pt = 0 THEN EXIT DO
   IF mstate.pt = 1 THEN
    hero_editor_stat_visibility her
    mstate.need_update = YES
   END IF
  END IF

  IF statnum < 0 THEN
   activemenu = 0
  ELSE
   IF twomenus AND (keyval(ccLeft) > 1 OR keyval(ccRight) > 1) THEN activemenu XOR= 1
   DIM selected_value as integer ptr
   IF activemenu = 0 THEN
    selected_value = @her.Lev0.sta(statnum)
   ELSE
    selected_value = @her.LevMax.sta(statnum)
   END IF
   mstate.need_update OR= intgrabber(*selected_value, min(statnum), max(statnum), scLeftCaret, scRightCaret)
  END IF

  IF mstate.need_update THEN
   mstate.need_update = NO
   middlemenu[3].text = "LEVEL " & gen(genMaxLevel)
   FOR i as integer = 0 TO UBOUND(her.Lev0.sta)
    WITH leftmenu[4 + i]
     .text = statnames(i) & " " & her.Lev0.sta(i)
     .col = uiLook(uiMenuItem)
     IF should_hide_hero_stat(her, i) THEN .col = uiLook(uiDisabledItem)
    END WITH
    WITH rightmenu[4 + i]
     .text = statnames(i) & " " & her.LevMax.sta(i)
     .col = uiLook(uiMenuItem)
     IF should_hide_hero_stat(her, i) THEN .col = uiLook(uiDisabledItem)
    END WITH
    WITH middlemenu[4 + i]
     .text = statnames(i) & " " & atlevel(gen(genMaxLevel), her.Lev0.sta(i), her.LevMax.sta(i))
     .col = uiLook(uiSelectedDisabled)
    END WITH
   NEXT i
  END IF

  'Update graph
  graph_sl->Visible = (statnum >= 0)
  DIM show_title as bool = (vpages(dpage)->h > 250)  'Only when there's plenty of room
  graph_sl->Pos = XY(0, pMenuY + 9 * (mstate.last + 1) + 2)  '2px below the bottom of the menu
  IF show_title THEN graph_sl->Y += 10
  graph_sl->Size = get_resolution() - graph_sl->Pos - XY(0, 4)
  IF statnum >= 0 THEN
   n0 = her.Lev0.sta(statnum)
   nMax = her.LevMax.sta(statnum)

   REDIM graph->x(gen(genMaxLevel))
   REDIM graph->y(gen(genMaxLevel))
   FOR level as integer = 0 TO gen(genMaxLevel)
    graph->x(level) = level
    graph->y(level) = atlevel(level, n0, nMax)
   NEXT

   graph->mouse_over()
  END IF

  ' Menu positions
  DIM rightmenux as integer = small(274, get_resolution().w - 110)
  DIM midmenux as integer = rightmenux \ 2

  '--Draw screen
  clearpage dpage
  DrawSlice rootsl, dpage
  IF statnum >= 0 ANDALSO show_title THEN
   textcolor uilook(uiDescription), 0
   printstr statnames(statnum) & " by level", pCentered, graph_sl->ScreenY + ancBottom + 2, dpage
  END IF

  'Note that exactly one of the following two standardmenus should be active, since they share 'state'
  IF activemenu = 0 THEN
   standardmenu leftmenu, mstate, , , dpage
   IF twomenus THEN standardmenu rightmenu, inactive_state, rightmenux, , dpage
  ELSE 'activemenu = 1
   standardmenu leftmenu, inactive_state, , , dpage
   IF twomenus THEN standardmenu rightmenu, mstate, rightmenux, , dpage
  END IF
  IF gen(genMaxLevel) <> 99 ANDALSO gen(genMaxLevel) > 0 THEN
   standardmenu middlemenu, inactive_state, midmenux, , dpage
  END IF

/'
  IF statnum >= 0 THEN
   'Experimental stuff
   IF keyval(scLeftShift) > 1 THEN
    debug "0=" & atlevel(0, n0, nMax) & " max(" & gen(genMaxLevel) & ")=" & atlevel(gen(genMaxLevel), n0, nMax) & " 99=" & atlevel(99, n0, nMax)
   END IF
   IF keyval(scF7) > 1 AND gen(genMaxLevel) > 0 THEN
    gen(genMaxLevel) -= 1
    debug "gen(genMaxLevel)=" & gen(genMaxLevel)
    mstate.need_update = YES
   END IF
   IF keyval(scF8) > 1 THEN
    gen(genMaxLevel) += 1
    debug "gen(genMaxLevel)=" & gen(genMaxLevel)
    mstate.need_update = YES
   END IF
  END IF
'/

  IF statnum >= 0 THEN
   'Show more info about selected stat value, if appropriate
   DIM as integer whatlevel, statval
   IF graph->showpoint THEN
    whatlevel = CINT(graph->showx)
   ELSEIF activemenu = 0 THEN
    whatlevel = 0
   ELSE
    whatlevel = gen(genMaxLevel)
   END IF
   statval = atlevel(whatlevel, her.Lev0.sta(statnum), her.LevMax.sta(statnum))
   textcolor uilook(uiMenuItem), 0
   printstr stat_value_caption(statnum, statval), pInfoX, pInfoY, dpage
  END IF

  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP
 v_free leftmenu
 v_free rightmenu
 DeleteSlice @rootsl  'Deletes graph
END SUB

SUB hero_editor_spell_lists_toplevel (her as HeroDef)
 DIM spell_list_types(3) as string
 spell_list_types(0) = ""
 spell_list_types(1) = "Level-MP (FF1 Style)"
 spell_list_types(2) = "Random Effects"

 DIM menu(4) as string
 menu(0) = "Previous Menu"

 DIM mstate as MenuState
 WITH mstate
  .pt = 0
  .first = 0
  .last = UBOUND(menu)
  .size = 24
 END WITH

 setkeys YES
 DO
  setwait 55
  setkeys YES
  IF keyval(ccCancel) > 1 THEN EXIT DO
  IF keyval(scF1) > 1 THEN show_help "hero_spell_lists_menu"
  usemenu mstate
  DIM listnum as integer = mstate.pt - 1
  IF mstate.pt >= 1 THEN
   strgrabber her.list_name(listnum), 10
  END IF
  IF menu_click(mstate) ORELSE keyval(scAnyEnter) > 1 THEN  'Avoid enter_space_click
   IF mstate.pt = 0 THEN EXIT DO
   IF mstate.pt >= 1 AND mstate.pt <= 4 THEN
    hero_editor_edit_spell_list her, listnum
   END IF
  END IF

  FOR i as integer = 0 TO 3
   menu(1 + i) =  "Spell list " & i & ": " & her.list_name(i)
  NEXT i
  clearpage dpage
  standardmenu menu(), mstate, , , dpage
  IF listnum >= 0 THEN
   edgeprint spell_list_types(her.list_type(listnum)), pInfoX, pInfoY - 10, uilook(uiSelectedDisabled), dpage
   edgeprint "Type a name; ENTER to edit spells", pInfoX, pInfoY, uilook(uiDisabledItem), dpage
  END IF
  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP
END SUB

SUB hero_editor_edit_spell_list (her as HeroDef, byval listnum as integer)

 DIM menu(3) as string
 DIM selectable(-4 TO 23) as bool
 DIM spellnames(23) as string
 DIM col as integer
 DIM bgcol as integer

 DIM root as Slice Ptr
 root = NewSliceOfType(slContainer)
 WITH *root
  .Fill = YES
  .PaddingLeft = 4
  .PaddingRight = 4
  .PaddingBottom = 4
 END WITH
 DIM spellbox as Slice Ptr
 spellbox = NewSliceOfType(slRectangle, root)
 WITH *spellbox
  .x = 2
  .y = 48
  .width = 308
  .height = 80
  ChangeRectangleSlice spellbox, 1
 END WITH
 DIM infobox as Slice Ptr
 infobox = NewSliceOfType(slText, root)
 ChangeTextSlice infobox, "", , , YES ' make wrapping
 WITH *infobox
  .Fill = YES
  .FillMode = sliceFillHoriz
  .AlignVert = alignBottom
  .AnchorVert = alignBottom
 END WITH
 
 DIM info as string

 DIM mstate as MenuState
 WITH mstate
  .first = -4
  .pt = -4
  .top = -4
  .last = UBOUND(spellnames)
  .size = 30   'For pageup/down
  .need_update = YES
 END WITH
 menu(0) = "Previous Menu"
 menu(3) = "Spells:"
 flusharray selectable(), , YES
 selectable(3 - 4) = NO  '"Spells:"

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

  'FIXME: this menu doesn't support mouse controls
  IF mstate.pt >= 0 THEN
   IF usemenu(mstate, selectable(), ccLeft, ccRight) THEN
    mstate.need_update = YES
   ELSE
    IF keyval(ccUp) > 1 THEN
     mstate.pt -= 3
     IF mstate.pt < 0 THEN mstate.pt = -2  'Skip over 'Spells:'
     mstate.need_update = YES
    ELSEIF keyval(ccDown) > 1 THEN
     mstate.pt += 3
     IF mstate.pt > mstate.last THEN mstate.pt = mstate.first
     mstate.need_update = YES
    END IF
   END IF
  ELSE
   IF usemenu(mstate, selectable()) THEN mstate.need_update = YES
  END IF

  IF enter_space_click(mstate) THEN
   IF mstate.pt = -4 THEN
    EXIT DO
   END IF
   IF mstate.pt >= 0 THEN
    hero_editor_edit_spell_list_slot her.spell_lists(listnum, mstate.pt)
    mstate.need_update = YES
   END IF
  END IF
  
  IF mstate.pt = -3 THEN
   IF strgrabber(her.list_name(listnum), 10) THEN mstate.need_update = YES
  END IF

  IF mstate.pt = -2 THEN
   IF intgrabber(her.list_type(listnum), 0, 2) THEN mstate.need_update = YES
  END IF

  IF mstate.pt >= 0 THEN
   IF zintgrabber(her.spell_lists(listnum, mstate.pt).attack, -1, gen(genMaxAttack), scLeftCaret, scRightCaret) THEN mstate.need_update = YES
  END IF

  IF mstate.need_update THEN
   menu(1) = "Spell list name: " & her.list_name(listnum)
   menu(2) = "List Type: "
   SELECT CASE her.list_type(listnum)
    CASE 0: menu(2) &= "Normal"
    CASE 1: menu(2) &= "Use Level-MP (FF1-Style)"
    CASE 2: menu(2) &= "Random Effects"
    CASE ELSE: menu(2) &= "???Unknown " & her.list_type(listnum)
   END SELECT
   FOR i as integer = 0 TO UBOUND(spellnames)
    WITH her.spell_lists(listnum, i)
     IF .attack > 0 THEN
      spellnames(i) = readattackname(.attack - 1)
     ELSE
      spellnames(i) = ""
     END IF
     spellnames(i) = rpad(spellnames(i), " ", 10)
    END WITH
   NEXT i
   info = ""
   IF LEN(her.list_name(listnum)) = 0 THEN
    info &= !"You must name this spell list or it will be hidden!\n"
   END IF

   IF mstate.pt >= 0 THEN
    WITH her.spell_lists(listnum, mstate.pt)
     IF .attack > 0 THEN
      info &= "Attack ID = " & (.attack - 1) & !"\n"
      IF .learned > 0 THEN
       info &= "Learned at level " & (.learned - 1) & !"\n"
      ELSE
       info &= "Learned from an item" & !"\n"
      END IF
     END IF
    END WITH
    IF her.list_type(listnum) = 1 THEN
     info &= "1 point of level " & (mstate.pt \ spellsPerLMP) + 1 & " " & readglobalstring(160, "Level MP", 20)
    END IF
   END IF
   ChangeTextSlice infobox, info
   mstate.need_update = NO
  END IF

  '--Draw screen
  mstate.tog XOR= 1
  clearpage dpage
  DrawSlice root, dpage
  FOR i as integer = 0 TO 3
   col = uiLook(uiMenuItem)
   IF mstate.pt + 4 = i THEN col = uiLook(uiSelectedItem) + mstate.tog
   edgeprint menu(i), pMenuX, pMenuY + 10 * i, col, dpage
  NEXT i
  FOR i as integer = 0 TO mstate.last
   col = uiLook(uiMenuItem)
   bgcol = 0
   IF mstate.pt = i THEN
    col = uiLook(uiSelectedItem) + mstate.tog
    bgcol = uilook(uiHighlight)
   END IF
   textcolor col, bgcol
   printstr spellnames(i), 12 + (i MOD 3) * 104, 54 + (i \ 3) * 8, dpage
  NEXT i

  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP
 
 DeleteSlice @root
END SUB

SUB hero_editor_edit_spell_list_slot (slot as SpellList)

 DIM menu as MenuDef
 append_menu_item menu, "Previous Menu"
 append_menu_item menu, "Attack:"
 append_menu_item menu, "Learned at level:"
 append_menu_item menu, "Learned from an item:"
 menu.textalign = alignLeft
 
 DIM st as MenuState
 init_menu_state st, menu
 st.pt = 1
 st.need_update = YES
 st.active = YES

 DIM cap as string = ""

 DIM holdscreen as integer
 holdscreen = allocatepage
 copypage vpage, holdscreen

 setkeys
 DO
  setwait 55
  setkeys
  IF keyval(scF1) > 1 THEN show_help "hero_spells_slot"
  IF keyval(ccCancel) > 1 THEN EXIT DO
  
  usemenu st

  SELECT CASE st.pt
   CASE 0:
    IF enter_space_click(st) THEN
     EXIT DO
    END IF
   CASE 1:
    IF attackgrabber(slot.attack, st, 1, 0) THEN st.need_update = YES
   CASE 2:
    IF zintgrabber(slot.learned, -1, 99) THEN st.need_update = YES
   CASE 3:
    IF enter_space_click(st) ORELSE keyval(ccLeft) > 1 ORELSE keyval(ccRight) > 1 THEN
     IF slot.learned > 0 THEN
      slot.learned = 0
     ELSE
      slot.learned = 1
     END IF
     st.need_update = YES
    END IF
  END SELECT
  
  IF st.need_update THEN
   cap = "(None)"
   IF slot.attack > 0 THEN cap = (slot.attack - 1) & " " & readattackname(slot.attack - 1)
   menu.items[1]->caption = "Attack: " & cap

   cap = ""
   IF slot.learned > 0 THEN cap = STR(slot.learned - 1)
   menu.items[2]->caption = "Learned at level: " & cap

   cap = "NO"
   IF slot.learned = 0 THEN cap = "YES"
   menu.items[3]->caption = "Learned from an item: " & cap
   
   st.need_update = NO
  END IF

  copypage holdscreen, vpage
  draw_menu menu, st, vpage
  IF st.pt = 1 THEN
   edgeprint THINGGRABBER_TOOLTIP, 0, pBottom, uilook(uiSelectedDisabled), vpage
  END IF
  setvispage vpage
  
  dowait
 LOOP

 freepage holdscreen
 setkeys
END SUB

SUB hero_editor_battle_menu (hero_id as integer, her as HeroDef)
 DIM menu as MenuDef
 DIM preview as MenuDef
 DIM viewport_page as integer = gameres_page()
  
 DIM st as MenuState
 st.active = YES
 st.autosize = YES
 st.autosize_ignore_pixels = 18
 st.need_update = YES
 
 DIM prst as MenuState
 
 DIM hero_node as NodePtr = her.reld
 DIM edit_node as NodePtr

 DIM item as ItemDef
 
 setkeys YES
 DO
  setwait 55
  setkeys YES
  IF keyval(scF1) > 1 THEN show_help "hero_battle_menu_edit"
  IF keyval(ccCancel) > 1 THEN EXIT DO

  IF st.need_update THEN
   InitLikeStandardMenu menu
   init_battle_menu preview, gen(genDefaultBattleMenu) - 1

   append_menu_item menu, "Previous Menu"
   ' So much duplication with the in-game code :(
   READNODE hero_node."battle_menus" as batmen, ignoreall
    WITHNODE batmen."menu" as bmenu
     DIM added_to_preview as bool = YES
     DIM k as NodePtr = bmenu."kind".ptr
     IF k."weapon".exists THEN
      append_menu_item menu, "Weapon"
      menu.last->dataptr = bmenu
      loaditemdata item, her.def_weapon
      DIM atk_id as integer = item.battle_weapon_attack
      IF atk_id < 0 THEN atk_id = 0
      append_menu_item preview, readattackname(atk_id)
     ELSEIF k."items".exists THEN
      append_menu_item menu, "Items"
      menu.last->dataptr = bmenu
      append_menu_item preview, readglobalstring(34, "Item", 10)
     ELSEIF k."spells".exists THEN
      DIM listname as string = her.list_name(k."spells".integer)
      append_menu_item menu, "Spell List " & k."spells".integer & " " & listname
      menu.last->dataptr = bmenu
      IF LEN(listname) THEN
       append_menu_item preview, listname
      ELSE
       added_to_preview = NO
      END IF
     ELSEIF k."attack".exists THEN
      append_menu_item menu, "Attack " & k."attack".integer & " " & readattackname(k."attack".integer)
      menu.last->dataptr = bmenu
      append_menu_item preview, readattackname(k."attack".integer)
     ELSEIF k."skip".exists THEN
      append_menu_item menu, "Skip Turn"
      menu.last->dataptr = bmenu
      append_menu_item preview, readglobalstring(332, "Skip Turn", 20)
     ELSE
      debug "Unknown battle menu item " & GetInteger(bmenu)
      append_menu_item menu, "[Invalid]"
      menu.last->dataptr = bmenu
      added_to_preview = NO
     END IF
     IF added_to_preview THEN
      ' Set up other data
      WITH *preview.last
       IF LEN(bmenu."caption".string) THEN .caption = bmenu."caption".string
       .col = bmenu."color".default(0)
      END WITH
      'Point to associated item in the preview
      menu.last->extra(0) = preview.numitems - 1
     ELSE
      menu.last->extra(0) = -1
     END IF
    END WITHNODE
   END READNODE
   init_menu_state st, menu
   init_menu_state prst, preview
   IF channel_to_Game THEN saveherodata her, hero_id
   st.need_update = NO
  END IF
  
  menu_of_reorderable_nodes st, menu
  usemenu st
  IF menu.items[st.pt]->extra(0) >= 0 THEN
   prst.pt = menu.items[st.pt]->extra(0)
   prst.active = YES
   init_menu_state prst, preview  'Update
  ELSE
   prst.active = NO
  END IF
  
  IF enter_space_click(st) THEN
   IF st.pt = 0 THEN EXIT DO
   edit_node = menu.items[st.pt]->dataptr
   edit_battle_menu_item her, edit_node
   st.need_update = YES
  END IF

  IF keyval(scPlus) > 1  ORELSE keyval(scNumpadPlus) > 1 ORELSE keyval(scInsert) > 1 THEN
   DIM ins_node as NodePtr
   ins_node = AppendChildNode(hero_node."battle_menus".ptr, "menu")
   DIM other_node as NodePtr
   other_node = menu.items[st.pt]->dataptr
   IF other_node = 0 THEN other_node = FirstChild(hero_node."battle_menus".ptr)
   IF other_node THEN AddSiblingBefore(other_node, ins_node)
   DIM k as NodePtr
   k = SetChildNode(ins_node, "kind")
   SetChildNode(k, "attack", 0)
   st.need_update = YES
  END IF
  
  IF keyval(scDelete) > 1 THEN
   DIM del_node as NodePtr
   del_node = menu.items[st.pt]->dataptr
   IF del_node THEN
    IF yesno("Really delete this menu item?", NO, NO) THEN
     FreeNode del_node
     st.need_update = YES
    END IF
   END IF
  END IF
    
  clearpage dpage
  preview_menu preview, prst, viewport_page, dpage
  edgeprint "INSERT=add SHIFT=move DEL=delete F1=help", pInfoX, pInfoY, uilook(uiSelectedDisabled), dpage
  edgeprint "Preview", pRight - 10, pMenuY, uilook(uiText), dpage
  draw_menu menu, st, dpage
  SWAP vpage, dpage
  setvispage vpage
  
  dowait
 LOOP

 freepage viewport_page
 
END SUB

ENUM EdBatMenuMenuItemType
 edbatmenu_EXIT = mtypeLAST + 1
 edbatmenu_KIND
 edbatmenu_NUM 'extra 0 = lower limit, extra 1 = upper limit
 edbatmenu_STR
 edbatmenu_TAG
 edbatmenu_COLOR 'extra 0 = lower limit, extra 1 = upper limit  (to reduce redundancy...)
 edbatmenu_ATTACK
 edbatmenu_UNSELECTABLE 'Does nothing
END ENUM

SUB edit_battle_menu_item (hero as HeroDef, byval edit_node as NodePtr)
 IF edit_node = 0 THEN debug "edit_battle_menu_item null NodePtr": EXIT SUB

 ' Initialise new defaults
 upgrade_hero_battle_menu_item edit_node

 DIM holdscreen as integer
 holdscreen = allocatepage
 copypage vpage, holdscreen

 DIM menu as MenuDef
 DIM st as MenuState
 st.active = YES
 st.need_update = YES
 
 DIM k as NodePtr
 DIM v as integer
 DIM spell_list_name as string

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

  IF st.need_update THEN
   ClearMenuData menu
   WITH menu
    .boxstyle = 1
    .textalign = alignLeft
    .min_chars = 36
   END WITH
   append_menu_item menu, "Previous menu...", edbatmenu_EXIT

   ' Kind-specific options
   DIM auto_okay as bool = NO
   k = edit_node."kind".ptr
   IF k."weapon".exists THEN
    append_menu_item menu, "Kind: Weapon's Attack", edbatmenu_KIND
    auto_okay = YES
   ELSEIF k."attack".exists THEN
    append_menu_item menu, "Kind: Specific Attack", edbatmenu_KIND
    v = k."attack".integer
    append_menu_item menu, "Attack: " & v & " " & readattackname(v), edbatmenu_ATTACK
    menu.last->dataptr = k."attack".ptr
    auto_okay = YES
   ELSEIF k."spells".exists THEN
    append_menu_item menu, "Kind: Spell List", edbatmenu_KIND
    v = k."spells".integer
    spell_list_name = safe_caption(hero.list_name(), v, "list")
    append_menu_item menu, "List Number: " & v & " " & spell_list_name, edbatmenu_NUM
    menu.last->dataptr = k."spells".ptr
    menu.last->extra(0) = 0
    menu.last->extra(1) = 3
    auto_okay = YES
   ELSEIF k."items".exists THEN
    append_menu_item menu, "Kind: Items Menu", edbatmenu_KIND
   ELSEIF k."skip".exists THEN
    append_menu_item menu, "Kind: Skip Turn", edbatmenu_KIND
   ELSE
    debug "Unknown battle menu kind"
    append_menu_item menu, "Kind: ???", edbatmenu_KIND
   END IF

   ' General options
   append_menu_item menu, "Caption: " & blank_default(edit_node."caption".string), edbatmenu_STR
   menu.last->dataptr = edit_node."caption".ptr
   append_menu_item menu, "Color: " & zero_default(edit_node."color"), edbatmenu_COLOR
   menu.last->dataptr = edit_node."color".ptr
   append_menu_item menu, tag_condition_caption(edit_node."enable_tag1", "Enabled if tag", "Always"), edbatmenu_TAG
   menu.last->dataptr = edit_node."enable_tag1".ptr
   append_menu_item menu, tag_condition_caption(edit_node."enable_tag2", "  and also tag", "Always"), edbatmenu_TAG
   menu.last->dataptr = edit_node."enable_tag2".ptr
   append_menu_item menu, "When disabled: " & IIF(edit_node."hide_disabled", "Hidden", "Unusable"), edbatmenu_NUM
   menu.last->dataptr = edit_node."hide_disabled".ptr
   menu.last->extra(0) = 0
   menu.last->extra(1) = 1
   DIM captmp as string = IIF(edit_node."exclude_auto_battle", "Exclude this menu item", "Can use this menu item")
   IF NOT auto_okay THEN captmp = "N/A"
   append_menu_item menu, "Auto Battle: " & captmp, edbatmenu_NUM
   menu.last->dataptr = edit_node."exclude_auto_battle".ptr
   menu.last->extra(0) = 0
   menu.last->extra(1) = 1

   ' Other messages
   IF k."spells".exists THEN
    ' Provide warnings that hide_disabled doesn't override this...
    IF LEN(spell_list_name) = 0 THEN
     append_menu_item menu, "(Always hidden: unnamed spell list!)", edbatmenu_UNSELECTABLE
     menu.last->disabled = YES
    ELSEIF readbit(hero.bits(), 0, 26) THEN  '"Do not show spell lists if empty"
     append_menu_item menu, "(Hidden if spell list empty)", edbatmenu_UNSELECTABLE
     menu.last->disabled = YES
    END IF
   'This is how it actually works, but I don't want to document this dubious behaviour
   ' ELSEIF k."attack".exists THEN
   '  DIM atk as AttackData
   '  loadattackdata atk, k."attack"
   '  IF atk.check_costs_as_weapon THEN
   '   append_menu_item menu, "(Disabled if atk costs not met)", edbatmenu_UNSELECTABLE
   '   menu.last->disabled = YES    
   '  END IF
   ELSEIF k."weapon".exists OR k."attack".exists THEN
    append_menu_item menu, IIF(k."weapon".exists, "(May be disabled", "(Disabled") + " if costs not met)", _
                     edbatmenu_UNSELECTABLE
    menu.last->disabled = YES
   END IF

   init_menu_state st, menu
   st.need_update = NO
  END IF
  
  usemenu st
  
  WITH *menu.items[st.pt]
   IF enter_space_click(st) THEN
    SELECT CASE .t
     CASE edbatmenu_EXIT: EXIT DO
     CASE edbatmenu_KIND:
      browse_battle_menu_kinds edit_node
      st.need_update = YES
     CASE edbatmenu_COLOR:
      SetContent(.dataptr, color_browser_256(GetInteger(.dataptr)))
      st.need_update = YES
     'edbatmenu_TAG action is handled by tag_grabber
    END SELECT
   END IF

   IF .dataptr THEN v = GetInteger(.dataptr)  'Doesn't matter if it's not an int
   SELECT CASE .t
    CASE edbatmenu_NUM
     st.need_update OR= intgrabber(v, .extra(0), .extra(1))
     SetContent(.dataptr, v)
    CASE edbatmenu_ATTACK
     st.need_update OR= attackgrabber(v, st)
     SetContent(.dataptr, v)
    CASE edbatmenu_COLOR
     st.need_update OR= intgrabber(v, 0, 255)
     SetContent(.dataptr, v)
    CASE edbatmenu_TAG
     st.need_update OR= tag_grabber(v, st)
     SetContent(.dataptr, v)
    CASE edbatmenu_STR
     DIM strval as string
     IF .dataptr THEN strval = GetString(.dataptr)
     st.need_update OR= strgrabber(strval, 24)
     SetContent(.dataptr, strval)
   END SELECT
  END WITH
  
  copypage holdscreen, vpage
  fuzzyrect 0, 0, , , uilook(uiBackground), vpage
  draw_menu menu, st, vpage
  IF menu.items[st.pt]->t = edbatmenu_ATTACK THEN
   edgeprint THINGGRABBER_TOOLTIP, 0, pBottom, uilook(uiMenuItem), vpage
  END IF
  setvispage vpage

  dowait
 LOOP

 freepage holdscreen
 setkeys
END SUB

SUB browse_battle_menu_kinds (byval menu_node as NodePtr)
 IF menu_node = 0 THEN debug "browse_battle_menu_kinds null NodePtr": EXIT SUB

 DIM holdscreen as integer
 holdscreen = allocatepage
 copypage vpage, holdscreen

 DIM menu as MenuDef
 DIM st as MenuState
 st.active = YES
 st.need_update = YES
 
 setkeys
 DO
  setwait 55
  setkeys
  IF keyval(scF1) > 1 THEN show_help "browse_battle_menu_kinds"
  IF keyval(ccCancel) > 1 THEN EXIT DO
  
  IF st.need_update THEN
   ClearMenuData menu
   append_menu_item menu, "Previous Menu...", , 0
   append_menu_item menu, "Weapon's Attack", , 1
   append_menu_item menu, "Specific Attack", , 2
   append_menu_item menu, "Spell List", , 3
   append_menu_item menu, "Items Menu", , 4
   append_menu_item menu, "Skip Turn", , 5
   init_menu_state st, menu
   st.need_update = NO
  END IF

  usemenu st
  
  IF enter_space_click(st) THEN
   WITH *menu.items[st.pt]
    SELECT CASE .sub_t
     CASE 1: change_battle_menu_kind menu_node, "weapon"
     CASE 2: change_battle_menu_kind menu_node, "attack", YES
     CASE 3: change_battle_menu_kind menu_node, "spells", YES
     CASE 4: change_battle_menu_kind menu_node, "items"
     CASE 5: change_battle_menu_kind menu_node, "skip"
    END SELECT
   END WITH
   EXIT DO
  END IF

  copypage holdscreen, vpage
  draw_menu menu, st, vpage
  setvispage vpage
  
  dowait
 LOOP

 freepage holdscreen
 setkeys
END SUB

SUB change_battle_menu_kind (byval menu_node as NodePtr, new_kind as string, init_integer as bool=NO)
 IF menu_node = 0 THEN debug "change_battle_menu_kind null NodePtr": EXIT SUB
 SELECT CASE new_kind
  CASE "weapon":
  CASE "attack":
  CASE "spells":
  CASE "items":
  CASE "skip":
  CASE ELSE:
   debug "change_battle_menu_kind attempted to set invalid kind: """ & new_kind & """"
   EXIT SUB
 END SELECT
 DIM k as NodePtr
 k = menu_node."kind".ptr
 IF k."weapon".exists THEN FreeNode k."weapon".ptr
 IF k."attack".exists THEN FreeNode k."attack".ptr
 IF k."spells".exists THEN FreeNode k."spells".ptr
 IF k."items".exists THEN FreeNode k."items".ptr
 IF k."skip".exists THEN FreeNode k."skip".ptr
 DIM n as NodePtr
 n = SetChildNode(k, new_kind)
 IF init_integer THEN
  SetContent n, 0
 END IF
END SUB

FUNCTION tag_range_check_string(byval check as TagRangeCheck) as string
 IF check.kind <> TagRangeCheckKind.level THEN RETURN "Check of kind """ & check.kind & """ is not supported"
 DIM kind_name as string = "level"
 IF check.tag < 0 THEN RETURN "Invalid tag value " & check.tag
 IF check.tag = 1 THEN RETURN "Tag 1 cannot be changed"
 IF check.tag = 0 THEN RETURN "(Select to edit the range check)"
 IF check.min = check.max THEN
  RETURN "Set tag " & check.tag & " if " & kind_name & " = " & check.min & " (" & load_tag_name(check.tag) & ")"
 END IF
 RETURN "Set tag " & check.tag & " if " & kind_name & " from " & check.min & " to " & check.max & " (" & load_tag_name(check.tag) & ")"
END FUNCTION

SUB edit_tag_range_check (byref check as TagRangeCheck)
 IF check.kind <> TagRangeCheckKind.level THEN visible_debug("edit_tag_range_check: unsupported check kind " & check.kind)
 DIM kind_name as string = "level"
 DIM menu as MenuDef
 append_menu_item menu, "Previous menu..."
 append_menu_item menu, "Tag:"
 append_menu_item menu, "At or above"
 append_menu_item menu, "At or below"
 menu.textalign = alignLeft
 menu.min_chars = 35

 DIM st as MenuState
 init_menu_state st, menu
 st.pt = 1
 st.need_update = YES
 st.active = YES

 DIM holdscreen as integer
 holdscreen = allocatepage
 copypage vpage, holdscreen

 setkeys
 DO
  setwait 55
  setkeys
  IF keyval(scF1) > 1 THEN show_help "edit_tag_range_check_" & kind_name
  IF keyval(ccCancel) > 1 THEN EXIT DO
  
  IF usemenu(st) THEN
   check.max = bound(check.max, check.min, 99)
   check.min = bound(check.min, 0, check.max)
   st.need_update = YES
  END IF

  SELECT CASE st.pt
   CASE 0:
    IF enter_space_click(st) THEN
     EXIT DO
    END IF
   CASE 1:
    IF tag_id_grabber(check.tag, st) THEN st.need_update = YES
   CASE 2:
    IF intgrabber(check.min, 0, 99) THEN st.need_update = YES
   CASE 3:
    IF intgrabber(check.max, 0, 99) THEN st.need_update = YES
  END SELECT

  IF st.need_update THEN
   menu.items[1]->caption = tag_condition_caption(check.tag, "Set tag", "NONE", "Not usable")
   menu.items[2]->caption = "At or above " & kind_name & " " & check.min
   menu.items[3]->caption = "At or below " & kind_name & " " & check.max
   st.need_update = NO
  END IF

  copypage holdscreen, vpage
  draw_menu menu, st, vpage
  setvispage vpage
  
  dowait
 LOOP

 freepage holdscreen
 setkeys
END SUB

TYPE HeroTagsMenu
 'DIM itemids(any) as integer  'Meaningless unique value, or -1
 DIM menu(any) as string
 DIM selectable(any) as bool
 DIM shaded(any) as bool      'Indicates headings

 'DECLARE SUB add_item(id as integer = -1, text as string = "", heading as bool = NO)
 'DECLARE SUB header(text as string)

 DECLARE SUB update(byref hero as HeroDef, byref st as MenuState)
END TYPE

SUB HeroTagsMenu.update (byref hero as HeroDef, byref st as MenuState)
 REDIM menu(9 + UBOUND(hero.checks) + 1)
 REDIM selectable(UBOUND(menu)) as bool
 REDIM shaded(UBOUND(menu)) as bool

 WITH hero
  menu(0) = "Previous Menu"
  shaded(1) = YES
  shaded(2) = YES
  menu(2) = " Hero Existence Checks"
  menu(3) = "have hero: " + tag_condition_caption(.have_tag, "set tag", "NONE", "Not usable")
  menu(4) = "is alive: " + tag_condition_caption(.alive_tag, "set tag", "NONE", "Not usable")
  menu(5) = "is leader: " + tag_condition_caption(.leader_tag, "set tag", "NONE", "Not usable")
  menu(6) = "is in party now: " + tag_condition_caption(.active_tag, "set tag", "NONE", "Not usable")

  shaded(7) = YES
  shaded(8) = YES
  menu(8) = " Hero Level Checks"
  FOR i as integer = 0 to UBOUND(hero.checks)
   menu(9 + i) = tag_range_check_string(hero.checks(i))
  NEXT i
  menu(UBOUND(menu)) = "Add a range check..."
 END WITH
 st.last = UBOUND(menu)

 FOR i as integer = 0 to UBOUND(selectable)
  selectable(i) = NOT shaded(i)
 NEXT i
END SUB

SUB hero_editor_tags (byval hero_id as integer, byref hero as HeroDef)
 DIM tagmenu as HeroTagsMenu
 DIM st as MenuState
 st.need_update = YES
 st.autosize = YES
 DIM menuopts as MenuOptions
 menuopts.disabled_col = uilook(eduiHeading)

 tagmenu.update hero, st

 setkeys
 DO
  setwait 55
  setkeys
  IF keyval(ccCancel) > 1 THEN EXIT DO
  IF keyval(scF1) > 1 THEN show_help "hero_tags"
  usemenu st, tagmenu.selectable()
  WITH hero
   SELECT CASE st.pt
    CASE 0
     IF enter_space_click(st) THEN EXIT DO
    CASE 3
     IF tag_id_grabber(.have_tag, st) THEN st.need_update = YES
    CASE 4
     IF tag_id_grabber(.alive_tag, st) THEN st.need_update = YES
    CASE 5
     IF tag_id_grabber(.leader_tag, st) THEN st.need_update = YES
    CASE 6
     IF tag_id_grabber(.active_tag, st) THEN st.need_update = YES
    CASE 9 TO UBOUND(tagmenu.menu) - 1
     'Edit an existing one here
     DIM check_id as integer = st.pt - 9
     IF enter_space_click(st) THEN
      edit_tag_range_check hero.checks(check_id)
      st.need_update = YES
     END IF
     IF keyval(scDelete) > 1 THEN
      IF yesno("Really delete this range check? """ & tag_range_check_string(hero.checks(check_id)) & """", NO, NO) THEN
       IF UBOUND(hero.checks) = 0 THEN
        ERASE hero.checks  'REDIMing would throw an error
       ELSE
        FOR j as integer = check_id + 1 TO UBOUND(hero.checks)
         hero.checks(j - 1) = hero.checks(j)
        NEXT j
        REDIM PRESERVE hero.checks(UBOUND(hero.checks) - 1) as TagRangeCheck
       END IF
       st.need_update = YES
      END IF
     END IF
    CASE UBOUND(tagmenu.menu)
     'Add a new range-check
     IF enter_space_click(st) THEN
      REDIM PRESERVE hero.checks(UBOUND(hero.checks) + 1) as TagRangeCheck
      hero.checks(UBOUND(hero.checks)).kind = TagRangeCheckKind.level
      st.need_update = YES
     END IF
   END SELECT

  END WITH

  IF st.need_update THEN
   tagmenu.update hero, st
   st.need_update = NO
   'Save the hero, and then regenerate the tag cache
   saveherodata hero, hero_id
   load_special_tag_caches ()
  END IF

  clearpage dpage
  standardmenu tagmenu.menu(), st, tagmenu.shaded(), , , dpage, menuopts
  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP
END SUB

'This is not complete; it exists just to prevent crashes due to data
'corruption (eg. bug 871)
SUB enforce_hero_data_limits(her as HeroDef)
 clamp_value her.sprite, 0, gen(genMaxHeroPic), "hero sprite"
 clamp_value her.sprite_pal, -1, gen(genMaxPal), "hero sprite pal"
 clamp_value her.walk_sprite, 0, gen(genMaxNPCPic), "hero walkabout sprite"
 clamp_value her.walk_sprite_pal, -1, gen(genMaxPal), "hero walkabout sprite pal"
 clamp_value her.portrait, -1, gen(genMaxPortrait), "hero portrait"
 clamp_value her.portrait_pal, -1, gen(genMaxPal), "hero portrait pal"
 FOR i as integer = 0 TO 3
  clamp_value her.list_type(i), 0, 2, "hero spell list " & i & " type"
 NEXT i
END SUB

SUB update_hero_appearance_menu(byref st as HeroEditState, menu() as string, her as HeroDef)
 menu(1) = "Battle Picture: " & her.sprite
 menu(2) = "Battle Palette: " & defaultint(her.sprite_pal)
 menu(3) = "Walkabout Picture: " & her.walk_sprite
 menu(4) = "Walkabout Palette: " & defaultint(her.walk_sprite_pal)
 menu(5) = "Base Level: " & her.def_level
 IF her.def_level < 0 THEN menu(5) = "Base Level: Party Average"
 menu(6) = "Experience Curve: " & format(her.exp_mult, "0.00")
 menu(7) = "Default Weapon: " & load_item_name(her.def_weapon, 0, 1)
 menu(8) = "Max Name Length: " & zero_default(her.max_name_len)
 menu(9) = "Hand position A..."
 menu(10) = "Hand position B..."
 menu(11) = "Portrait Picture: " & defaultint(her.portrait, "None")
 menu(12) = "Portrait Palette: " & defaultint(her.portrait_pal)
 menu(13) = "Actions in Battle: " & IIF(her.default_auto_battle, "Automatic", "Player Controlled")
 menu(14) = "Skip Victory Dance: " & IIF(her.skip_victory_dance, "Yes", "No")
 update_hero_preview_pics st, her
 st.changed = NO
END SUB

SUB update_hero_preview_pics(byref st as HeroEditState, her as HeroDef)
 clear_hero_preview_pics st
 WITH st
  load_sprite_and_pal .battle, sprTypeHero, her.sprite, her.sprite_pal
  load_sprite_and_pal .walkabout, sprTypeWalkabout, her.walk_sprite, her.walk_sprite_pal
  IF her.portrait >= 0 THEN
   load_sprite_and_pal .portrait, sprTypePortrait, her.portrait, her.portrait_pal
  END IF
 END WITH
END SUB

SUB clear_hero_preview_pics(byref st as HeroEditState)
 WITH st
  unload_sprite_and_pal .battle
  unload_sprite_and_pal .walkabout
  unload_sprite_and_pal .portrait
 END WITH
END SUB

SUB draw_hero_preview(st as HeroEditState, her as HeroDef)
 'Note that hero menu runs as genMillisecPerFrame, so use same number of ticks as in-game...
 'BUT battles still are 18.3fps and 2 ticks per frame so need to convert.
 DIM battle_wtog_ticks as integer = large(1, 55 * 2 / gen(genMillisecPerFrame))

 STATIC as integer wtog, battle_wtog
 loopvar wtog, 0, max_wtog(st.walkabout.sprite, st.preview_walk_direction)
 loopvar battle_wtog, 0, 2 * battle_wtog_ticks - 1

 STATIC tog as integer
 tog = tog XOR 1

 DIM frame as integer
 IF st.previewframe <> -1 THEN
  frame = frameATTACKA + st.previewframe
 ELSE
  frame = battle_wtog \ battle_wtog_ticks
 END IF
 frame_draw st.battle.sprite + frame, st.battle.pal, 250, 25, , dpage

 DIM frameid as integer
 frameid = 100 * st.preview_walk_direction + wtog_to_frame(wtog)
 frame = large(0, frameid_to_frame(st.walkabout.sprite, frameid))
 frame_draw st.walkabout.sprite + frame, st.walkabout.pal, 230 + st.preview_walk_pos.x, 5 + st.preview_walk_pos.y, , dpage
 DIM col as integer = uilook(uiSelectedItem + tog)
 IF st.previewframe <> -1 THEN
  DIM hand as XYPair = her.hand_pos(st.previewframe)
  drawline 248 + hand.x, 25 + hand.y, 249 + hand.x, 25 + hand.y, col, dpage
  drawline 250 + hand.x, 23 + hand.y, 250 + hand.x, 24 + hand.y, col, dpage
  drawline 251 + hand.x, 25 + hand.y, 252 + hand.x, 25 + hand.y, col, dpage
  drawline 250 + hand.x, 26 + hand.y, 250 + hand.x, 27 + hand.y, col, dpage
 END IF
 IF st.portrait.sprite THEN frame_draw st.portrait.sprite, st.portrait.pal, 240, 110, , dpage
END SUB

SUB animate_hero_preview(byref st as HeroEditState)
 WITH st
  .preview_steps += 1
  IF .preview_steps >= 15 THEN
   .preview_steps = 0
   loopvar .preview_walk_direction, 0, 3
  END IF
  IF .preview_walk_direction = 0 THEN .preview_walk_pos.y -= 4
  IF .preview_walk_direction = 1 THEN .preview_walk_pos.x += 4
  IF .preview_walk_direction = 2 THEN .preview_walk_pos.y += 4
  IF .preview_walk_direction = 3 THEN .preview_walk_pos.x -= 4
 END WITH
END SUB

SUB hero_editor_appearance(byref st as HeroEditState, byref her as HeroDef)
 
 DIM menu(14) as string
 DIM min(14) as integer
 DIM max(14) as integer
 menu(0) = "Previous Menu"
 min(1) = 0: max(1) = gen(genMaxHeroPic)
 min(2) = -1: max(2) = 32767
 min(3) = 0: max(3) = gen(genMaxNPCPic)
 min(4) = -1: max(4) = 32767
 min(5) = -1: max(5) = 99
 min(6) = 0: max(6) = 100
 min(7) = 0: max(7) = gen(genMaxItem)
 min(8) = 0: max(8) = 16
 min(9) = -100:max(9) = 100
 min(10) = -100:max(10) = 100
 min(11) = -1:max(11) = gen(genMaxPortrait)
 min(12) = -1:max(12) = 32767
 min(13) = -1:max(13) = 0
 min(14) = -1:max(14) = 0

 DIM state as MenuState
 WITH state
  .pt = 0
  .last = UBOUND(menu)
  .size = 24
 END WITH

 update_hero_appearance_menu st, menu(), her
 st.changed = NO
 setkeys
 DO
  setwait gen(genMillisecPerFrame)
  setkeys
  animate_hero_preview st
  IF keyval(ccCancel) > 1 THEN EXIT DO
  IF keyval(scF1) > 1 THEN show_help "hero_appearance"
  usemenu state
  st.previewframe = -1
  IF state.pt = 9 THEN st.previewframe = 0
  IF state.pt = 10 THEN st.previewframe = 1
  IF enter_space_click(state) AND state.pt = 0 THEN EXIT DO
  IF state.pt > 0 THEN
   SELECT CASE state.pt
    CASE 1
     IF intgrabber(her.sprite, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 2
     IF intgrabber(her.sprite_pal, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 3
     IF intgrabber(her.walk_sprite, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 4
     IF intgrabber(her.walk_sprite_pal, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 5
     IF intgrabber(her.def_level, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 6
     DIM exp_mult_int as integer = her.exp_mult * 100
     IF intgrabber(exp_mult_int, min(state.pt), max(state.pt)) THEN
      her.exp_mult = exp_mult_int / 100
      st.changed = YES
     END IF
    CASE 7
     IF intgrabber(her.def_weapon, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 8
     IF intgrabber(her.max_name_len, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 11
     IF intgrabber(her.portrait, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 12
     IF intgrabber(her.portrait_pal, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 13
     IF intgrabber(her.default_auto_battle, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
    CASE 14
     IF intgrabber(her.skip_victory_dance, min(state.pt), max(state.pt)) THEN
      st.changed = YES
     END IF
   END SELECT
   IF enter_space_click(state) THEN
    SELECT CASE state.pt
     CASE 1
      DIM spriteb as HeroSpriteBrowser
      her.sprite = spriteb.browse(her.sprite)
     CASE 2
      her.sprite_pal = pal16browse(her.sprite_pal, sprTypeHero, her.sprite, YES)
     CASE 3
      DIM spriteb as WalkaboutSpriteBrowser
      her.walk_sprite = spriteb.browse(her.walk_sprite)
     CASE 4
      her.walk_sprite_pal = pal16browse(her.walk_sprite_pal, sprTypeWalkabout, her.walk_sprite, YES)
     CASE 6
      her.exp_mult = experience_chart(her.exp_mult)
     CASE 7
      her.def_weapon = item_picker(her.def_weapon)
      max(7) = gen(genMaxItem)
     CASE 9
      xy_position_on_sprite st.battle, her.hand_pos(0).x, her.hand_pos(0).y, frameATTACKA, "Hand position (for weapon)", "xy_hero_hand"
     CASE 10
      xy_position_on_sprite st.battle, her.hand_pos(1).x, her.hand_pos(1).y, frameATTACKB, "Hand position (for weapon)", "xy_hero_hand"
     CASE 11
      DIM spriteb as PortraitSpriteBrowser
      her.portrait = spriteb.browse(her.portrait)
     CASE 12
      her.portrait_pal = pal16browse(her.portrait_pal, sprTypePortrait, her.portrait, YES)
     CASE 13
      her.default_auto_battle = NOT her.default_auto_battle
     CASE 14
      her.skip_victory_dance = NOT her.skip_victory_dance
    END SELECT
    st.changed = YES
   END IF
  END IF

  IF st.changed THEN update_hero_appearance_menu st, menu(), her

  clearpage dpage
  standardmenu menu(), state, , , dpage
  draw_hero_preview st, her
  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP
 st.previewframe = -1
END SUB

' Selection of equip slots; you're then taken to the menu for that slot
SUB hero_editor_equipment_list (byval hero_id as integer, byref her as HeroDef)
 DIM menu(5) as string
 DIM state as MenuState
 WITH state
  .last = 5
  .size = 22
 END WITH
 menu(0) = "Previous menu"
 menu(1) = her.name & "'s " & readglobalstring(38, "Weapon", 10) & " items"
 FOR i as integer = 0 TO 3
  menu(2+i) = her.name & "'s " & readglobalstring(25+i, "Armor " & (1+i), 10) & " items"
 NEXT i
 
 setkeys
 DO
  setwait 55
  setkeys
  IF keyval(ccCancel) > 1 THEN EXIT DO
  IF keyval(scF1) > 1 THEN show_help "hero_equipment"
  usemenu state
  IF enter_space_click(state) THEN
   IF state.pt = 0 THEN
    EXIT DO
   ELSE
    hero_editor_equipbits hero_id, state.pt, her.name
   END IF
  END IF
  clearpage dpage
  standardmenu menu(), state, , , dpage
  SWAP vpage, dpage
  setvispage vpage
  dowait
 LOOP
END SUB

SUB hero_editor_equipbits (byval hero_id as integer, byval equip_type as integer, heroname as string)
 '--equip_type is 0 for none (which would be silly) 1 for weapons and 2-5 for armor
 DIM title as string
 IF equip_type = 1 THEN
  title = readglobalstring(38, "Weapon", 10)
 ELSE
  title = readglobalstring(25 + (equip_type - 2), "Armor" & (equip_type - 1))
 END IF
 title &= " equipment for " & heroname

 DIM tempbits(gen(genMaxItem) \ 16 + 1) as integer 
 DIM itemname(gen(genMaxItem)) as string
 DIM item_id(gen(genMaxItem)) as integer
 DIM item as ItemDef
 DIM nextbit as integer = 0
 FOR i as integer = 0 TO gen(genMaxItem)
  loaditemdata item, i
  IF item.eqslots(equip_type - 1) THEN
   itemname(nextbit) = readitemname(i)
   'Blank menu items would be hidden in editbitset
   IF LEN(itemname(nextbit)) = 0 THEN itemname(nextbit) = "Item " & i
   item_id(nextbit) = i
   setbit tempbits(), 0, nextbit, item_read_equipbit(item, hero_id)
   nextbit += 1
  END IF
 NEXT i
 editbitset tempbits(), 0, itemname(), , , , title
 FOR i as integer = 0 TO nextbit-1
  loaditemdata item, item_id(i)
  item_write_equipbit item, hero_id, readbit(tempbits(), 0, i)
  saveitemdata item, item_id(i)
 NEXT i
END SUB

SUB hero_editor_elementals(byref her as HeroDef)
 common_elementals_editor her.elementals(), "hero_elementals"
END SUB

ENUM EditCounter
 Quit = 0
 Elemental = 100
 NonElemental = 101
 StatDamage = 102
END ENUM

SUB hero_editor_counterattacks (hero as HeroDef)

 DIM menu as MenuDef
 hero_editor_counterattacks_update_menu hero, menu
 
 DIM st as MenuState
 init_menu_state st, menu
 st.autosize = YES
 st.autosize_ignore_lines = 1
 st.pt = 1
 st.need_update = YES
 st.active = YES

 DIM holdscreen as integer
 holdscreen = allocatepage
 copypage vpage, holdscreen

 setkeys
 DO
  setwait 55
  setkeys
  IF keyval(scF1) > 1 THEN show_help "hero_counterattacks"
  IF keyval(ccCancel) > 1 THEN EXIT DO
  
  usemenu st

  DIM n as integer = menu.items[st.pt]->sub_t
  SELECT CASE menu.items[st.pt]->t
   CASE EditCounter.Quit:
    IF enter_space_click(st) THEN
     EXIT DO
    END IF
   CASE EditCounter.Elemental:
    IF attackgrabber(hero.elem_counter_attack(n), st, 1, 0) THEN st.need_update = YES
   CASE EditCounter.NonElemental:
    IF attackgrabber(hero.non_elem_counter_attack, st, 1, 0) THEN st.need_update = YES
   CASE EditCounter.StatDamage:
    IF attackgrabber(hero.stat_counter_attack(n), st, 1, 0) THEN st.need_update = YES
  END SELECT
  
  IF st.need_update THEN
   st.need_update = NO
   hero_editor_counterattacks_update_menu hero, menu
  END IF

  copypage holdscreen, vpage
  draw_menu menu, st, vpage
  setvispage vpage
  
  dowait
 LOOP

 freepage holdscreen
 setkeys
END SUB

SUB hero_editor_counterattacks_update_menu(hero as HeroDef, menu as MenuDef)
 DIM elementnames() as string
 getelementnames elementnames()

 ClearMenuData menu
 DIM atk_id as integer
 DIM atk_cap as string
 append_menu_item menu, "Previous menu...", EditCounter.Quit
 FOR i as integer = 0 TO gen(genNumElements) - 1
  atk_id = hero.elem_counter_attack(i) - 1
  atk_cap = IIF(atk_id >= 0, atk_id & " " & readattackname(atk_id), "NONE") 
  append_menu_item menu, "Counter element " & elementnames(i) & ": " & atk_cap, EditCounter.Elemental, i
 NEXT i
 atk_id = hero.non_elem_counter_attack - 1
 atk_cap = IIF(atk_id >= 0, atk_id & " " & readattackname(atk_id), "NONE") 
 append_menu_item menu, "Counter non-elemental attacks: " & atk_cap, EditCounter.NonElemental
 FOR i as integer = 0 TO statLast
  atk_id = hero.stat_counter_attack(i) - 1
  atk_cap = IIF(atk_id >= 0, atk_id & " " & readattackname(atk_id), "NONE") 
  append_menu_item menu, "Counter damage to " & statnames(i) & ": " & atk_cap, EditCounter.StatDamage, i
 NEXT i
 
 menu.textalign = alignLeft
 menu.anchorHoriz = alignLeft
 menu.alignHoriz = alignLeft
 menu.min_chars = 80
END SUB

