'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 RefreshSliceScreenPos root 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 itembuf(dimbinsize(binITM)) as integer 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 itembuf(), her.def_weapon DIM atk_id as integer = itembuf(48) - 1 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() 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 frame = st.preview_walk_direction * WALKFRAMES + wtog_to_frame(wtog) 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 itembuf(dimbinsize(binITM)) as integer DIM nextbit as integer = 0 FOR i as integer = 0 TO gen(genMaxItem) loaditemdata itembuf(), i IF item_is_equippable_in_slot(itembuf(), 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(itembuf(), hero_id) nextbit += 1 END IF NEXT i editbitset tempbits(), 0, itemname(), , , , title FOR i as integer = 0 TO nextbit-1 loaditemdata itembuf(), item_id(i) item_write_equipbit itembuf(), hero_id, readbit(tempbits(), 0, i) saveitemdata itembuf(), 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