'OHRRPGCE - Custom common code ' 'Please read LICENSE.txt for GPL License details and disclaimer of liability 'See README.txt for code docs and apologies for crappyness of this code ;) ' ' This file is for general purpose code use by CUSTOM but not by GAME. #include "config.bi" #include "string.bi" 'for format #include "allmodex.bi" #include "common.bi" #include "loading.bi" #include "const.bi" #include "scrconst.bi" #include "cglobals.bi" #include "reload.bi" #include "slices.bi" #include "ver.txt" #include "custom.bi" #include "customsubs.bi" 'Subs and functions only used here DECLARE SUB cond_editor (cond as Condition, default as bool = NO, outer_state as MenuState) DECLARE SUB platform_options_append_gamepad_button_menu_items (byval gamepad as NodePtr, menu as MenuDef, byval use_dpad as bool, buttons() as string, default_scancodes() as integer) DECLARE SUB edit_button_name_strings() DECLARE FUNCTION edit_purchase_enumgrabber(byval node as NodePtr, enumstr() as string) as bool DECLARE FUNCTION edit_purchase_enumbrowse(caption as string, byval node as NodePtr, enumstr() as string, helpkey as string) as bool DECLARE SUB edit_purchase_options_rebuild_menu(menu as MenuDef, st as MenuState, byval purchase_root as NodePtr, enabled() as bool) 'Module-local variables DIM SHARED comp_strings() as string REDIM comp_strings(7) as string comp_strings(0) = "" comp_strings(1) = "=" comp_strings(2) = "<>" comp_strings(3) = "<" comp_strings(4) = "<=" comp_strings(5) = ">" comp_strings(6) = ">=" comp_strings(7) = "tag" 'debugging use only FUNCTION safe_tag_name(byval tagnum as integer) as string IF tagnum >= 1 AND tagnum <= gen(genMaxTagName) THEN RETURN load_tag_name(tagnum) ELSE RETURN "" END IF END FUNCTION 'allowspecial indicates whether to allow picking 'special' tags: those automatically 'set, eg. based on inventory conditions FUNCTION tag_grabber (byref n as integer, byval min as integer=-99999, byval max as integer=99999, byval allowspecial as integer=YES) as integer min = large(min, -max_tag()) max = small(max, max_tag()) IF intgrabber(n, min, max) THEN RETURN YES IF enter_or_space() THEN DIM browse_tag as integer browse_tag = tags_menu(n, YES, allowspecial) IF browse_tag >= 2 OR browse_tag <= -2 THEN n = browse_tag RETURN YES END IF END IF RETURN NO END FUNCTION PRIVATE SUB tag_autoset_warning(byval tag_id as integer) notification !"This tag is automatically set or unset on the following conditions:\n" + describe_tag_autoset_places(tag_id) + !"\nThis means that you should not attempt to set or unset the tag in any other way, because your changes will be erased -- unpredictably!" END SUB 'If picktag is true, then can be used to pick a tag. In that case, allowspecial indicates whether to allow 'picking 'special' tags: those automatically set, eg. based on inventory conditions FUNCTION tags_menu (byval starttag as integer=0, byval picktag as integer=NO, byval allowspecial as integer=YES) as integer STATIC searchstring as string DIM thisname as string DIM ret as integer = starttag IF gen(genMaxTagname) < 1 THEN gen(genMaxTagname) = 1 DIM menu as BasicMenuItem vector v_new menu, gen(genMaxTagname) + 1 IF picktag THEN menu[0].text = "Cancel" ELSE menu[0].text = "Previous Menu" END IF DIM i as integer FOR i = 2 TO gen(genMaxTagname) + 1 'Load all tag names plus the first blank name menu[i - 1].text = "Tag " & i & ":" & load_tag_name(i) IF tag_is_autoset(i) THEN IF allowspecial = NO THEN menu[i - 1].disabled = YES ELSE 'We don't have any UI colours that are subtle enough! 'menu[i - 1].col = uilook(uiText) END IF END IF NEXT i DIM tagsign as integer tagsign = SGN(starttag) IF tagsign = 0 THEN tagsign = 1 DIM menuopts as MenuOptions menuopts.fullscreen_scrollbar = YES DIM state as MenuState state.autosize = YES state.autosize_ignore_lines = 1 state.last = gen(genMaxTagname) init_menu_state state, menu state.pt = 0 IF ABS(starttag) >= 2 THEN state.pt = small(ABS(starttag) - 1, gen(genMaxTagName)) thisname = safe_tag_name(state.pt + 1) DIM int_browsing as integer = NO DIM uninterrupted_alt_press as integer = NO 'Usually equal to state.pt + 1, but can reach 0 DIM alt_pt as integer DIM do_search as bool = NO DIM search_cur_str as string DIM search_found as bool = NO setkeys YES DO setwait 55 setkeys YES IF keyval(scESC) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "tagnames" IF keyval(scCTRL) > 0 ANDALSO keyval(scF) > 1 THEN IF prompt_for_string(searchstring, "Search") THEN do_search = YES END IF END IF IF keyval(scF3) > 1 THEN do_search = YES END IF IF do_search THEN do_search = NO IF searchstring <> "" THEN FOR i as integer = large(state.pt + 1, 1) TO gen(genMaxTagname) search_cur_str = load_tag_name(i + 1) IF INSTR(LCASE(search_cur_str), LCASE(searchstring)) THEN state.pt = i correct_menu_state state search_found = YES EXIT FOR END IF NEXT i IF NOT search_found THEN '--wrap the search FOR i as integer = 1 TO state.pt - 1 search_cur_str = load_tag_name(i + 1) IF INSTR(LCASE(search_cur_str), LCASE(searchstring)) THEN state.pt = i correct_menu_state state search_found = YES EXIT FOR END IF NEXT i END IF search_found = NO END IF END IF IF usemenu(state) THEN thisname = safe_tag_name(state.pt + 1) alt_pt = state.pt + 1 END IF IF keyval(scAlt) AND 4 THEN uninterrupted_alt_press = YES IF keyval(scAlt) = 0 AND uninterrupted_alt_press = YES THEN uninterrupted_alt_press = NO int_browsing XOR= YES alt_pt = state.pt + 1 END IF IF int_browsing THEN IF intgrabber(alt_pt, 0, gen(genMaxTagName) + 1) THEN state.pt = large(0, alt_pt - 1) correct_menu_state state thisname = safe_tag_name(state.pt + 1) END IF END IF IF state.pt = 0 AND enter_space_click(state) THEN EXIT DO IF state.pt > 0 AND state.pt + 1 <= gen(genMaxTagName) + 1 THEN IF keyval(scTab) > 1 ANDALSO tag_is_autoset(state.pt + 1) THEN tag_autoset_warning state.pt + 1 END IF IF keyval(scAnyEnter) > 1 THEN IF menu[state.pt].disabled THEN tag_autoset_warning state.pt + 1 ELSEIF picktag THEN ret = (state.pt + 1) * tagsign EXIT DO END IF END IF IF int_browsing = NO ANDALSO strgrabber(thisname, 20) THEN uninterrupted_alt_press = NO save_tag_name thisname, state.pt + 1 menu[state.pt].text = "Tag " & state.pt + 1 & ":" & thisname IF state.pt + 1 = gen(genMaxTagName) + 1 THEN IF gen(genMaxTagName) < max_tag() THEN gen(genMaxTagName) += 1 v_resize menu, gen(genMaxTagName) + 1 menu[gen(genMaxTagName)].text = "Tag " & gen(genMaxTagName) + 1 & ":" state.last += 1 END IF END IF END IF END IF clearpage dpage standardmenu menu, state, 0, 0, dpage, menuopts DIM tmpstr as string IF int_browsing THEN textcolor uilook(uiText), uilook(uiHighlight) tmpstr = "Tag " & alt_pt ELSE textcolor uilook(uiDisabledItem), 0 tmpstr = "Alt:Tag #" END IF printstr tmpstr, pRight, 0, dpage IF NOT int_browsing THEN tmpstr = "CTRL+F Search" printstr tmpstr, pRight, 10, dpage IF LEN(searchstring) > 0 THEN tmpstr = "F3 Again" printstr tmpstr, pRight, 20, dpage END IF END IF IF tag_is_autoset(state.pt + 1) THEN textcolor uilook(uiDisabledItem), 0 printstr "An auto-set tag. Press TAB for details", 0, pBottom, dpage END IF SWAP vpage, dpage setvispage vpage dowait LOOP v_free menu RETURN ret END FUNCTION 'default: meaning of the null condition (true: ALWAYS, false: NEVER) 'alwaysedit: experimental parameter, changes behaviour of enter/space 'Return value is currently very unreliable. FUNCTION cond_grabber (cond as Condition, default as bool = NO, alwaysedit as bool, st as MenuState) as bool DIM intxt as string = getinputtext DIM entered_operator as integer = NO DIM temp as integer WITH cond 'debug "cond_grabber: .type = " & comp_strings(.type) & " tag/var = " & .tag & " value = " & .value & " editst = " & .editstate & " lastchar = " & CHR(.lastinput) & " default = " & default IF keyval(scDelete) > 1 THEN .type = 0 RETURN YES END IF 'Simplify IF .type = compTag AND .tag = 0 THEN .type = 0 'enter_or_space IF .type = compTag AND alwaysedit = NO THEN IF enter_or_space() THEN DIM browse_tag as integer browse_tag = tags_menu(.tag, YES, YES) IF browse_tag >= 2 OR browse_tag <= -2 THEN .tag = browse_tag RETURN YES ELSE 'Return once enter/space processed RETURN NO END IF END IF ELSE IF keyval(scAnyEnter) > 1 THEN cond_editor(cond, default, st) END IF CONST compare_chars as string = "=<>!" 'Use strings instead of integers for convenience -- have to decode to use STATIC statetable(3, 7) as string * 2 => { _ /'Current comparison type: '/ _ /'None = <> < <= > >= Tag '/ _ {"=" ,"=" ,"=" ,"<=","=" ,">=","=" ,"=" }, /' = pressed '/ _ {"<" ,"<=","<" ,"<" ,"<" ,"<>","<" ,"<" }, /' < pressed '/ _ {">" ,">=",">" ,"<>",">" ,">" ,">" ,">" }, /' > pressed '/ _ {"<>","<>","=" ,">=",">" ,"<=","<" ,"<>"} /' ! pressed '/ _ } 'Listen for comparison operator input FOR i as integer = 1 TO LEN(intxt) DIM inchar as string = MID(intxt, i) DIM charnum as integer = INSTR(compare_chars, inchar) IF charnum THEN entered_operator = YES DIM newtype as integer = -1 IF .type = compNone OR .type = compTag OR .editstate = 1 OR .editstate = 5 THEN 'Ignore the current operator; we're pretending there is none newtype = str_array_findcasei(comp_strings(), inchar) ELSE 'First check whether in the middle of typing a comparison operator. 'This special check ensure that eg. typing >= causes the operator to 'change to >= regardless of initial state IF .lastinput THEN 'Only checking input strings of len 2 newtype = str_array_findcasei(comp_strings(), CHR(.lastinput) + inchar) END IF IF newtype = -1 THEN 'This _temp variable is to work around a FB bug, https://sourceforge.net/p/fbc/bugs/816/ '(Fixed in FB 1.06.) 'It only occurs when compiling with debug=0 (without -exx. Whether adding/removing -exx causes 'the bug to occur depends on the surrounding context in the function). DIM _temp as integer = charnum - 1 DIM tempcomp as string = statetable(_temp, .type) newtype = str_array_findcasei(comp_strings(), tempcomp) END IF END IF IF newtype > -1 THEN IF .type = compNone OR .type = compTag THEN 'In future, largest allowable tag ID will increase .varnum = small(ABS(.tag), maxScriptGlobals) .value = 0 .editstate = 2 END IF .type = newtype END IF END IF .lastinput = ASC(inchar) NEXT 'Other input: a finite state machine IF .type = compNone THEN 'No need to check for entered_operator: the type would have changed .tag = 0 IF intgrabber(.tag, -max_tag(), max_tag()) THEN .type = compTag END IF ELSEIF .type = compTag THEN 'editstate meaning (asterisks indicate highlighting) '0: Tag #=OFF/ON (no highlight) '1: Tag *#*=OFF/ON 'No need to check for entered_operator IF INSTR(intxt, "!") THEN .tag = -.tag ELSE intgrabber(.tag, -max_tag(), max_tag()) END IF ELSE 'Globals .varnum = bound(.varnum, 0, maxScriptGlobals) 'Could be negative if it was a tag condition 'editstate is just a state id, defining the way the condition is edited and displayed '(below, asterisks indicate highlighting) '0: Global # .. # (initial) '1: Global *#* '2: Global # *..* '3: Global # .. *#* '4: Global # *..* # '5: Global # *?* # '6: Global *#* .. # SELECT CASE .editstate CASE 0 IF keyval(scTab) > 1 THEN .editstate = 3 ELSEIF keyval(scBackspace) > 1 THEN 'Backspace works from the right... intgrabber(.value, -2147483648, 2147483647) .editstate = 3 ELSEIF entered_operator THEN .editstate = 4 ELSE '...and numerals enter from the left temp = 0 'Don't erase previous value when trying to inc/decrement it IF keyval(scLeft) > 0 OR keyval(scRight) > 0 THEN temp = .varnum IF intgrabber(temp, 0, maxScriptGlobals, , , YES) THEN .varnum = temp .editstate = 6 END IF END IF CASE 1, 6 IF .editstate = 6 AND keyval(scTab) > 1 THEN .editstate = 3 ELSEIF entered_operator THEN IF .editstate = 1 THEN .editstate = 2 ELSE .editstate = 4 ELSEIF keyval(scBackspace) > 1 AND .varnum = 0 THEN .editstate = 0 .type = compNone ELSE intgrabber(.varnum, 0, maxScriptGlobals) END IF CASE 3 IF keyval(scTab) > 1 THEN .editstate = 6 ELSEIF entered_operator THEN .editstate = 4 ELSEIF keyval(scBackspace) > 1 AND .value = 0 THEN .editstate = 2 ELSE intgrabber(.value, -2147483648, 2147483647) END IF CASE 2, 4, 5 'Operator editing IF keyval(scTab) > 1 THEN .editstate = 3 ELSEIF .editstate = 5 AND entered_operator THEN .editstate = 4 ELSEIF keyval(scBackspace) > 1 THEN DIM newcomp as string = comp_strings(.type) IF .editstate = 5 THEN 'state 5 simulates LEN(newcomp) = 0 .editstate = 1 ELSEIF LEN(newcomp) = 1 THEN IF .editstate = 2 THEN .editstate = 1 ELSEIF .editstate = 4 THEN .editstate = 5 END IF ELSE 'LEN = 2 .type = str_array_findcasei(comp_strings(), LEFT(newcomp, 1)) END IF ELSE temp = 0 'IF .editstate <> 2 THEN temp = .value 'Don't erase previous value when trying to inc/decrement it IF keyval(scLeft) > 0 OR keyval(scRight) > 0 THEN temp = .value IF intgrabber(temp, -2147483648, 2147483647, , , YES) THEN .value = temp .editstate = 3 END IF END IF END SELECT END IF END WITH 'FIXME: check if anything changed, and return YES if so END FUNCTION 'default: meaning of the null condition (true: ALWAYS, false: NEVER) SUB cond_editor (cond as Condition, default as bool = NO, outer_state as MenuState) DIM menu(10) as string DIM compty(10) as integer 'CompType (can't pass that to int_array_find) menu(0) = "Cancel" menu(1) = "Always" menu(2) = "Never" menu(3) = "Tag # ON" : compty(3) = compTag menu(4) = "Tag # OFF" : compty(4) = compTag menu(5) = "Global # = #" : compty(5) = compEq menu(6) = "Global # <> #" : compty(6) = compNe menu(7) = "Global # < #" : compty(7) = compLt menu(8) = "Global # <= #" : compty(8) = compLe menu(9) = "Global # > #" : compty(9) = compGt menu(10) = "Global # >= #" : compty(10) = compGe DIM st as MenuState st.last = UBOUND(menu) st.size = st.last + 1 DIM starttag as integer = 1 ' Determine initial menu selection IF cond.type = compTag AND cond.tag = 0 THEN cond.type = compNone IF cond.type = compNone THEN st.pt = IIF(default, 1, 2) ELSEIF cond.type = compTag THEN starttag = ABS(cond.tag) IF cond.tag = 1 THEN st.pt = 2 ELSEIF cond.tag = -1 THEN st.pt = 1 ELSEIF cond.tag >= 2 THEN st.pt = 3 ELSE st.pt = 4 END IF ELSE st.pt = int_array_find(compty(), cond.type) IF st.pt = -1 THEN st.pt = 1 'If cond.type is invalid END IF DIM menuopts as MenuOptions menuopts.wide = 13 * 8 ' Minimum width menuopts.calc_size = YES ' Position to draw the menu (calculated next) DIM mpos as XYPair = (60, 0) ' Precompute the menu size calc_menustate_size st, menuopts, mpos.x, mpos.y, vpage ' Calculate screen position of the outer menu ' (Note: MenuState doesn't tell where the menu will be drawn; we have to assume 0,0!) WITH outer_state ' Position the new menu so that the initially selected menu item ' is at the same y position as the current item in the previous menu mpos.y = (.pt - .top) * .spacing - (st.pt - st.top) * st.spacing ' Make sure the new position is fully onscreen (and leave extra space at the bottom of the screen) mpos.y = bound(mpos.y, 0, vpages(vpage)->h - .spacing) IF mpos.y + st.rect.high > vpages(vpage)->h - 15 THEN mpos.y -= st.rect.high - st.spacing END WITH DIM holdpage as integer = allocatepage copypage vpage, holdpage DO setwait 55 setkeys IF keyval(scEsc) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "cond_editor" ' Typing a number could be any type. Select what's under the cursor IF compty(st.pt) <> 0 AND INSTR(getinputtext, ANY "0123456789") > 0 THEN cond.type = compty(st.pt) cond.varnum = 0 'Clear ID so you can type it in cond.editstate = 6 'Editing global ID. Also valid editstate for tags. EXIT DO END IF ' If you start typing a relation, exit to cond_grabber which will handle it ' FIXME: regardless of editstate, the cond_grabber ignores the keypress IF INSTR(getinputtext, ANY "=<>!") > 0 THEN IF cond.type = compTag OR cond.type = compNone THEN cond.type = compEq cond.editstate = 6 EXIT DO END IF ' Exit on TAB so that you simultaneously change to the selected comparison ' and cond_grabber processes the TAB. ' Also, press TAB to select a tag option but skip the tag browser. IF enter_space_click(st) OR keyval(scTab) > 1 THEN IF compty(st.pt) THEN cond.type = compty(st.pt) SELECT CASE st.pt CASE 0: EXIT DO CASE 1: IF default THEN cond.type = compNone ELSE cond.type = compTag cond.tag = -1 END IF CASE 2: IF default = NO THEN cond.type = compNone ELSE cond.type = compTag cond.tag = 1 END IF CASE 3, 4: IF st.pt = 4 THEN starttag *= -1 'tag=OFF IF keyval(scTab) > 1 THEN cond.tag = starttag ELSE cond.tag = tags_menu(starttag, YES, YES) END IF CASE ELSE: 'TODO: global variable browser cond.editstate = 6 'start by entering global variable number END SELECT EXIT DO END IF usemenu st copypage holdpage, vpage edgeboxstyle mpos.x - 5, mpos.y - 5, st.rect.wide + 10, st.rect.high + 10, 2, vpage DIM msg as string IF compty(st.pt) = compNone THEN ELSEIF compty(st.pt) = compTag THEN msg = "ENTER to pick tag/TAB confirm" ELSE msg = "Type expression, TAB to switch" END IF edgeprint "F1 Help " + msg, pLeft, pBottom, uilook(uiText), vpage standardmenu menu(), st, mpos.x, mpos.y, vpage, menuopts setvispage vpage dowait LOOP freepage holdpage END SUB 'Returns a printable representation of a Condition with lots of ${K} colours 'default: the text displayed for a null Condition 'selected: whether this menu item is selected 'wide: max string length to return (not implemented yet) FUNCTION condition_string (cond as Condition, selected as bool, default as string = "Always", wide as integer = 40) as string DIM ret as string = default DIM hlcol as integer = uilook(uiHighlight2) IF selected = NO THEN cond.editstate = 0 cond.lastinput = 0 ELSEIF cond.editstate = 0 THEN ' Set initial edit state to highlight the relevant part IF cond.type = compTag THEN cond.editstate = 1 ELSEIF cond.type <> compNone THEN cond.editstate = 6 ' Initially, the global ID is edited END IF END IF IF cond.type = compNone THEN ELSEIF cond.type = compTag THEN IF cond.tag = 0 THEN ELSE IF cond.editstate = 0 THEN ret = "Tag " & ABS(cond.tag) ELSE ret = "Tag " & hilite(STR(ABS(cond.tag)), hlcol) END IF ret += IIF(cond.tag >= 0, "=ON", "=OFF") IF cond.tag = 1 THEN ret += " [Never]" ELSEIF cond.tag = -1 THEN ret += " [Always]" ELSE ret += " (" & load_tag_name(ABS(cond.tag)) & ")" END IF END IF ELSEIF cond.type >= compEq AND cond.type <= compGe THEN SELECT CASE cond.editstate CASE 0 ret = "Global #" & cond.varnum & " " & comp_strings(cond.type) & " " & cond.value CASE 1 ret = "Global #" & hilite(str(cond.varnum), hlcol) CASE 2 ret = "Global #" & cond.varnum & " " & hilite(comp_strings(cond.type), hlcol) CASE 3 ret = "Global #" & cond.varnum & " " & comp_strings(cond.type) & hilite(" " & cond.value, hlcol) CASE 4 ret = "Global #" & cond.varnum & " " & hilite(comp_strings(cond.type), hlcol) & " " & cond.value CASE 5 'FIXME: a tag for text background colour hasn't been implemented yet ret = "Global #" & cond.varnum & hilite(" ? ", hlcol) & cond.value CASE 6 ret = "Global #" & hilite(cond.varnum & " ", hlcol) & comp_strings(cond.type) & " " & cond.value END SELECT ELSE ret = "[Corrupt condition data]" END IF IF selected THEN ' Provide an indication that you can press Enter ret += "..." END IF RETURN ret END FUNCTION FUNCTION charpicker() as string STATIC pt as integer DIM i as integer DIM f(255) as integer DIM last as integer = -1 DIM linesize as integer DIM offset as XYPair FOR i = 32 TO 255 last = last + 1 f(last) = i NEXT i linesize = 16 offset.x = 160 - (linesize * 9) \ 2 offset.y = 100 - ((last \ linesize) * 9) \ 2 DIM tog as integer = 0 setkeys DO setwait 55 setkeys tog = tog XOR 1 IF keyval(scESC) > 1 THEN setkeys RETURN "" END IF IF keyval(scF1) > 1 THEN show_help "charpicker" IF keyval(scUp) > 1 THEN pt = large(pt - linesize, 0) IF keyval(scDown) > 1 THEN pt = small(pt + linesize, last) IF keyval(scLeft) > 1 THEN pt = large(pt - 1, 0) IF keyval(scRight) > 1 THEN pt = small(pt + 1, last) IF enter_or_space() THEN setkeys RETURN CHR(f(pt)) END IF clearpage dpage FOR i = 0 TO last textcolor uilook(uiMenuItem), uilook(uiDisabledItem) IF (i MOD linesize) = (pt MOD linesize) OR (i \ linesize) = (pt \ linesize) THEN textcolor uilook(uiMenuItem), uilook(uiHighlight) IF pt = i THEN textcolor uilook(uiSelectedItem + tog), 0 printstr CHR(f(i)), offset.x + (i MOD linesize) * 9, offset.y + (i \ linesize) * 9, dpage NEXT i textcolor uilook(uiMenuItem), 0 printstr "ASCII " & f(pt), 78, 190, dpage FOR i = 2 TO 53 IF f(pt) = ASC(key2text(2, i)) THEN printstr "ALT+" + UCASE(key2text(0, i)), 178, 190, dpage IF f(pt) = ASC(key2text(3, i)) THEN printstr "ALT+SHIFT+" + UCASE(key2text(0, i)), 178, 190, dpage NEXT i IF f(pt) = 32 THEN printstr "SPACE", 178, 190, dpage SWAP vpage, dpage setvispage vpage dowait LOOP END FUNCTION 'Return initial representation string for percent_cond_grabber FUNCTION format_percent_cond(byref cond as AttackElementCondition, default as string, byval decimalplaces as integer = 4) as string IF cond.type = compNone THEN RETURN default ELSE RETURN " " + comp_strings(cond.type) + " " + format_percent(cond.value, decimalplaces) END IF END FUNCTION 'This will probably only be used for editing AttackElementConditions, but it more general than that FUNCTION percent_cond_grabber(byref cond as AttackElementCondition, repr as string, default as string, byval min as double, byval max as double, byval decimalplaces as integer = 4) as integer WITH cond DIM intxt as string = getinputtext DIM newtype as integer = .type IF keyval(scDelete) > 1 THEN newtype = compNone 'Listen for comparison operator input FOR i as integer = 1 TO LEN(intxt) DIM inchar as string = MID(intxt, i) IF inchar = "<" THEN newtype = compLt IF inchar = ">" THEN newtype = compGt NEXT IF newtype <> .type THEN IF .type = compNone THEN .value = 0 .type = newtype repr = format_percent_cond(cond, default, decimalplaces) RETURN YES ELSEIF .type = compNone THEN DIM temp as string = "0%" .value = 0 'typing 0 doesn't change the value or repr, workaround IF percent_grabber(.value, temp, min, max, decimalplaces) OR INSTR(intxt, "0") > 0 THEN repr = " < " + temp .type = compLt RETURN YES END IF RETURN NO ELSE 'Trim comparison operator repr = MID(repr, 4) IF keyval(scBackspace) > 1 ANDALSO repr = "0%" THEN repr = default .type = compNone RETURN YES ELSE DIM ret as integer = percent_grabber(.value, repr, min, max, decimalplaces) 'Add back operator repr = " " + comp_strings(.type) + " " + repr RETURN ret END IF END IF END WITH END FUNCTION SUB percent_cond_editor (cond as AttackElementCondition, byval min as double, byval max as double, byval decimalplaces as integer = 4, do_what as string = "...", percent_of_what as string = "") DIM cond_types(2) as integer = {compNone, compLt, compGt} DIM type_num as integer FOR i as integer = 0 TO 2 IF cond.type = cond_types(i) THEN type_num = i NEXT DIM menu(2) as string menu(0) = "Previous Menu" DIM st as MenuState st.size = 18 st.pt = 1 DIM repr as string = format_percent(cond.value, decimalplaces) DO setwait 55 setkeys YES IF keyval(scEsc) > 1 OR enter_space_click(st) THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "percent_cond_editor" SELECT CASE st.pt CASE 1: IF intgrabber(type_num, 0, 2) THEN cond.type = cond_types(type_num) CASE 2: percent_grabber(cond.value, repr, min, max, decimalplaces) END SELECT 'Update IF cond.type = compNone THEN menu(1) = "Condition: Never" IF cond.type = compGt THEN menu(1) = "Condition: " + do_what + " when more than..." IF cond.type = compLt THEN menu(1) = "Condition: " + do_what + " when less than..." menu(2) = "Threshold: " + repr + percent_of_what st.last = IIF(cond.type = compNone, 1, 2) usemenu st clearpage vpage standardmenu menu(), st, 0, 0, vpage setvispage vpage dowait LOOP END SUB 'Whether thinggrabber will enter an editor. FUNCTION enter_or_add_new(state as MenuState) as bool RETURN enter_space_click(state) OR keyval(scPlus) > 1 OR keyval(scInsert) > 1 END FUNCTION 'Returns true if the calling menu needs to refresh. 'Edit an object ID numerically (unless intgrab=NO) or by entering an editor. 'See FnEditor for information about the calling convention. 'This handles enter/space/click, +/insert, digits, left/right, - and backspace. 'offset should be 0 or 1, eg: ' offset=0: datum X means attack X ' offset=1: datum 0 is none, X+1 is attack X 'min is the minimum value of datum. Usually equal to offset if there is no '"none" option, or offset-1 if there is. (Not the same as min for zintgrabber!) 'max_record is the maximum object ID, not the upper range of datum. PRIVATE FUNCTION thinggrabber (byref datum as integer, state as MenuState, offset as integer, min as integer, max_record as integer, intgrab as bool = YES, editor_func as FnEditor) as bool DIM editor_arg as integer 'What to pass to editor_func. IF keyval(scPlus) > 1 OR keyval(scInsert) > 1 THEN editor_arg = max_record + 1 ELSEIF enter_space_click(state) THEN ' A value < 0 would mean None; default to 0 in that case editor_arg = large(datum - offset, 0) ELSE ' Check this only after checking scPlus, because intgrabber reads it IF intgrab THEN IF offset <> 0 AND offset <> 1 THEN showerror "thinggrabber: weird offset " & offset 'min and max for zintgrabber are offset by one: -1 means datum=0 IF (offset = 1 ANDALSO zintgrabber(datum, min - 1, max_record)) OR _ (offset = 0 ANDALSO intgrabber(datum, min, max_record)) THEN RETURN YES END IF END IF RETURN NO END IF DIM newrecord as integer newrecord = editor_func(editor_arg) IF newrecord = -1 THEN RETURN NO 'Cancelled add-new datum = newrecord + offset ' Even if the ID hasn't changed you might have edited the data, so need to refresh the menu RETURN YES END FUNCTION 'Edit an attack ID (possibly offset) numerically or by entering the attack editor. 'See thinggrabber. FUNCTION attackgrabber (byref datum as integer, state as MenuState, offset as integer = 0, min as integer = 0, intgrab as bool = YES) as bool RETURN thinggrabber(datum, state, offset, min, gen(genMaxAttack), intgrab, @attack_editor) END FUNCTION 'Edit an enemy ID (possibly offset) numerically or by entering the enemy editor. 'See thinggrabber. FUNCTION enemygrabber (byref datum as integer, state as MenuState, offset as integer = 0, min as integer = 0, intgrab as bool = YES) as bool RETURN thinggrabber(datum, state, offset, min, gen(genMaxEnemy), intgrab, @enemy_editor) END FUNCTION 'Edit a textbox ID (possibly offset) numerically or by entering the textbox editor. 'See thinggrabber. FUNCTION textboxgrabber (byref datum as integer, state as MenuState, offset as integer = 0, min as integer = 0, intgrab as bool = YES) as bool 'TODO: textbox 0 is never selectable, but ID 0 is None. So this acts a bit weird. RETURN thinggrabber(datum, state, offset, min, gen(genMaxTextbox), intgrab, @text_box_editor) END FUNCTION ' Shared by ui_color_editor and ui_boxstyle_editor SUB make_ui_editor_sample_menu(sample_menu as MenuDef, sample_state as MenuState) ClearMenuData sample_menu WITH sample_menu .alignhoriz = alignRight .alignvert = alignTop .anchorhoriz = alignRight .anchorvert = alignTop .offset.x = -4 .offset.y = 4 END WITH append_menu_item sample_menu, "Sample" append_menu_item sample_menu, "Example" append_menu_item sample_menu, "Disabled" sample_menu.last->disabled = YES sample_state.active = YES init_menu_state sample_state, sample_menu END SUB SUB ui_color_editor(palnum as integer) DIM index as integer DIM default_colors(uiColorLast) as integer DIM default_boxes(uiBoxLast) as BoxStyle DIM sample_menu as MenuDef DIM sample_state as MenuState make_ui_editor_sample_menu sample_menu, sample_state GuessDefaultUIColors master(), default_colors(), default_boxes() LoadUIColors uilook(), boxlook(), palnum DIM color_menu(-1 TO uiColorLast) as string make_ui_color_editor_menu color_menu(), uilook() DIM state as MenuState state.size = 22 state.first = -1 state.top = -1 state.pt = -1 state.last = UBOUND(color_menu) state.autosize_ignore_pixels = 16 setkeys DO setwait 55 setkeys IF keyval(scESC) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "ui_color_editor" IF usemenu(state) THEN IF uilook(uiShadow) = 0 THEN 'This is a hack. Unfortunately ellipse slice border and fill colors can't be 0 as that 'counts as transparent. This is a design mistake in ellipse slices, but is too much trouble to fix uilook(uiShadow) = nearcolor(master(), 0, 1) make_ui_color_editor_menu color_menu(), uilook() END IF END IF index = state.pt IF enter_space_click(state) THEN IF state.pt = -1 THEN EXIT DO ELSE 'Color browser uilook(index) = color_browser_256(uilook(index)) make_ui_color_editor_menu color_menu(), uilook() END IF END IF IF index >= 0 THEN IF intgrabber(uilook(index), 0, 255) THEN make_ui_color_editor_menu color_menu(), uilook() SaveUIColors uilook(), boxlook(), palnum END IF IF keyval(scCtrl) > 0 AND keyval(scD) > 1 THEN ' Ctrl+D uilook(index) = default_colors(index) make_ui_color_editor_menu color_menu(), uilook() SaveUIColors uilook(), boxlook(), palnum END IF END IF '--update sample according to what you have highlighted sample_state.pt = 0 SELECT CASE index CASE 5,6 ' selected disabled sample_state.pt = 2 END SELECT '--draw screen clearpage dpage draw_menu sample_menu, sample_state, dpage standardmenu color_menu(), state, 10, 0, dpage FOR i as integer = large(state.top, 0) TO small(state.top + state.size, state.last) rectangle 0, 8 * (i - state.top), 8, 8, uilook(i), dpage NEXT i DIM msg as string = "Ctrl+D to revert to default" edgeprint msg, pCentered, pBottom, uilook(uiText), dpage SWAP vpage, dpage setvispage vpage dowait LOOP SaveUIColors uilook(), boxlook(), palnum ClearMenuData sample_menu END SUB SUB make_ui_color_editor_menu(m() as string, colors() as integer) m(-1) = "Previous Menu" FOR i as integer = 0 TO uiColorLast m(i) = UiColorCaption(i) & ": " & colors(i) NEXT i END SUB SUB ui_boxstyle_editor(palnum as integer) DIM index as integer DIM kind as integer DIM default_colors(uiColorLast) as integer DIM default_boxes(uiBoxLast) as BoxStyle DIM sample_menu as MenuDef DIM sample_state as MenuState make_ui_editor_sample_menu sample_menu, sample_state GuessDefaultUIColors master(), default_colors(), default_boxes() LoadUIColors uilook(), boxlook(), palnum DIM color_menu(-1 TO uiBoxLast * 3 + 2) as string make_ui_boxstyle_editor_menu color_menu(), boxlook() DIM state as MenuState state.size = 22 state.top = -1 state.pt = -1 state.first = -1 state.last = UBOUND(color_menu) state.autosize_ignore_pixels = 16 setkeys DO setwait 55 setkeys IF keyval(scESC) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "ui_boxstyle_editor" IF usemenu(state) THEN END IF index = INT(state.pt / 3) kind = state.pt MOD 3 ' 0=bgcol, 1=edgecol, 2=border IF enter_space_click(state) THEN IF state.pt = -1 THEN EXIT DO ELSEIF kind = 0 THEN 'Color browser for bgcol boxlook(index).bgcol = color_browser_256(boxlook(index).bgcol) make_ui_boxstyle_editor_menu color_menu(), boxlook() ELSEIF kind = 1 THEN 'Color browser for edgecol boxlook(index).edgecol = color_browser_256(boxlook(index).edgecol) make_ui_boxstyle_editor_menu color_menu(), boxlook() END IF END IF IF index >= 0 THEN SELECT CASE kind CASE 0: IF intgrabber(boxlook(index).bgcol, 0, 255) THEN make_ui_boxstyle_editor_menu color_menu(), boxlook() SaveUIColors uilook(), boxlook(), palnum END IF CASE 1: IF intgrabber(boxlook(index).edgecol, 0, 255) THEN make_ui_boxstyle_editor_menu color_menu(), boxlook() SaveUIColors uilook(), boxlook(), palnum END IF CASE 2: IF zintgrabber(boxlook(index).border, -1, gen(genMaxBoxBorder)) THEN make_ui_boxstyle_editor_menu color_menu(), boxlook() SaveUIColors uilook(), boxlook(), palnum END IF END SELECT END IF IF index >= 0 THEN IF keyval(scCtrl) > 0 AND keyval(scD) > 1 THEN ' Ctrl+D SELECT CASE kind CASE 0: boxlook(index).bgcol = default_boxes(index).bgcol make_ui_boxstyle_editor_menu color_menu(), boxlook() SaveUIColors uilook(), boxlook(), palnum CASE 1: boxlook(index).edgecol = default_boxes(index).edgecol make_ui_boxstyle_editor_menu color_menu(), boxlook() SaveUIColors uilook(), boxlook(), palnum CASE 2: boxlook(index).border = default_boxes(index).border make_ui_boxstyle_editor_menu color_menu(), boxlook() SaveUIColors uilook(), boxlook(), palnum END SELECT END IF END IF '--update sample according to what you have highlighted sample_menu.boxstyle = 0 sample_state.pt = 0 IF index >= 0 THEN sample_menu.boxstyle = index END IF '--draw screen clearpage dpage draw_menu sample_menu, sample_state, dpage standardmenu color_menu(), state, 10, 0, dpage FOR i as integer = large(state.top, 0) TO small(state.top + state.size, state.last) IF i MOD 3 = 0 THEN rectangle 0, 8 * (i - state.top), 8, 8, boxlook(INT(i / 3)).bgcol, dpage END IF IF i MOD 3 = 1 THEN rectangle 0, 8 * (i - state.top), 8, 8, boxlook(INT(i / 3)).edgecol, dpage END IF NEXT i DIM msg as string = "Ctrl+D to revert to default" edgeprint msg, pCentered, pBottom, uilook(uiText), dpage SWAP vpage, dpage setvispage vpage dowait LOOP SaveUIColors uilook(), boxlook(), palnum ClearMenuData sample_menu END SUB SUB make_ui_boxstyle_editor_menu(m() as string, boxes() as BoxStyle) m(-1) = "Previous Menu" FOR i as integer = 0 TO uiBoxLast m(i * 3) = "Box style " & i & " color: " & boxes(i).bgcol m(i * 3 + 1) = "Box style " & i & " edge: " & boxes(i).edgecol m(i * 3 + 2) = "Box style " & i & " border image: " & defaultint(boxes(i).border - 1, "none", -1) NEXT i END SUB FUNCTION pick_ogg_quality(byref quality as integer) as integer STATIC q as integer = 2 DIM i as integer DIM descrip as string DIM page as integer = compatpage setkeys DO setwait 55 setkeys IF keyval(scESC) > 1 THEN freepage page RETURN -1 'cancelled END IF IF keyval(scF1) > 1 THEN show_help "pick_ogg_quality" IF enter_or_space() THEN EXIT DO intgrabber (q, -1, 10) clearpage page centerbox 160, 105, 300, 54, 4, page edgeprint "Pick Ogg quality level (" & q & ")", 64, 86, uilook(uiText), page FOR i = 0 TO q + 1 rectangle 30 + 21 * i, 100, 20, 16, uilook(uiText), page NEXT i SELECT CASE q CASE -1: descrip = "scratchy, smallest" CASE 0: descrip = "not too bad, very small" CASE 1: descrip = "pretty good, quite small" CASE 2: descrip = "good, pretty small" CASE 3: descrip = "good, smallish" CASE 4: descrip = "good, medium sized" CASE 5: descrip = "good, biggish" CASE 6: descrip = "better than you need, big" CASE 7: descrip = "much better than you need, too big" CASE 8: descrip = "excessive, wasteful" CASE 9: descrip = "very excessive, very wasteful" CASE 10: descrip = "flagrantly excessive and wasteful" END SELECT edgeprint descrip, pCentered, 118, uilook(uiText), page setvispage vpage dowait LOOP freepage page quality = q RETURN 0 END FUNCTION FUNCTION needaddset (byref pt as integer, byref check as integer, what as string) as integer IF pt <= check THEN RETURN NO IF yesno("Add new " & what & "?") THEN check += 1 RETURN YES ELSE pt -= 1 END IF RETURN NO END FUNCTION 'This is intgrabber, and if the 'more' key is pressed when pt=max, asks whether to 'add a new set. DOES NOT INCREMENT max. Check whether pt > max to see whether this 'needs to be handled. 'maxmax is max value of max, of course FUNCTION intgrabber_with_addset(byref pt as integer, byval min as integer, byval max as integer, byval maxmax as integer=32767, what as string, byval less as integer=scLeft, byval more as integer=scRight) as integer IF keyval(more) > 1 AND pt = max AND max < maxmax THEN IF yesno("Add new " & what & "?") THEN pt += 1 RETURN YES END IF RETURN NO ELSE IF less = scUp ANDALSO more = scDown THEN 'special case to work around a strange bug in mac os x sprite brows menu arrow key handling RETURN intgrabber(pt, min, max, scComma, scPeriod, NO, NO) ELSE RETURN intgrabber(pt, min, max, less, more, NO, NO) END IF END IF END FUNCTION FUNCTION load_vehicle_name(vehID as integer) as string IF vehID < 0 OR vehID > gen(genMaxVehicle) THEN RETURN "" DIM vehicle as VehicleData LoadVehicle game + ".veh", vehicle, vehID RETURN vehicle.name END FUNCTION FUNCTION load_item_name (it as integer, hidden as integer, offbyone as integer) as string 'it - the item number 'hidden - whether to *not* prefix the item number 'offbyone - whether it is the item number (1), or the itemnumber + 1 (0) IF it <= 0 AND offbyone = NO THEN RETURN "NONE" DIM itn as integer IF offbyone THEN itn = it ELSE itn = it - 1 DIM result as string = readitemname(itn) IF hidden = 0 THEN result = itn & " " & result RETURN result END FUNCTION ' maxwidth is in pixels FUNCTION textbox_preview_line(boxnum as integer, maxwidth as integer = 700) as string IF boxnum <= 0 OR boxnum > gen(genMaxTextBox) THEN RETURN "" DIM box as TextBox LoadTextBox box, boxnum RETURN textbox_preview_line(box, maxwidth) END FUNCTION FUNCTION textbox_preview_line(box as TextBox, maxwidth as integer = 700) as string DIM ret as string FOR i as integer = 0 TO UBOUND(box.text) IF LEN(ret) THEN ret &= " " ret &= TRIM(box.text(i)) IF textwidth(ret) >= maxwidth THEN EXIT FOR NEXT i ret = RTRIM(ret) ' Remove spaces due to trailing blank lines RETURN shorten_to_right(ret, maxwidth) END FUNCTION ' Toggle tagnum between 0 and an unused onetime tag ID (2 to max_onetime) ' This only checks NPC definitions that have already been written ' to disk, so that should be done so before calling SUB onetimetog(byref tagnum as integer) IF tagnum > 0 THEN tagnum = 0 EXIT SUB END IF DIM onetimeusage(max_onetime \ 16) as integer check_used_onetime_npcs onetimeusage() 'Marks bits 0 and 1 as used DIM tried as integer = 0 DIM i as integer = gen(genOneTimeNPC) DO IF i > max_onetime THEN i = 2 IF readbit(onetimeusage(), 0, i) = NO THEN EXIT DO IF tried = max_onetime THEN visible_debug "All onetime usage tags have been used up! Do you really have 16000+ NPCs? This is probably an engine bug!" tagnum = 0 EXIT SUB END IF i += 1 tried += 1 LOOP tagnum = i gen(genOneTimeNPC) = i END SUB FUNCTION pal16browse (byval curpal as integer, byval picset as SpriteType, byval picnum as integer) as integer DIM buf(7) as integer DIM sprite(9) as Frame ptr DIM pal16(9) as Palette16 ptr DIM as integer i, o, j, k DIM c as integer DIM state as MenuState state.need_update = YES state.top = curpal - 1 state.first = -1 state.size = 9 '--Find last palette which is not blank loadrecord buf(), game + ".pal", 8, 0 FOR i = buf(1) TO 0 STEP -1 state.last = i loadrecord buf(), game + ".pal", 8, 1 + i FOR j = 0 TO 7 IF buf(j) <> 0 THEN EXIT FOR, FOR NEXT j NEXT i 'We actually want to be able to browse past the last palette to the first empty palette state.last += 1 state.pt = bound(curpal, 0, state.last) correct_menu_state state setkeys DO setwait 55 setkeys state.tog = state.tog XOR 1 IF keyval(scESC) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "pal16browse" IF usemenu(state) THEN state.need_update = YES DIM temppt as integer = large(state.pt, 0) IF intgrabber(temppt, 0, state.last, , , YES) THEN state.pt = temppt correct_menu_state state state.need_update = YES END IF IF enter_space_click(state) THEN IF state.pt >= 0 THEN curpal = state.pt EXIT DO END IF IF state.need_update THEN state.need_update = NO FOR i = 0 TO 9 frame_unload @sprite(i) palette16_unload @pal16(i) sprite(i) = frame_load(picset, picnum) ' Pass default_blank = NO, to avoid debug spam if picnum is off the end. ' (This isn't strictly necessary) IF state.top + i <= gen(genMaxPal) THEN pal16(i) = palette16_load(state.top + i, picset, picnum, NO) NEXT i END IF '--Draw screen clearpage dpage FOR i = 0 TO 9 textcolor uilook(uiMenuItem), 0 IF state.top + i = state.pt THEN textcolor uilook(uiSelectedItem + state.tog), 0 SELECT CASE state.top + i CASE IS >= 0 o = LEN(" " & (state.top + i)) * 8 IF state.top + i = state.pt THEN edgebox o - 1, 1 + i * 20, 114, 18, uilook(uiBackground), uilook(uiMenuitem), dpage END IF FOR j = 0 TO 15 IF pal16(i) THEN c = pal16(i)->col(j) rectangle o + j * 7, 2 + i * 20, 5, 16, c, dpage END IF NEXT j IF state.top + i <> state.pt THEN IF pal16(i) THEN WITH sprite_sizes(picset) FOR k = 0 TO .frames - 1 frame_draw sprite(i) + k, pal16(i), o + 140 + (k * .size.x), i * 20 - (.size.y \ 2 - 10), 1, YES, dpage NEXT k END WITH END IF END IF printstr "" & (state.top + i), 4, 5 + i * 20, dpage CASE ELSE printstr "Cancel", 4, 5 + i * 20, dpage END SELECT NEXT i IF state.pt >= 0 THEN '--write current pic on top i = state.pt - state.top o = LEN(" " & state.pt) * 8 IF pal16(i) THEN WITH sprite_sizes(picset) FOR k = 0 TO .frames - 1 frame_draw sprite(i) + k, pal16(i), o + 130 + (k * .size.x), i * 20 - (.size.y \ 2 - 10), 1, YES, dpage NEXT k END WITH END IF END IF SWAP vpage, dpage setvispage vpage dowait LOOP FOR i = 0 TO 9 frame_unload @sprite(i) palette16_unload @pal16(i) NEXT RETURN curpal END FUNCTION ' Number of steps before a random formation triggers FUNCTION step_estimate(freq as integer, low as integer, high as integer, infix as string="-", suffix as string= "", zero as string="never") as string IF freq = 0 THEN RETURN zero ' Round upwards to get the actual number steps required DIM low_est as integer = (low + freq - 1) \ freq DIM high_est as integer = (high + freq - 1) \ freq ' This average is just an estimate. Add 0.5 due to the rounding-up behaviour. DIM average as double = (high + low) / 2 / freq + 0.5 RETURN low_est & infix & high_est & suffix & ", avg~" & format(average, "0.0") END FUNCTION FUNCTION speed_estimate(speed as integer, suffix as string=" seconds", zero as string="infinity") as string IF speed = 0 THEN RETURN zero DIM ticks as integer = INT(1000 / speed) DIM result as string result = STR(INT(ticks * 10 \ 18) / 10) 'Special case for dumb floating point math freak-outs WHILE INSTR(result, ".") AND RIGHT(result, 2) = "99" result = LEFT(result, LEN(result) - 1) WEND RETURN result & suffix END FUNCTION FUNCTION seconds_estimate(ticks as integer) as string IF ticks = 0 THEN RETURN "0.0" DIM sec as double sec = ticks * (1 / ideal_ticks_per_second()) DIM s as string = STR(sec) DIM dot as integer = INSTR(s, ".") DIM prefix as string = LEFT(s, dot - 1) DIM suffix as string = MID(s, dot + 1, 2) WHILE LEN(suffix) > 1 ANDALSO RIGHT(suffix, 1) = "0" suffix = LEFT(suffix, LEN(suffix) - 1) WEND RETURN prefix & "." & suffix END FUNCTION SUB load_text_box_portrait (byref box as TextBox, byref gfx as GraphicPair) 'NOTE: Compare this to the portrait loading code in game.bas:init_text_box_slices() 'If you update this here, you might need to update that one too! DIM img_id as integer = -1 DIM pal_id as integer = -1 DIM her as HeroDef unload_sprite_and_pal gfx WITH gfx SELECT CASE box.portrait_type CASE 1' Fixed ID number img_id = box.portrait_id pal_id = box.portrait_pal CASE 2' Hero by caterpillar 'In custom, no party exists, so preview using the first hero loadherodata her, 0 img_id = her.portrait pal_id = her.portrait_pal CASE 3' Hero by party slot 'In custom, no party exists, so preview using the first hero loadherodata her, 0 img_id = her.portrait pal_id = her.portrait_pal CASE 4' Hero by ID loadherodata her, box.portrait_id img_id = her.portrait pal_id = her.portrait_pal END SELECT IF img_id >= 0 THEN load_sprite_and_pal gfx, sprTypePortrait, img_id, pal_id END IF END WITH END SUB 'This is basically a reimplementation of editbitsets, but bools instead of packed bits. 'Currently specific to export_textboxes, but could be generalised. FUNCTION askwhatmetadata (metadata() as bool, metadatalabels() as string) as bool DIM tog as integer DIM state as MenuState state.size = UBOUND(metadata) + 1 state.first = -1 state.last = UBOUND(metadata) state.top = -1 state.pt = -1 setkeys DO setwait 55 setkeys usemenu state tog = tog XOR 1 IF keyval(scESC) > 1 THEN RETURN NO IF keyval(scF1) > 1 THEN show_help "textbox_export_askwhatmetadata" IF enter_space_click(state) THEN IF state.pt = -1 THEN RETURN YES metadata(state.pt) XOR= YES END IF clearpage dpage textcolor uilook(uiText), 0 printstr "Choose what metadata to include:", 4, 4, dpage IF state.pt <> -1 THEN textcolor uilook(uiText), 0 ELSE textcolor uilook(uiSelectedItem + tog), 1 printstr "Done", 4, 4 + 9, dpage FOR i as integer = 0 TO UBOUND(metadatalabels) IF state.pt = i THEN IF metadata(i) = YES THEN textcolor uilook(uiSelectedItem + tog), 1 ELSE textcolor uilook(uiSelectedDisabled), 1 ELSE IF metadata(i) = YES THEN textcolor uilook(uiText), 0 ELSE textcolor uilook(uiDisabledItem), 0 END IF printstr metadatalabels(i), 4, 4 + 18 + i * 9, dpage NEXT SWAP vpage, dpage setvispage vpage dowait LOOP END FUNCTION FUNCTION str2bool(q as string, default as integer = NO, invert as bool = NO) as integer IF LCASE(LEFT(TRIM(q), 3)) = "yes" THEN IF invert THEN RETURN NO ELSE RETURN YES END IF IF LCASE(LEFT(TRIM(q), 2)) = "no" THEN IF invert THEN RETURN YES ELSE RETURN NO END IF RETURN default END FUNCTION SUB xy_position_on_slice (sl as Slice Ptr, byref x as integer, byref y as integer, caption as string, helpkey as string) DIM col as integer DIM tog as integer DIM root as Slice Ptr setkeys DO setwait 55 setkeys tog = tog XOR 1 IF keyval(scEsc) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help helpkey IF enter_or_space() THEN EXIT DO IF keyval(scLeft) > 0 THEN x -= 1 IF keyval(scRight) > 0 THEN x += 1 IF keyval(scUp) > 0 THEN y -= 1 IF keyval(scDown) > 0 THEN y += 1 clearpage dpage DrawSlice sl, dpage col = uilook(uiBackground) IF tog = 0 THEN col = uilook(uiSelectedItem) rectangle sl->ScreenX + x - 2, sl->ScreenY + y, 2, 2, col, dpage rectangle sl->ScreenX + x + 2, sl->ScreenY + y, 2, 2, col, dpage rectangle sl->ScreenX + x, sl->ScreenY + y - 2, 2, 2, col, dpage rectangle sl->ScreenX + x, sl->ScreenY + y + 2, 2, 2, col, dpage wrapprint caption, pCentered, 0, uilook(uiText), dpage edgeprint "Position point and press Enter or SPACE", 0, pBottom, uilook(uiText), dpage SWAP vpage, dpage setvispage vpage dowait LOOP END SUB SUB xy_position_on_sprite (spr as GraphicPair, byref x as integer, byref y as integer, byval frame as integer, byval wide as integer, byval high as integer, caption as string, helpkey as string) DIM col as integer DIM tog as integer setkeys DO setwait 55 setkeys tog = tog XOR 1 IF keyval(scEsc) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help helpkey IF enter_or_space() THEN EXIT DO IF keyval(scLeft) > 0 THEN x -= 1 IF keyval(scRight) > 0 THEN x += 1 IF keyval(scUp) > 0 THEN y -= 1 IF keyval(scDown) > 0 THEN y += 1 clearpage dpage drawbox rCenter - wide, rCenter - high, wide * 2, high * 2, uilook(uiSelectedDisabled), 1, dpage frame_draw spr.sprite + frame, spr.pal, rCenter - wide, rCenter - high, 2,, dpage col = uilook(uiBackground) IF tog = 0 THEN col = uilook(uiSelectedItem) rectangle rCenter - wide + x * 2 - 2, rCenter - high + y * 2, 2, 2, col, dpage rectangle rCenter - wide + x * 2 + 2, rCenter - high + y * 2, 2, 2, col, dpage rectangle rCenter - wide + x * 2, rCenter - high + y * 2 - 2, 2, 2, col, dpage rectangle rCenter - wide + x * 2, rCenter - high + y * 2 + 2, 2, 2, col, dpage wrapprint caption, pCentered, 0, uilook(uiText), dpage edgeprint "Position point and press Enter or SPACE", 0, pBottom, uilook(uiText), dpage SWAP vpage, dpage setvispage vpage dowait LOOP END SUB 'Returns "prefix ABS(n) suffix [AUTOSET] ()" 'where everything except the ABS(n) is optional. PRIVATE FUNCTION base_tag_caption(byval n as integer, prefix as string, suffix as string, zerocap as string, onecap as string, negonecap as string, byval allowspecial as integer) as string DIM ret as string ret = prefix IF LEN(ret) > 0 THEN ret &= " " ret &= ABS(n) & suffix IF allowspecial <> YES ANDALSO tag_is_autoset(n) THEN ret &= " [AUTOSET]" 'Append " ($cap)" DIM cap as string cap = load_tag_name(n) IF n = 0 AND LEN(zerocap) > 0 THEN cap = zerocap IF n = 1 AND LEN(onecap) > 0 THEN cap = onecap IF n = -1 AND LEN(negonecap) > 0 THEN cap = negonecap cap = TRIM(cap) IF LEN(cap) > 0 THEN ret &= " (" & cap & ")" RETURN ret END FUNCTION FUNCTION tag_toggle_caption(byval n as integer, prefix as string="Toggle tag", byval allowspecial as integer=NO) as string RETURN base_tag_caption(n, prefix, "", "N/A", "Unchangeable", "Unchangeable", allowspecial) END FUNCTION FUNCTION tag_choice_caption(byval n as integer, prefix as string="", byval allowspecial as integer=NO) as string RETURN base_tag_caption(n, prefix, "", "None", "Unchangeable", "Unchangeable", allowspecial) END FUNCTION FUNCTION tag_set_caption(byval n as integer, prefix as string="Set Tag", byval allowspecial as integer=NO) as string RETURN base_tag_caption(n, prefix, "=" & onoroff(n), "No tag set", "Unchangeable", "Unchangeable", allowspecial) END FUNCTION ' Note that this similar to textbox_condition[_short]_caption and describe_tag_condition. Sorry! FUNCTION tag_condition_caption(byval n as integer, prefix as string="Tag", zerocap as string, onecap as string="Never", negonecap as string="Always") as string RETURN base_tag_caption(n, prefix, "=" & onoroff(n), zerocap, onecap, negonecap, YES) END FUNCTION 'Describe a condition which checks two tags (both conditions need to pass) 'zerovalue: meaning of 0. true is always, false is never FUNCTION describe_two_tag_condition(prefix as string, truetext as string, falsetext as string, byval zerovalue as bool, byval tag1 as integer, byval tag2 as integer) as string DIM ret as string = prefix DIM true_count as integer DIM false_count as integer IF tag1 = 0 THEN tag1 = IIF(zerovalue, -1, 1) IF tag2 = 0 THEN tag2 = IIF(zerovalue, -1, 1) IF tag1 = 1 THEN false_count += 1 ELSEIF tag1 = -1 THEN true_count += 1 ELSE ret &= " tag " & ABS(tag1) & " = " & onoroff(tag1) END IF IF tag2 = 1 THEN false_count += 1 ELSEIF tag2 = -1 THEN true_count += 1 ELSE IF true_count = 0 AND false_count = 0 THEN ret &= " and" ret &= " tag " & ABS(tag2) & " = " & onoroff(tag2) END IF IF true_count = 2 THEN ret = truetext IF false_count > 0 THEN ret = falsetext RETURN ret END FUNCTION FUNCTION sublist (s() as string, helpkey as string="", byval x as integer=0, byval y as integer=0, byval page as integer=-1) as integer DIM state as MenuState state.pt = 0 state.last = UBOUND(s) state.size = 22 DIM holdscreen as integer holdscreen = allocatepage IF page > -1 THEN copypage page, holdscreen ELSE clearpage holdscreen END IF setkeys YES DO setwait 55 setkeys YES usemenu state IF keyval(scESC) > 1 THEN sublist = -1 EXIT DO END IF IF keyval(scF1) > 1 AND helpkey <> "" THEN show_help helpkey IF enter_space_click(state) THEN sublist = state.pt EXIT DO END IF copypage holdscreen, vpage standardmenu s(), state, x, y, vpage setvispage vpage dowait LOOP END FUNCTION 'The maximum - 1 number of global text strings that can appear in the global 'text strings menu (the actual number varies) CONST GTSnumitems = 209 TYPE GlobalTextStringsMenu index(-1 TO GTSnumitems) as integer description(-1 TO GTSnumitems) as string shaded(-1 TO GTSnumitems) as bool text(-1 TO GTSnumitems) as string maxlen(GTSnumitems) as integer help(GTSnumitems) as string curitem as integer END TYPE PRIVATE SUB GTS_add_to_menu (menu as GlobalTextStringsMenu, description as string, byval index as integer, default as string, byval maxlen as integer, helpfile as string = "") WITH menu IF .curitem > GTSnumitems THEN fatalerror "GlobalTextStringsMenu.curitem too large" .index(.curitem) = index .description(.curitem) = description .text(.curitem) = readglobalstring(index, default, maxlen) .maxlen(.curitem) = maxlen IF LEN(helpfile) THEN .help(.curitem) = "globalstring_" + helpfile .curitem += 1 END WITH END SUB PRIVATE SUB GTS_menu_header (menu as GlobalTextStringsMenu, description as string) WITH menu 'IF .curitem > -1 THEN .curitem += 1 IF .curitem > GTSnumitems THEN fatalerror "GlobalTextStringsMenu.curitem too large" .shaded(.curitem) = YES .description(.curitem) = description .curitem += 1 END WITH END SUB SUB edit_global_text_strings() DIM search as string = "" DIM state as MenuState DIM menuopts as MenuOptions DIM menu as GlobalTextStringsMenu '--load current names 'getelementnames handles the double-defaulting of element names DIM elementnames() as string getelementnames elementnames() FOR i as integer = -1 TO UBOUND(menu.index) 'initialize unused menu items to -1 because if you leave them at 0 'they collide with HP menu.index(i) = -1 NEXT i menu.description(-1) = "Back to Previous Menu" GTS_menu_header menu, "Stats:" GTS_add_to_menu menu, "Health Points", 0, "HP", 10 GTS_add_to_menu menu, "Spell Points", 1, "MP", 10 GTS_add_to_menu menu, "Attack Power", 2, "Attack", 10 GTS_add_to_menu menu, "Accuracy", 3, "Accuracy", 10 GTS_add_to_menu menu, "Extra Hits", 4, "Hits", 10 GTS_add_to_menu menu, "Blocking Power", 5, "Blocking", 10 GTS_add_to_menu menu, "Dodge Rate", 6, "Dodge", 10 GTS_add_to_menu menu, "Counter Rate", 7, "Counter", 10 GTS_add_to_menu menu, "Speed", 8, "Speed", 10 GTS_add_to_menu menu, "Spell Skill", 29, "SpellSkill", 10 GTS_add_to_menu menu, "Spell Block", 30, "SpellBlock", 10 GTS_add_to_menu menu, "Spell cost %", 31, "SpellCost%", 10 GTS_menu_header menu, "Elements:" FOR i as integer = 0 TO gen(genNumElements) - 1 GTS_add_to_menu menu, "Elemental " & i, 174 + i*2, elementnames(i), 14 NEXT i GTS_menu_header menu, "Equip slots:" GTS_add_to_menu menu, "Weapon", 38, "Weapon", 10 FOR i as integer = 1 TO 4 GTS_add_to_menu menu, "Armor " & i, 24 + i, "Armor " & i, 10 NEXT i GTS_menu_header menu, "Special Menu Item Default Captions:" GTS_add_to_menu menu, "Items", 60, "Items", 10 GTS_add_to_menu menu, "Spells", 61, "Spells", 10 GTS_add_to_menu menu, "Status", 62, "Status", 10 GTS_add_to_menu menu, "Equip", 63, "Equip", 10 GTS_add_to_menu menu, "Order", 64, "Order", 10 GTS_add_to_menu menu, "Team", 65, "Team", 10 GTS_add_to_menu menu, "Save", 66, "Save", 10 GTS_add_to_menu menu, "Load", 322, "Load", 20 GTS_add_to_menu menu, "Quit", 67, "Quit", 10 GTS_add_to_menu menu, "Minimap", 68, "Map", 10 GTS_add_to_menu menu, "Volume Menu [obsolete]", 69, "Volume", 10 'Obsolete GTS_add_to_menu menu, "Music Volume", 318, "Music", 20 GTS_add_to_menu menu, "Sound Volume", 320, "Sound", 20 GTS_add_to_menu menu, "Margins", 308, "Margins", 10 GTS_add_to_menu menu, "Purchases", 313, "Purchases", 10 GTS_add_to_menu menu, "Switch to windowed", 314, "Windowed", 20 GTS_add_to_menu menu, "Switch to fullscreen", 316, "Fullscreen", 20 GTS_menu_header menu, "Item Menu:" GTS_add_to_menu menu, "Exit Item Menu", 35, "DONE", 10 GTS_add_to_menu menu, "Sort Item Menu", 36, "AUTOSORT", 10 GTS_add_to_menu menu, "Drop Item", 37, "TRASH", 10 GTS_add_to_menu menu, "Drop Prompt", 41, "Discard", 10 GTS_add_to_menu menu, "Negative Drop Prefix", 42, "Cannot", 10 GTS_menu_header menu, "Status Main Screen:" GTS_add_to_menu menu, "Level", 43, "Level", 10 GTS_add_to_menu menu, "Experience", 33, "Experience", 10 GTS_add_to_menu menu, "(exp) for next (level)", 47, "for next", 10 GTS_add_to_menu menu, "Money", 32, "Money", 10 GTS_add_to_menu menu, "Level MP", 160, "Level MP", 20 GTS_menu_header menu, "Status Second Screen:" GTS_add_to_menu menu, "Elemental Effects Title", 302, "Elemental Effects:", 30 GTS_add_to_menu menu, "No Elemental Effects", 130, "No Elemental Effects", 30 GTS_add_to_menu menu, "Takes > 100% element dmg", 162, "Weak to $E", 25, "elemental_resist" GTS_add_to_menu menu, "Takes 0 to 100% element dmg",165, "Strong to $E", 25, "elemental_resist" GTS_add_to_menu menu, "Takes 0% element dmg", 168, "Immune to $E", 25, "elemental_resist" GTS_add_to_menu menu, "Takes < 0% element dmg", 171, "Absorb $E", 25, "elemental_resist" GTS_menu_header menu, "Equip Menu:" GTS_add_to_menu menu, "Equip Nothing (unequip)", 110, "Nothing", 10 GTS_add_to_menu menu, "Unequip All", 39, "-REMOVE-", 8 GTS_add_to_menu menu, "Exit Equip", 40, "-EXIT-", 8 GTS_menu_header menu, "Spells Menu:" GTS_add_to_menu menu, "(hero) has no spells", 133, "has no spells", 20 GTS_add_to_menu menu, "Exit Spell List Menu", 46, "Exit", 10 GTS_add_to_menu menu, "Cancel Spell Menu", 51, "(CANCEL)", 10 GTS_menu_header menu, "Team/Order Menu:" GTS_add_to_menu menu, "Remove Hero from Team", 48, "REMOVE", 10 GTS_menu_header menu, "Save/Load Menus:" GTS_add_to_menu menu, "New Game", 52, "NEW GAME", 10 GTS_add_to_menu menu, "Exit Game", 53, "EXIT", 10 GTS_add_to_menu menu, "Cancel Save", 59, "CANCEL", 10 GTS_add_to_menu menu, "Replace Save Prompt", 102, "Replace Old Data?", 20 GTS_add_to_menu menu, "Overwrite Save Yes", 44, "Yes", 10 GTS_add_to_menu menu, "Overwrite Save No", 45, "No", 10 GTS_add_to_menu menu, "day", 154, "day", 10 GTS_add_to_menu menu, "days", 155, "days", 10 GTS_add_to_menu menu, "hour", 156, "hour", 10 GTS_add_to_menu menu, "hours", 157, "hours", 10 GTS_add_to_menu menu, "minute", 158, "minute", 10 GTS_add_to_menu menu, "minutes", 159, "minutes", 10 GTS_menu_header menu, "Quit Playing Prompt:" GTS_add_to_menu menu, "Prompt", 55, "Quit Playing?", 20 GTS_add_to_menu menu, "Yes", 57, "Yes", 10 GTS_add_to_menu menu, "No", 58, "No", 10 GTS_menu_header menu, "Shop Menu:" GTS_add_to_menu menu, "Buy", 70, "Buy", 10 GTS_add_to_menu menu, "Sell", 71, "Sell", 10 GTS_add_to_menu menu, "Inn", 72, "Inn", 10 GTS_add_to_menu menu, "Hire", 73, "Hire", 10 GTS_add_to_menu menu, "Exit", 74, "Exit", 10 GTS_add_to_menu menu, "You own (itemcount)", 324, "You own", 20 GTS_add_to_menu menu, "Equipped (itemcount)", 326, "Equipped", 20 GTS_menu_header menu, "Buy/Hire Menu:" GTS_add_to_menu menu, "Buy trade prefix", 85, "Trade for", 20 GTS_add_to_menu menu, "($) and a (item)", 81, "and a", 10 GTS_add_to_menu menu, "($) and (number) (item)", 153, "and", 10 GTS_add_to_menu menu, "Hire price prefix", 87, "Joins for", 20 GTS_add_to_menu menu, "(#) in stock", 97, "in stock", 20 GTS_add_to_menu menu, "Equipability prefix", 99, "Equip:", 10 GTS_add_to_menu menu, "Cannot buy prefix", 89, "Cannot Afford", 20 GTS_add_to_menu menu, "Cannot hire prefix", 91, "Cannot Hire", 20 GTS_add_to_menu menu, "Inventory full warning", 305, "No room in inventory", 30 GTS_add_to_menu menu, "Party full warning", 100, "No Room In Party", 20 GTS_add_to_menu menu, "Buy alert", 93, "Purchased", 20 GTS_add_to_menu menu, "Hire alert (suffix)", 95, "Joined!", 20 GTS_add_to_menu menu, "The shop is empty", 309, "The shop is empty", 30 GTS_menu_header menu, "Sell Menu:" GTS_add_to_menu menu, "Unsellable item warning", 75, "CANNOT SELL", 20 GTS_add_to_menu menu, "Sell value prefix", 77, "Worth", 20 GTS_add_to_menu menu, "Sell trade prefix", 79, "Trade for", 20 GTS_add_to_menu menu, "Worthless item warning", 82, "Worth Nothing", 20 GTS_add_to_menu menu, "Sell alert", 84, "Sold", 10 GTS_menu_header menu, "Inns:" GTS_add_to_menu menu, "THE INN COSTS (# gold)", 143, "THE INN COSTS", 20 GTS_add_to_menu menu, "You have (# gold)", 145, "You have", 20 GTS_add_to_menu menu, "Pay at Inn", 49, "Pay", 10 GTS_add_to_menu menu, "Cancel Inn", 50, "Cancel", 10 GTS_menu_header menu, "Battles:" GTS_add_to_menu menu, "Battle Item Menu", 34, "Item", 10 GTS_add_to_menu menu, "Stole (itemname)", 117, "Stole", 30 GTS_add_to_menu menu, "Nothing to Steal", 111, "Has Nothing", 30 GTS_add_to_menu menu, "Steal Failure", 114, "Cannot Steal", 30 GTS_add_to_menu menu, "When an Attack Misses", 120, "miss", 20 GTS_add_to_menu menu, "When a Spell Fails", 122, "fail", 20 GTS_add_to_menu menu, "CANNOT RUN!", 147, "CANNOT RUN!", 20 GTS_add_to_menu menu, "Pause", 54, "PAUSE", 10 GTS_add_to_menu menu, "Gained (experience)", 126, "Gained", 10 GTS_add_to_menu menu, "Level up for (hero)", 149, "Level up for", 20 GTS_add_to_menu menu, "(#) levels for (hero)", 151, "levels for", 20 GTS_add_to_menu menu, "(hero) learned (spell)", 124, "learned", 10 GTS_add_to_menu menu, "Found a (item)", 139, "Found a", 20 GTS_add_to_menu menu, "Found (number) (items)", 141, "Found", 20 GTS_add_to_menu menu, "Found (gold)", 125, "Found", 10 GTS_menu_header menu, "Misc:" GTS_add_to_menu menu, "Status Prompt", 104, "Whose Status?", 20 GTS_add_to_menu menu, "Spells Prompt", 106, "Whose Spells?", 20 GTS_add_to_menu menu, "Equip Prompt", 108, "Equip Who?", 20 GTS_add_to_menu menu, "Plotscript: pick hero", 135, "Which Hero?", 20 GTS_add_to_menu menu, "Hero name prompt", 137, "Name the Hero", 20 '**** next unused index is 324 'NOTE: if you add global strings here, be sure to update the limit-checking on 'the implementation of the "get global string" plotscripting command WITH state .top = -1 .pt = -1 .first = -1 .last = menu.curitem - 1 .autosize = YES .autosize_ignore_lines = 3 END WITH setkeys YES DO setwait 55 setkeys YES IF keyval(scESC) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN IF state.pt >= 0 ANDALSO LEN(menu.help(state.pt)) THEN show_help menu.help(state.pt) ELSE show_help "edit_global_strings" END IF END IF IF keyval(scCTRL) > 0 AND keyval(scS) > 1 THEN IF prompt_for_string(search, "Search (descriptions & values)") THEN FOR i as integer = 0 TO state.last DIM idx as integer = (state.pt + 1 + i) MOD (state.last + 1) IF INSTR(LCASE(menu.text(idx)), LCASE(search)) OR INSTR(LCASE(menu.description(idx)), LCASE(search)) THEN state.pt = idx correct_menu_state state EXIT FOR END IF NEXT i END IF END IF usemenu state IF state.pt = -1 THEN IF enter_space_click(state) THEN EXIT DO ELSEIF menu.index(state.pt) <> -1 THEN strgrabber menu.text(state.pt), menu.maxlen(state.pt) END IF clearpage dpage menuopts.highlight = YES menuopts.scrollbar = YES standardmenu menu.text(), state, 232, 0, dpage, menuopts 'Since both halves of the menu share the same state and both are active, 'they both toggle state.tog. So work around that state.tog XOR= 1 standardmenu menu.description(), state, menu.shaded(), 0, 0, dpage edgeprint "CTRL+S Search", 0, pBottom, uilook(uiDisabledItem), dpage IF state.pt >= 0 ANDALSO LEN(menu.help(state.pt)) THEN edgeprint "Press F1 for help about this string", 0, pBottom - 10, uilook(uiDisabledItem), dpage END IF IF menu.index(state.pt) >= 0 THEN edgeboxstyle rCenter - (menu.maxlen(state.pt) * 4), pBottom, 8 * menu.maxlen(state.pt) + 4, 8, 0, dpage, transOpaque, YES edgeprint menu.text(state.pt), rCenter + 2 - (menu.maxlen(state.pt) * 4), pBottom, uilook(uiText), dpage END IF SWAP vpage, dpage setvispage vpage dowait LOOP 'Note: it is safe to write the strings to file out of order as long as we write 'all of them. Any gaps in the file will be filled with garbage: do not leave 'unused global string indices, or you won't be able to use them later! FOR i as integer = 0 TO GTSnumitems writeglobalstring menu.index(i), menu.text(i), menu.maxlen(i) NEXT i 'Write defaults for all elements that don't appear in the menu FOR i as integer = gen(genNumElements) TO 63 writeglobalstring 174 + i*2, "Element" & i+1, 14 NEXT i getstatnames statnames() END SUB SUB writeglobalstring (index as integer, s as string, maxlen as integer) IF index < 0 THEN EXIT SUB DIM fh as integer = FREEFILE OPENFILE(game & ".stt", FOR_BINARY, fh) DIM ch as string ch = CHR(small(LEN(s), small(maxlen, 255))) PUT #fh, 1 + index * 11, ch ch = LEFT(s, small(maxlen, 255)) PUT #fh, 2 + index * 11, ch CLOSE #fh loadglobalstrings END SUB SUB update_attack_editor_for_chain (byval mode as integer, byref caption1 as string, byref max1 as integer, byref min1 as integer, byref menutype1 as integer, byref caption2 as string, byref max2 as integer, byref min2 as integer, byref menutype2 as integer) SELECT CASE mode CASE 0 '--no special condition caption1 = "" max1 = 32767 min1 = -32767 menutype1 = 18'skipper caption2 = "" max2 = 32767 min2 = -32767 menutype2 = 18'skipper CASE 1 '--tagcheck caption1 = " if Tag:" max1 = max_tag() min1 = -max_tag() menutype1 = 2 caption2 = " and Tag:" max2 = max_tag() min2 = -max_tag() menutype2 = 2 CASE 2 TO 13 SELECT CASE mode CASE 2 TO 5: caption1 = " if attacker" CASE 6 TO 9: caption1 = " if any target" CASE 10 TO 13: caption1 = " if all target" END SELECT max1 = 15 min1 = 0 menutype1 = 16 'stat SELECT CASE mode CASE 2,6,10 caption2 = " is >" max2 = 32767 min2 = -32767 menutype2 = 0 CASE 3,7,11 caption2 = " is <" max2 = 32767 min2 = -32767 menutype2 = 0 CASE 4,8,12 caption2 = " is >" max2 = 100 min2 = 0 menutype2 = 17 'int% CASE 5,9,13 caption2 = " is <" max2 = 100 min2 = 0 menutype2 = 17 'int% END SELECT END SELECT END SUB FUNCTION attack_chain_browser (byval start_attack as integer) as integer DIM state as AttackChainBrowserState DIM selected as integer = start_attack state.before.size = 2 state.after.size = 2 DO '--Init FOR i as integer = 0 TO UBOUND(state.chainto) state.chainto(i) = 0 NEXT i FOR i as integer = 0 TO UBOUND(state.chainfrom) state.chainfrom(i) = 0 NEXT i state.root = NewSliceOfType(slRoot) state.lbox = NewSliceOfType(slContainer, state.root) state.lbox->Width = 80 state.rbox = NewSliceOfType(slContainer, state.root) state.rbox->Width = 80 state.rbox->AlignHoriz = 2 state.rbox->AnchorHoriz = 2 init_attack_chain_screen selected, state state.column = 1 state.refresh = YES state.focused = state.current state.before.pt = 0 state.before.top = 0 state.after.pt = 0 state.after.top = 0 setkeys DO setwait 55 setkeys IF keyval(scESC) > 1 THEN state.done = YES EXIT DO END IF IF keyval(scF1) > 1 THEN show_help "attack_chain_browse" IF enter_or_space() THEN IF state.focused <> 0 THEN IF state.column = 1 THEN state.done = YES selected = state.focused->extra(0) EXIT DO END IF END IF IF keyval(scLeft) > 1 THEN state.column = loopvar(state.column, 0, 2, -1) : state.refresh = YES IF keyval(scRight) > 1 THEN state.column = loopvar(state.column, 0, 2, 1) : state.refresh = YES SELECT CASE state.column CASE 0: IF usemenu(state.before) THEN state.refresh = YES CASE 1: CASE 2: IF usemenu(state.after) THEN state.refresh = YES END SELECT IF state.refresh THEN state.refresh = NO attack_preview_slice_defocus state.focused SELECT CASE state.column CASE 0: state.focused = state.chainfrom(state.before.pt) CASE 1: state.focused = state.current CASE 2: state.focused = state.chainto(state.after.pt) END SELECT attack_preview_slice_focus state.focused state.lbox->Y = state.before.top * -56 END IF clearpage dpage DrawSlice state.root, dpage SWAP vpage, dpage setvispage vpage dowait LOOP DeleteSlice @(state.root) IF state.done THEN EXIT DO LOOP RETURN selected END FUNCTION FUNCTION find_free_attack_preview_slot(slots() as Slice Ptr) as integer FOR i as integer = 0 TO UBOUND(slots) IF slots(i) = 0 THEN RETURN i NEXT i 'Oops! Can't hold any more 'FIXME: if/when FreeBasic supports resizeable arrays in types, use them here RETURN -1 END FUNCTION SUB init_attack_chain_screen(byval attack_id as integer, state as AttackChainBrowserState) DIM atk as AttackData loadattackdata atk, attack_id state.current = create_attack_preview_slice("", attack_id, state.root) state.current->AnchorHoriz = 1 state.current->AlignHoriz = 1 state.current->Y = 6 DIM slot as integer IF atk.instead.atk_id > 0 THEN slot = find_free_attack_preview_slot(state.chainto()) IF slot >= 0 THEN state.chainto(slot) = create_attack_preview_slice("Instead", atk.instead.atk_id - 1, state.rbox) END IF END IF IF atk.chain.atk_id > 0 THEN slot = find_free_attack_preview_slot(state.chainto()) IF slot >= 0 THEN state.chainto(slot) = create_attack_preview_slice("Regular", atk.chain.atk_id - 1, state.rbox) END IF END IF IF atk.elsechain.atk_id > 0 THEN slot = find_free_attack_preview_slot(state.chainto()) IF slot >= 0 THEN state.chainto(slot) = create_attack_preview_slice("Else", atk.elsechain.atk_id - 1, state.rbox) END IF END IF position_chain_preview_boxes(state.chainto(), state.after) '--now search for attacks that chain to this one FOR i as integer = 0 TO gen(genMaxAttack) loadattackdata atk, i IF atk.chain.atk_id - 1 = attack_id THEN slot = find_free_attack_preview_slot(state.chainfrom()) IF slot = -1 THEN EXIT FOR 'give up when out of space state.chainfrom(slot) = create_attack_preview_slice("Regular", i, state.lbox) END IF IF atk.elsechain.atk_id - 1 = attack_id THEN slot = find_free_attack_preview_slot(state.chainfrom()) IF slot = -1 THEN EXIT FOR 'give up when out of space state.chainfrom(slot) = create_attack_preview_slice("Else", i, state.lbox) END IF IF atk.instead.atk_id - 1 = attack_id THEN slot = find_free_attack_preview_slot(state.chainfrom()) IF slot = -1 THEN EXIT FOR 'give up when out of space state.chainfrom(slot) = create_attack_preview_slice("Instead", i, state.lbox) END IF NEXT i position_chain_preview_boxes(state.chainfrom(), state.before) END SUB SUB position_chain_preview_boxes(sl_list() as Slice ptr, st as MenuState) st.last = -1 DIM y as integer = 6 FOR i as integer = 0 TO UBOUND(sl_list) IF sl_list(i) <> 0 THEN WITH *(sl_list(i)) .Y = y y += .Height + 6 END WITH st.last += 1 END IF NEXT i IF st.last = -1 THEN st.last = 0 END SUB FUNCTION create_attack_preview_slice(caption as string, byval attack_id as integer, byval parent as Slice Ptr) as Slice Ptr DIM atk as AttackData loadattackdata atk, attack_id DIM box as Slice Ptr = NewSliceOfType(slRectangle, parent) box->Width = 80 box->Height = 50 ChangeRectangleSlice box, 0 ChangeRectangleSlice box, , , , -1 DIM spr as Slice Ptr = NewSliceOfType(slSprite, box) ChangeSpriteSlice spr, 6, atk.picture, atk.pal, 2 spr->AnchorHoriz = 1 spr->AlignHoriz = 1 spr->AnchorVert = 2 spr->AlignVert = 2 DIM numsl as Slice Ptr = NewSliceOfType(slText, box) ChangeTextSlice numsl, STR(attack_id), , YES numsl->AnchorHoriz = 1 numsl->AlignHoriz = 1 DIM namesl as Slice Ptr = NewSliceOfType(slText, box) ChangeTextSlice namesl, atk.name, , YES namesl->AnchorHoriz = 1 namesl->AlignHoriz = 1 namesl->Y = 10 DIM capsl as Slice Ptr = NewSliceOfType(slText, box) ChangeTextSlice capsl, caption, , YES capsl->AnchorHoriz = 1 capsl->AlignHoriz = 1 capsl->AnchorVert = 2 capsl->AlignVert = 2 '--Save attack_id in the extra data box->extra(0) = attack_id RETURN box END FUNCTION SUB attack_preview_slice_focus(byval sl as Slice Ptr) IF sl = 0 THEN EXIT SUB ChangeRectangleSlice sl, , , , 0 DIM ch as Slice Ptr = sl->FirstChild WHILE ch IF ch->SliceType= slText THEN ChangeTextSlice ch, , uilook(uiSelectedItem) END IF ch = ch->NextSibling WEND END SUB SUB attack_preview_slice_defocus(byval sl as Slice Ptr) IF sl = 0 THEN EXIT SUB ChangeRectangleSlice sl, , , , -1 DIM ch as Slice Ptr = sl->FirstChild WHILE ch IF ch->SliceType = slText THEN ChangeTextSlice ch, , uilook(uiText) END IF ch = ch->NextSibling WEND END SUB SUB fontedit (font() as integer) DIM f(255) as integer 'Contains the character indices which should be shown (always 32-255) DIM copybuf(4) as integer DIM menu(6) as string DIM selectable(6) as bool flusharray selectable(), , YES menu(0) = "Previous Menu" menu(1) = "Edit Font..." menu(2) = "Import Font..." menu(3) = "Export Font..." selectable(4) = NO menu(5) = "" 'Set below selectable(6) = NO DIM i as integer DIM last as integer = -1 FOR i = 32 TO 255 last += 1 f(last) = i NEXT i 'mode = -1: the menu 'mode = 0: select a character to edit 'mode = 1: editing a character DIM mode as integer = -1 'This state is used for the menu, not the charpicker DIM state as MenuState WITH state .pt = 0 .top = 0 .last = UBOUND(menu) .size = 22 END WITH DIM fonttype as fontTypeEnum = get_font_type(font()) DIM linesize as integer = 14 DIM pt as integer = -1 * linesize DIM x as integer DIM y as integer DIM xoff as integer DIM yoff as integer DIM c as integer setkeys DO setwait 55 setkeys IF keyval(scF1) > 1 THEN show_help "fontedit" SELECT CASE mode CASE -1 IF keyval(scEsc) > 1 THEN EXIT DO usemenu state, selectable() IF enter_space_click(state) THEN IF state.pt = 0 THEN EXIT DO IF state.pt = 1 THEN mode = 0 IF state.pt = 2 THEN fontedit_import_font font() fonttype = get_font_type(font()) state.pt = 1 mode = 0 END IF IF state.pt = 3 THEN fontedit_export_font font() END IF IF state.pt = 5 THEN IF intgrabber(fonttype, ftypeASCII, ftypeLatin1) THEN set_font_type font(), fonttype xbsave game + ".fnt", font(), 2048 END IF END IF CASE 0 IF keyval(scEsc) > 1 THEN mode = -1 IF keyval(scUp) > 1 THEN pt = large(pt - linesize, -1 * linesize) IF keyval(scDown) > 1 THEN pt = small(pt + linesize, last) IF keyval(scLeft) > 1 THEN pt = large(pt - 1, 0) IF keyval(scRight) > 1 THEN pt = small(pt + 1, last) IF enter_space_click(state) THEN IF pt < 0 THEN mode = -1 ELSE mode = 1 x = 0 y = 0 END IF END IF CASE 1 IF keyval(scEsc) > 1 OR keyval(scAnyEnter) > 1 THEN mode = 0 IF keyval(scUp) > 1 THEN y = loopvar(y, 0, 7, -1) IF keyval(scDown) > 1 THEN y = loopvar(y, 0, 7, 1) IF keyval(scLeft) > 1 THEN x = loopvar(x, 0, 7, -1) IF keyval(scRight) > 1 THEN x = loopvar(x, 0, 7, 1) IF keyval(scSpace) > 1 THEN setbit font(), 0, (f(pt) * 8 + x) * 8 + y, (readbit(font(), 0, (f(pt) * 8 + x) * 8 + y) XOR 1) setfont font() xbsave game + ".fnt", font(), 2048 END IF END SELECT IF mode >= 0 THEN '--copy and paste support IF copy_keychord() THEN FOR i = 0 TO 63 setbit copybuf(), 0, i, readbit(font(), 0, f(pt) * 64 + i) NEXT i END IF IF paste_keychord() THEN FOR i = 0 TO 63 setbit font(), 0, f(pt) * 64 + i, readbit(copybuf(), 0, i) NEXT i setfont font() xbsave game + ".fnt", font(), 2048 END IF END IF '--Draw screen clearpage dpage IF mode = -1 THEN menu(5) = "Font type: " IF fonttype = ftypeASCII THEN menu(5) &= "ASCII" menu(6) = " (Characters 127-255 are icons)" ELSEIF fonttype = ftypeLatin1 THEN menu(5) &= "Latin1" menu(6) = " (Characters 127-160 are icons)" END IF standardmenu menu(), state, 0, 0, dpage END IF IF mode >= 0 THEN xoff = 8 yoff = 8 FOR i = 0 TO last textcolor uilook(uiMenuItem), uilook(uiDisabledItem) IF pt >= 0 THEN IF mode = 0 THEN IF (i MOD linesize) = (pt MOD linesize) OR (i \ linesize) = (pt \ linesize) THEN textcolor uilook(uiMenuItem), uilook(uiHighlight) END IF IF pt = i THEN textcolor uilook(uiSelectedItem + state.tog), 0 END IF printstr CHR(f(i)), xoff + (i MOD linesize) * 9, yoff + (i \ linesize) * 9, dpage NEXT i textcolor uilook(uiMenuItem), 0 IF pt < 0 THEN textcolor uilook(uiSelectedItem + state.tog), 0 printstr menu(0), 8, 0, dpage IF pt >= 0 THEN xoff = 150 yoff = 4 rectangle xoff, yoff, 160, 160, uilook(uiDisabledItem), dpage FOR i = 0 TO 7 FOR j as integer = 0 TO 7 IF readbit(font(), 0, (f(pt) * 8 + i) * 8 + j) THEN rectangle xoff + i * 20, yoff + j * 20, 20, 20, uilook(uiMenuItem), dpage END IF NEXT j NEXT i IF mode = 1 THEN IF readbit(font(), 0, (f(pt) * 8 + x) * 8 + y) THEN c = uilook(uiSelectedItem2) ELSE c = uilook(uiSelectedDisabled) END IF rectangle xoff + x * 20, yoff + y * 20, 20, 20, c, dpage END IF textcolor uilook(uiText), 0 DIM tmp as string = "CHAR " & f(pt) IF f(pt) >= &hA1 THEN tmp &= "/U+00" & HEX(f(pt)) printstr tmp, 12, 190, dpage IF f(pt) < 32 THEN printstr "RESERVED", 160, 190, dpage ELSE FOR i = 2 TO 53 IF f(pt) = ASC(key2text(2, i)) THEN printstr "ALT+" + UCASE(key2text(0, i)), 160, 190, dpage IF f(pt) = ASC(key2text(3, i)) THEN printstr "ALT+SHIFT+" + UCASE(key2text(0, i)), 160, 190, dpage NEXT i IF f(pt) = 32 THEN printstr "SPACE", 160, 190, dpage END IF END IF END IF SWAP vpage, dpage setvispage vpage dowait LOOP END SUB SUB fontedit_export_font(font() as integer) DIM newfont as string = "newfont" newfont = inputfilename("Input a filename to save to", ".ohf", "", "input_file_export_font") IF newfont <> "" THEN xbsave game & ".fnt", font(), 2048 copyfile game & ".fnt", newfont & ".ohf" END IF END SUB SUB fontedit_import_font(font() as integer) STATIC default as string DIM newfont as string newfont = browse(0, default, "*.ohf", "browse_font") IF newfont <> "" THEN writeablecopyfile newfont, game & ".fnt" DIM i as integer DIM font_tmp(1023) as integer '--character 0 (actually font(0)) contains metadata (marks as ASCII or Latin-1) '--character 1 to 31 are internal icons and should never be overwritten FOR i = 1 * 4 TO 32 * 4 - 1 font_tmp(i) = font(i) NEXT i '--Reload the font xbload game + ".fnt", font(), "Can't load font" setfont font() '--write back the old 1-31 characters FOR i = 1 * 4 TO 32 * 4 - 1 font(i) = font_tmp(i) NEXT i END IF END SUB ' Pressed Shift+Backspace, or Delete on the "<- Record # ->" line FUNCTION cropafter_keycombo(index_selected as bool = NO) as bool IF index_selected AND keyval(scDelete) > 1 THEN RETURN YES RETURN keyval(scShift) > 0 AND keyval(scBackspace) > 1 END FUNCTION SUB cropafter (byval index as integer, byref limit as integer, byval flushafter as bool, lump as string, byval bytes as integer, byval prompt as bool=YES) 'flushafter YES = flush records (set to zero) 'flushafter NO = trim file DIM i as integer IF prompt THEN DIM menu(1) as string menu(0) = "No, do not delete anything" menu(1) = "Yes, DELETE!" IF multichoice("Do you want to delete ALL records AFTER this one?", menu()) < 1 _ ORELSE yesno("Are you SURE?", NO, NO) = NO THEN setkeys EXIT SUB ELSE setkeys END IF END IF DIM buf(bytes \ 2 - 1) as integer FOR i = 0 TO index loadrecord buf(), lump, bytes \ 2, i storerecord buf(), tmpdir & "_cropped.tmp", bytes \ 2, i NEXT i IF flushafter THEN 'FIXME: this flushafter hack only exists for the .DT0 lump, ' out of fear that some code with read hero data past the end of the file. ' after cleanup of all hero code has confurmed this fear is unfounded, we can ' eliminate this hack entirely 'Also, if we're not doing flushing, all of this can be replaced with a truncation flusharray buf() FOR i = index + 1 TO limit storerecord buf(), tmpdir & "_cropped.tmp", bytes \ 2, i NEXT i END IF limit = index copyfile tmpdir & "_cropped.tmp", lump safekill tmpdir & "_cropped.tmp" END SUB FUNCTION numbertail (s as string) as string DIM n as integer IF s = "" THEN RETURN "BLANK" FOR i as integer = 1 TO LEN(s) IF is_int(MID(s, i)) THEN n = str2int(MID(s, i)) + 1 RETURN LEFT(s, i - 1) & n END IF NEXT RETURN s + "2" END FUNCTION 'Get a list of the first letters (lowercase) of every word in each menu() string, except 'those words listed in excludewords. excludewords should be a space-separated 'list (case matters). 'menukeys() should be statically sized. SUB get_menu_hotkeys (menu() as string, byval menumax as integer, menukeys() as string, excludewords as string = "") 'Easy exercise for the reader: Write this in three lines of Python DIM excludes() as string IF excludewords = "" THEN REDIM excludes(-1 TO -1) ELSE split excludewords, excludes(), " " END IF FOR i as integer = 0 TO menumax menukeys(i) = "" DIM firstletter as integer = YES FOR j as integer = 1 TO LEN(menu(i)) DIM isalp as integer = isalpha(menu(i)[j - 1]) IF firstletter ANDALSO isalp THEN DIM excluded as integer = NO FOR k as integer = 0 TO UBOUND(excludes) IF MID(menu(i), j, LEN(excludes(k))) = excludes(k) THEN excluded = YES : EXIT FOR NEXT IF excluded = NO THEN menukeys(i) += LCASE(MID(menu(i), j, 1)) END IF END IF firstletter = (isalp = 0) NEXT 'debug "hotkeys from '" & menu(i) & "' -> '" & menukeys(i) & "'" NEXT END SUB SUB experience_chart () 'DIM exp_first_level as integer = 30 'DIM exp_multiplier as single = 1.2 'DIM exp_adder as integer = 5 'DIM exp_uppercap as integer = 1000000 DIM mode as integer = 0 STATIC hero_count as integer = 4 STATIC enemy_id as integer = 0 DIM enemy as EnemyDef STATIC form_id as integer = 0 DIM startfrom as integer = 3 DIM menu(startfrom + gen(genMaxLevel)) as string menu(0) = "Previous menu..." DIM state as MenuState WITH state .size = 24 .last = UBOUND(menu) .need_update = YES END WITH DIM menuopts as MenuOptions menuopts.wide = 312 STATIC first_view as bool = YES setkeys DO setwait 55 setkeys IF keyval(scESC) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "experience_chart" usemenu state IF enter_space_click(state) THEN IF state.pt = 0 THEN EXIT DO END IF IF state.pt = 1 THEN IF intgrabber(mode, 0, 2) THEN state.need_update = YES END IF IF state.pt = 2 THEN IF mode = 1 THEN IF enemygrabber(enemy_id, state) THEN state.need_update = YES ELSEIF mode = 2 THEN IF intgrabber(form_id, 0, gen(genMaxFormation)) THEN state.need_update = YES END IF END IF IF state.pt = 3 THEN IF intgrabber(hero_count, 1, 4) THEN state.need_update = YES END IF IF state.need_update THEN DIM test_exp as integer = 0 DIM test_name as string IF mode = 0 THEN menu(1) = "Preview mode: Total Exp." menu(2) = "Compared to N/A" ELSEIF mode = 1 THEN loadenemydata enemy, enemy_id menu(1) = "Preview mode: Enemy" menu(2) = "Compared to enemy: " & enemy_id & " " & enemy.name & " (" & enemy.reward.exper & " exp)" test_exp = enemy.reward.exper test_name = enemy.name ELSEIF mode = 2 THEN DIM form as Formation LoadFormation form, form_id test_exp = 0 FOR i as integer = 0 TO 7 IF form.slots(i).id >= 0 THEN loadenemydata enemy, form.slots(i).id test_exp += enemy.reward.exper END IF NEXT i menu(1) = "Compare mode: Formation" menu(2) = "Compared to formation: " & form_id & " (" & test_exp & " exp)" test_name = "Formation" & form_id END IF menu(3) = "Distributed to a party of: " & hero_count & " heroes" DIM suffix as string DIM killcount as string FOR lev as integer = 1 TO gen(genMaxLevel) IF mode = 0 THEN suffix = "total " & total_exp_to_level(lev) ELSE IF test_exp > 0 THEN killcount = STR(ceiling(exptolevel(lev) / test_exp * hero_count)) ELSE killcount = "infinite" END IF suffix = "= " & test_name & "*" & killcount END IF menu(startfrom + lev) = "Level " & lev & " +" & exptolevel(lev) & " " & suffix NEXT lev state.need_update = NO END IF clearpage vpage draw_fullscreen_scrollbar state, , vpage standardmenu menu(), state, 0, 0, vpage, menuopts setvispage vpage dowait IF first_view THEN first_view = NO notification "This screen is informational only. You cannot customize the experience formula yet." END IF LOOP END SUB SUB stat_growth_chart () 'midpoint should stored in gen() DIM midpoint as double = 0.3219 'default to current DIM midpoint_repr as string = format_percent(midpoint, 4) DIM menu(2) as string menu(0) = "Previous menu..." DIM state as MenuState WITH state .size = 24 .last = UBOUND(menu) .need_update = YES END WITH DIM preview_lev as integer = gen(genMaxLevel) \ 2 'Position and size of the graph DIM rect as RectType rect.x = 150 rect.y = 40 rect.wide = 150 rect.high = 140 DIM origin_y as integer = rect.y + rect.high setkeys YES DO setwait 55 setkeys YES IF keyval(scESC) > 1 THEN EXIT DO IF keyval(scF1) > 1 THEN show_help "stat_growth" usemenu state IF enter_space_click(state) THEN IF state.pt = 0 THEN EXIT DO END IF IF state.pt = 1 THEN state.need_update OR= percent_grabber(midpoint, midpoint_repr, -0.1, 1.2, 4) ELSEIF state.pt = 2 THEN state.need_update OR= intgrabber(preview_lev, 0, gen(genMaxLevel)) END IF IF state.need_update THEN menu(1) = "Fix value at level " & (gen(genMaxLevel) / 2) & " : " & midpoint_repr menu(2) = "Preview: at level " & preview_lev & " = " & format_percent(atlevel_quadratic(preview_lev, 0, 1000000, midpoint) / 1000000, 4) ' of Level" & gen(genMaxLevel) & " value" state.need_update = NO END IF 'Draw screen clearpage vpage standardmenu menu(), state, 0, 0, vpage 'Draw a 150x150 graph 'axes drawline rect.x, origin_y, rect.x, rect.y, uilook(uiDisabledItem), vpage drawline rect.x, origin_y, rect.x + rect.wide, origin_y, uilook(uiDisabledItem), vpage 'line (drawn so that if genMaxLevel is small, you get a lot of steps, and never sloped line segments) DIM lasty as double FOR x as integer = 0 TO rect.wide - 1 DIM lev as integer = INT((gen(genMaxLevel) + 1) * x / rect.wide) 'floor DIM y as double = atlevel_quadratic(lev, 0, rect.high * 100, midpoint) / 100 IF x = 0 THEN lasty = y drawline x + rect.x, origin_y - y, x + rect.x, origin_y - lasty, uilook(uiHighlight), vpage lasty = y NEXT 'Draw crosshair DIM crosshair_lev as double IF state.pt = 2 THEN crosshair_lev = preview_lev ELSE crosshair_lev = gen(genMaxLevel) / 2 DIM as double crosshairx, crosshairy 'in pixels crosshairx = rect.wide * crosshair_lev / gen(genMaxLevel) crosshairy = atlevel_quadratic(crosshair_lev, 0, rect.high * 100, midpoint) / 100 drawline rect.x + crosshairx - 3, origin_y - crosshairy, rect.x + crosshairx + 3, origin_y - crosshairy, uilook(uiHighlight2), vpage drawline rect.x + crosshairx, origin_y - crosshairy - 3, rect.x + crosshairx, origin_y - crosshairy + 3, uilook(uiHighlight2), vpage setvispage vpage dowait LOOP END SUB FUNCTION pick_channel_name() as string #ifdef __FB_WIN32__ return "\\.\pipe\ohrrpgce_lump_updates_testing_" & randint(100000) #else return tmpdir + ".lump_updates.txt" #endif END FUNCTION ' Used by spawn_game to run gdb or valgrind. ' Returns a ProcessHandle or NO/NULL. ' Note: these's a fair bit of overlap between this and spawn_and_wait for Unix, ' but this is specific to spawn_game. FUNCTION spawn_console_process(executable as string, args as string, title as string) as ProcessHandle #IFDEF __FB_UNIX__ ' On Unix the spawned program doesn't inherit access to our tty even if we have one, ' so need to spawn it inside an xterm ' TODO: this isn't going to work on OSX... unless maybe you have X11 installed? ' Wrap in a xterm call... DIM xtermargs as string xtermargs = " -geometry 120x30 -bg black -fg gray90 -title '" & title & "' -e " _ """" & escape_string(executable & " " & args, """\") & """" executable = find_helper_app("xterm") IF LEN(executable) = 0 THEN notification "xterm is missing; can't continue" RETURN NO END IF RETURN open_process(executable, xtermargs, YES, YES) #ELSEIF defined(__FB_WIN32__) ' Likewise on Windows need a console. But this is only implemented on Windows RETURN open_console_process(executable, args) #ELSE notification "Running under GDB/Valgrind not implemented on this platform" RETURN NO #ENDIF END FUNCTION 'Spawn Game for live-previewing. Puts the ProcessHandle in slave_process. 'At most one of gdb and valgrind should be true. 'Returns true if successfully spawned FUNCTION spawn_game(gdb as bool = NO, valgrind as bool = NO) as bool IF slave_process <> 0 THEN 'First clean up after the last time we ran Game cleanup_process @slave_process END IF DIM channel_name as string channel_name = pick_channel_name() IF channel_open_server(slave_channel, channel_name) = NO THEN notification "Couldn't open channel to communicate with Game" RETURN NO END IF debuginfo "Successfully opened IPC channel " + channel_name DIM gameexename as string = GAMEEXE DIM executable as string executable = exepath & SLASH & GAMEEXE #ifdef __FB_DARWIN__ gameexename = "OHRRPGCE-Game" CONST mac_game_bundle as string = "/OHRRPGCE-Game.app/Contents/MacOS/ohrrpgce-game" ' First check for unbundled ohrrpgce-game, then OHRRPGCE-Game.app executable = exepath & SLASH & GAMEEXE IF isfile(executable) = NO THEN executable = app_dir & mac_game_bundle IF isfile(executable) = NO THEN executable = parentdir(exepath, 3) & mac_game_bundle END IF END IF #endif IF isfile(executable) = NO THEN notification "Couldn't find " & gameexename & !"\nIt should be in the same directory as " & CUSTOMEXE RETURN NO END IF DIM arguments as string #IFDEF __FB_UNIX__ arguments = "--slave " & escape_filename(channel_name) #ELSE 'Not a filename arguments = "--slave " & channel_name #ENDIF IF gdb THEN ' Wrap in a gdb call... ' channel_wait_for_client_connection is going to time out if Game isn't ' allowed to run normally, so as a compromise break immediately after the ' channel is connected. (You can always increase the 3sec delay instead.) ' We imitate invoking gdb via the gdbgame.sh/bat script DIM cmdfile as string cmdfile = finddatafile("misc/gdbcmds1.txt", NO) DIM gdbargs as string IF LEN(cmdfile) > 0 THEN gdbargs = "-x=""" & cmdfile & """ " ELSE gdbargs = "-ex ""break HOOK_AFTER_ATTACH_TO_MASTER"" -ex ""run"" " END IF gdbargs &= "--args " & executable & " " & arguments executable = find_helper_app("gdb") IF LEN(executable) = 0 THEN notification "Couldn't find gdb. You need to install it." RETURN NO END IF slave_process = spawn_console_process(executable, gdbargs, "gdb " & GAMEEXE) ELSEIF valgrind THEN #IFNDEF __FB_UNIX__ notification "Valgrind only supported on UNIX" RETURN NO #ENDIF executable = find_helper_app("valgame.sh") IF LEN(executable) = 0 THEN notification "Couldn't find valgame.sh. You should run from a copy of the source code." RETURN NO END IF slave_process = spawn_console_process(executable, arguments, "valgame.sh") ELSE slave_process = open_process(executable, arguments, YES, YES) END IF 'debuginfo "Spawning: " & executable & " " & arguments IF slave_process = 0 THEN notification "Couldn't run " & gameexename RETURN NO END IF 'We currently do nothing at all with slave_process except cleanup (on Unix 'closing the channel freezes until the child finishes). 'Instead we test Game is still running with slave_channel <> NULL_CHANNEL 'Need Game to connect before we can safely write to the pipe; wait up to 3000ms DIM waitms as integer = IIF(valgrind, 9000, 3000) IF channel_wait_for_client_connection(slave_channel, waitms) = 0 THEN notification "Error communicating with " & gameexename & !" (couldn't connect); aborting\n(Press a key)" channel_close slave_channel cleanup_process @slave_process RETURN NO END IF 'Write version info DIM tmp as string 'msgtype magickey,proto_ver,program_ver,version_string tmp = "V OHRRPGCE," & CURRENT_TESTING_IPC_VERSION & "," & version_revision & "," & version channel_write_line(slave_channel, tmp) tmp = "G " & sourcerpg channel_write_line(slave_channel, tmp) tmp = "W " & workingdir channel_write_line(slave_channel, tmp) 'If any of these writes fails, slave_channel is closed IF slave_channel <> NULL_CHANNEL THEN 'If we got this far, start sending lump updates and locking files before writing set_OPEN_hook @inworkingdir, YES, @slave_channel ELSE notification "Error communicating with " & gameexename & !" (channel write failure); aborting\n(Press a key)" channel_close slave_channel cleanup_process @slave_process RETURN NO END IF RETURN YES END FUNCTION SUB spawn_game_menu(gdb as bool = NO, valgrind as bool = NO) #IFDEF __FB_WIN32__ IF is_windows_9x() THEN notification "Testing your game while editing isn't supported on your version of Windows; it requires an NT-based Windows release" EXIT SUB END IF #ENDIF 'Prod the channel to see whether it's still up (send ping) channel_write_line(slave_channel, "P ") DIM scancode as integer IF slave_channel <> NULL_CHANNEL THEN scancode = notification(!"Game is already running! Running multiple test copies of a game is not yet supported.\n" _ "Press F1 to see the help file for Test Game.") ELSE gen(genCurrentDebugMode) = 1 xbsave game + ".gen", gen(), 1000 IF spawn_game(gdb, valgrind) THEN scancode = notification( "You're running your game in live preview mode. " _ !"Please press F1 now to read the help file for this if you haven't already.\n\n" _ "Press any key") END IF END IF IF scancode = scF1 THEN show_help "test_game" END SUB 'Return true on success FUNCTION save_current_game (byval genDebugMode_override as integer=-1) as bool 'Apply the appropriate genCurrentDebugMode IF genDebugMode_override >= 0 THEN gen(genCurrentDebugMode) = genDebugMode_override ELSE gen(genCurrentDebugMode) = gen(genDebugMode) END IF xbsave game + ".gen", gen(), 1000 clearpage 0 textcolor uilook(uiText), 0 printstr "LUMPING DATA: please wait.", 0, 0, 0 setvispage 0, NO '--verify various stuff rpg_sanity_checks '--lump data to SAVE rpg file IF write_rpg_or_rpgdir(workingdir, sourcerpg) THEN write_session_info 'Update sourcerpg mtime and reset editing start time automatic_backup sourcerpg RETURN YES END IF END FUNCTION SUB automatic_backup (rpgfile as string) DIM keep_how_many as integer = 10 'FIXME: this could be customized per-game DIM backupdir as string = trimfilename(rpgfile) & SLASH & "autobackups" IF NOT isdir(backupdir) THEN makedir backupdir IF NOT diriswriteable(backupdir) THEN debuginfo "Can't do automatic backups """ & backupdir & """ is not writeable" END IF DIM warnfile as string = backupdir & SLASH & "README-WARNING-automatic-backups.txt" IF NOT isfile(warnfile) THEN string_to_file !"WARNING! These automatic backups are no substitute for manual backups!\nThey are just here to save your slime that one time you forget!\n\nIf you don't have any other backup plan, at least remember to e-mail\na copy of your .rpg file to yourself once a week.", warnfile END IF 'Copy the backup DIM datestr as string = MID(DATE, 7, 4) & "-" & MID(DATE, 1, 2) & "-" & MID(DATE, 4, 2) DIM destfile as string = backupdir & SLASH & trimextension(trimpath(rpgfile)) & "-" & datestr & "." & justextension(rpgfile) IF isdir(rpgfile) THEN IF isdir(destfile) THEN killdir destfile confirmed_copydirectory(rpgfile, destfile) ELSE copyfile rpgfile, destfile END IF 'Cull old backups to avoid bloatclutter. REDIM oldfiles() as string findfiles backupdir, trimextension(trimpath(rpgfile)) & "-*.rpg", fileTypeFile, , oldfiles() REDIM olddirs() as string findfiles backupdir, trimextension(trimpath(rpgfile)) & "-*.rpgdir", fileTypeDirectory, , olddirs() DIM old as string vector v_new old FOR i as integer = 0 TO UBOUND(oldfiles) IF LEN(oldfiles(i)) THEN v_append old, oldfiles(i) NEXT i FOR i as integer = 0 TO UBOUND(olddirs) IF LEN(olddirs(i)) THEN v_append old, olddirs(i) NEXT i v_sort old DIM oldrpg as string FOR i as integer = v_len(old) - 1 - keep_how_many TO 0 STEP -1 oldrpg = backupdir & SLASH & old[i] IF isdir(oldrpg) THEN killdir oldrpg ELSE safekill oldrpg END IF NEXT i v_free old END SUB ' Save all lumps in lumpsdir, except for *.tmp, as a lumped file or .rpgdir directory. ' Returns true on success. FUNCTION write_rpg_or_rpgdir (lumpsdir as string, filetolump as string) as bool '--build the list of files to lump. We don't need hidden files DIM filelist() as string findfiles lumpsdir, ALLFILES, fileTypeFile, NO, filelist() 'Removes .tmp files fixlumporder filelist() IF isdir(filetolump) THEN '---copy changed files back to source rpgdir--- IF NOT fileiswriteable(filetolump & SLASH & "archinym.lmp") THEN move_unwriteable_rpg filetolump makedir filetolump END IF FOR i as integer = 0 TO UBOUND(filelist) safekill filetolump + SLASH + filelist(i) IF copyfile(lumpsdir + SLASH + filelist(i), filetolump + SLASH + filelist(i)) = NO THEN pop_warning "Failed to save game to " & filetolump & LINE_END "Look in c_debug.txt for error messages." RETURN NO END IF 'Moving files instead would offer no failsafe if something goes wrong while moving '(Plus can't move from different mounted filesystem) NEXT ELSE '---relump data into lumpfile package--- IF NOT fileiswriteable(filetolump) THEN move_unwriteable_rpg filetolump END IF DIM errmsg as string = lumpfiles(filelist(), filetolump, lumpsdir + SLASH) IF lumpsdir = workingdir THEN cleanup_workingdir_on_exit = (LEN(errmsg) = 0) 'Don't delete workingdir if it hasn't been saved END IF IF LEN(errmsg) THEN 'Show a warning instead of a fatal error: this isn't fatal pop_warning "Failed to save game to " & filetolump & ": " & errmsg RETURN NO END IF END IF RETURN YES END FUNCTION SUB move_unwriteable_rpg (filetolump as string) clearpage vpage DIM newfile as string = documents_dir & SLASH & trimpath(filetolump) basic_textbox filetolump + " is not writeable. Saving to " + newfile + !"\n[Press Any Key]", uilook(uiText), vpage setvispage vpage waitforanykey filetolump = newfile END SUB SUB check_used_onetime_npcs(bits() as integer) 'Search through all the NPC definitions and figure out which NPC onetime ' bits have been used. The result is a bitset array with 0 bits for unused ' onetimes and 1 bits for used onetimes. flusharray bits() setbit bits(), 0, 0, YES ' bit 0 can't be used, 0 indicates usable repeatedly. setbit bits(), 0, 1, YES ' bit 1 can't be used, because istag() doesn't allow tag 1. REDIM npcdata(0) as NPCType FOR m as integer = 0 TO gen(genMaxMap) LoadNPCD maplumpname(m, "n"), npcdata() FOR i as integer = 0 TO UBOUND(npcdata) WITH npcdata(i) IF .usetag > max_onetime THEN debugc errError, "out-of-range onetime tag " & .usetag & " for NPC " & i & " on map " & m ELSEIF .usetag > 0 THEN 'debug "Map " & m & " NPC " & i & " uses onetime " & .usetag IF readbit(bits(), 0, .usetag) THEN 'Don't show a warning for duplicate onetime tags because it happens all the time with map copying END IF setbit bits(), 0, .usetag, YES END IF END WITH NEXT i NEXT m END SUB SUB menu_of_reorderable_nodes(st as MenuState, menu as MenuDef) 'This is intended for menus that represent sibling Nodes. The NodePtr 'is in the .dataptr of the selected menu item WITH *menu.items[st.pt] IF .dataptr <> 0 THEN DIM node as NodePtr = .dataptr IF reorderable_node(node) THEN st.need_update = YES END IF END IF END WITH END SUB FUNCTION reorderable_node(byval node as NodePtr) as integer IF keyval(scShift) > 0 THEN IF node THEN IF keyval(scUp) > 1 THEN SwapNodePrev node RETURN YES ELSEIF keyval(scDown) > 1 THEN SwapNodeNext node RETURN YES END IF END IF END IF RETURN NO END FUNCTION CONST platformoptEXIT as integer = 1 CONST platformoptTOGGLEVGAMEPAD as integer = 2 CONST platformoptBUTTON as integer = 3 CONST platformoptVBUTTON as integer = 4 CONST platformoptADDVBUTTON as integer = 5 CONST platformoptTOGGLEMULTI as integer = 6 CONST platformoptBUTTONNAMES as integer = 7 CONST platformoptMARGINS as integer = 8 CONST platformoptTOGGLETOUCHTEXTBOXES as integer = 9 CONST platformoptPURCHASE as integer = 10 CONST platformoptTOGGLEVGAMEPADSUSPENDPLAYER as integer = 11 SUB edit_platform_options () DIM menu as MenuDef DIM st as MenuState st.autosize = YES st.autosize_ignore_pixels = 14 st.active = YES st.need_update = YES REDIM enabled(0) as bool DIM gen_root as NodePtr = get_general_reld() DIM mobile as NodePtr IF gen_root."mobile_options".exists THEN mobile = gen_root."mobile_options".ptr ELSE 'Node does not exist, create it and set defaults mobile = SetChildNode(gen_root, "mobile_options") END IF DIM console as NodePtr IF gen_root."console_options".exists THEN console = gen_root."console_options".ptr ELSE 'Node does not exist, create it and set defaults console = SetChildNode(gen_root, "console_options") END IF DIM vgp as NodePtr IF mobile."virtual_gamepad".exists THEN vgp = mobile."virtual_gamepad".ptr ELSE 'Node does not exist, create it and set defaults vgp = SetChildNode(mobile, "virtual_gamepad") AppendChildNode(vgp, "button", scEnter) AppendChildNode(vgp, "button", scESC) END IF DIM vgpb as NodePtr IF mobile."virtual_gamepad_battle".exists THEN vgpb = mobile."virtual_gamepad_battle".ptr ELSE 'Node does not exist, create it and set defaults vgpb = SetChildNode(mobile, "virtual_gamepad_battle") AppendChildNode(vgpb, "button", scEnter) AppendChildNode(vgpb, "button", scESC) END IF DIM gamepad as NodePtr IF gen_root."gamepad".exists THEN gamepad = gen_root."gamepad".ptr ELSE gamepad = SetChildNode(gen_root, "gamepad") END IF DIM buttons(...) as string = {"A", "B", "X", "Y", "L1", "R1", "L2", "R2", "UP", "RIGHT", "DOWN", "LEFT"} DIM ouya_buttons(...) as string = {"O", "A", "U", "Y", "L1", "R1", "L2", "R2", "UP", "RIGHT", "DOWN", "LEFT"} DIM xperia_buttons(...) as string = {"X?", "O?", "Square", "Triangle", "L1", "R1", "Doesn't Exist", "Doesn't Exist", "UP", "RIGHT", "DOWN", "LEFT"} DIM default_scancodes(...) as integer = {scEnter, scESC, scESC, scESC, scPageUp, scPageDown, scHome, scEnd, scUp, scRight, scDown, scLeft} DIM default_scancodes_multiplayer(...) as integer = {scEnter, scESC, scESC, scESC, scPageUp, scPageDown, scHome, scEnd, scUp, scRight, scDown, scLeft} DIM player_who(...) as string = {"First", "Second", "Third", "Fourth"} DIM vbutton_count as integer = 0 DIM vbutton_max as integer = 6 DIM has_player_node(3) as bool DIM pnum as integer DIM t as integer DIM sub_t as integer DIM node as NodePtr DIM sc as integer setkeys DO setwait 55 setkeys IF keyval(scF1) > 1 THEN show_help "general_platform_options" IF keyval(scEsc) > 1 THEN EXIT DO IF st.need_update THEN DO '--normally a single-pass loop. Only repeats if something '-- happens during update that requires another update st.need_update = NO DIM cap as string InitLikeStandardMenu menu append_menu_item menu, "Previous menu..." menu.last->t = platformoptEXIT append_menu_item menu, "Disable Virtual Gamepad: " & yesorno(mobile."disable_virtual_gamepad".exists) menu.last->t = platformoptTOGGLEVGAMEPAD IF NOT mobile."disable_virtual_gamepad".exists THEN append_menu_item menu, "Hide V.Gamepad when suspendplayer: " & yesorno(mobile."hide_virtual_gamepad_when_suspendplayer".integer.default(NO)) menu.last->t = platformoptTOGGLEVGAMEPADSUSPENDPLAYER append_menu_item menu, "Phone/Tablet virtual gamepad (Normal)" menu.last->unselectable = YES menu.last->disabled = YES vbutton_count = 0 READNODE vgp WITHNODE vgp."button" as but vbutton_count += 1 IF vbutton_count > 6 THEN debug "WARNING: not allowed to have more than 6 mobile virtual gamepad buttons" ELSE append_menu_item menu, "Button " & vbutton_count - 1 & ": " & scancode_to_name(GetInteger(but)) menu.last->t = platformoptVBUTTON menu.last->dataptr = but END IF END WITHNODE END READNODE IF vbutton_count < vbutton_max THEN append_menu_item menu, "Add another button" menu.last->t = platformoptADDVBUTTON menu.last->dataptr = vgp END IF append_menu_item menu, "Phone/Tablet virtual gamepad (Battle Mode)" menu.last->unselectable = YES menu.last->disabled = YES vbutton_count = 0 READNODE vgpb WITHNODE vgpb."button" as but vbutton_count += 1 IF vbutton_count > 6 THEN debug "WARNING: not allowed to have more than 6 mobile virtual gamepad buttons" ELSE append_menu_item menu, "Button " & vbutton_count - 1 & ": " & scancode_to_name(GetInteger(but)) menu.last->t = platformoptVBUTTON menu.last->dataptr = but END IF END WITHNODE END READNODE IF vbutton_count < vbutton_max THEN append_menu_item menu, "Add another button" menu.last->t = platformoptADDVBUTTON menu.last->dataptr = vgpb END IF END IF append_menu_item menu, "" menu.last->unselectable = YES append_menu_item menu, "Touch Textboxes: " & yesorno(get_gen_bool("/mobile_options/touch_textboxes/enabled")) menu.last->t = platformoptTOGGLETOUCHTEXTBOXES append_menu_item menu, "" menu.last->unselectable = YES append_menu_item menu, "Gamepad buttons" menu.last->unselectable = YES menu.last->disabled = YES platform_options_append_gamepad_button_menu_items gamepad, menu, NO, buttons(), default_scancodes() append_menu_item menu, "Multiplayer Options:" menu.last->unselectable = YES menu.last->disabled = YES IF NOT gen_root."multiplayer_gamepads".exists THEN '--this caption will change if any other platform ever gets multiplayer gamepad support append_menu_item menu, "Enable OUYA multiplayer..." menu.last->t = platformoptTOGGLEMULTI ELSE flusharray has_player_node(), NO READNODE gen_root."multiplayer_gamepads" as multi WITHNODE multi."player" as player pnum = GetInteger(player) IF pnum < 1 ORELSE pnum > 3 THEN debug "WARNING: Should not have multiplayer_gamepads.player node for player " & pnum ELSE has_player_node(pnum) = YES append_menu_item menu, player_who(pnum) & " player buttons" menu.last->unselectable = YES menu.last->disabled = YES platform_options_append_gamepad_button_menu_items player, menu, YES, buttons(), default_scancodes_multiplayer() END IF END WITHNODE END READNODE FOR i as integer = 1 TO 3 IF NOT has_player_node(i) THEN AppendChildNode(gen_root."multiplayer_gamepads".ptr, "player", i) st.need_update = YES END IF NEXT i END IF append_menu_item menu, "" menu.last->unselectable = YES append_menu_item menu, "Button name strings for text boxes..." menu.last->t = platformoptBUTTONNAMES append_menu_item menu, "" menu.last->unselectable = YES cap = "Console TV Safe Margin: " IF console."safe_margin".exists THEN cap &= console."safe_margin".integer ELSE cap &= "Default" END IF append_menu_item menu, cap menu.last->t = platformoptMARGINS append_menu_item menu, "" menu.last->unselectable = YES append_menu_item menu, "In-App Purchases... (experimental)" menu.last->t = platformoptPURCHASE LOOP WHILE st.need_update init_menu_state st, menu REDIM enabled(menu.numitems - 1) as bool FOR i as integer = 0 TO UBOUND(enabled) WITH *menu.items[i] enabled(i) = NOT .unselectable END WITH NEXT i END IF '--done updating menu usemenu st, enabled() t = menu.items[st.pt]->t sub_t = menu.items[st.pt]->sub_t node = menu.items[st.pt]->dataptr IF enter_space_click(st) THEN SELECT CASE t CASE platformoptEXIT: EXIT DO CASE platformoptTOGGLEVGAMEPAD: ToggleChildNode mobile, "disable_virtual_gamepad" st.need_update = YES CASE platformoptTOGGLEVGAMEPADSUSPENDPLAYER: ToggleBoolChildNode mobile, "hide_virtual_gamepad_when_suspendplayer" st.need_update = YES CASE platformoptTOGGLEMULTI: ToggleChildNode gen_root, "multiplayer_gamepads" st.need_update = YES CASE platformoptTOGGLETOUCHTEXTBOXES: toggle_gen_bool("/mobile_options/touch_textboxes/enabled") st.need_update = YES CASE platformoptBUTTON: sc = prompt_for_scancode() IF sc >= 0 THEN SetChildNode(node, buttons(sub_t), sc) st.need_update = YES END IF CASE platformoptVBUTTON: sc = prompt_for_scancode() IF sc >= 0 THEN SetContent(node, sc) st.need_update = YES END IF CASE platformoptADDVBUTTON: AppendChildNode(node, "button", 0) st.need_update = YES CASE platformoptBUTTONNAMES: edit_button_name_strings CASE platformoptPURCHASE: edit_purchase_options() END SELECT END IF SELECT CASE t CASE platformoptTOGGLEVGAMEPAD: IF keyval(scLeft) > 1 ORELSE keyval(scRight) > 1 THEN ToggleChildNode mobile, "disable_virtual_gamepad" st.need_update = YES END IF CASE platformoptTOGGLEVGAMEPADSUSPENDPLAYER: IF keyval(scLeft) > 1 ORELSE keyval(scRight) > 1 THEN ToggleBoolChildNode mobile, "hide_virtual_gamepad_when_suspendplayer" st.need_update = YES END IF CASE platformoptTOGGLEMULTI: IF keyval(scLeft) > 1 ORELSE keyval(scRight) > 1 THEN ToggleChildNode gen_root, "multiplayer_gamepads" st.need_update = YES END IF CASE platformoptTOGGLETOUCHTEXTBOXES: IF keyval(scLeft) > 1 ORELSE keyval(scRight) > 1 THEN toggle_gen_bool("/mobile_options/touch_textboxes/enabled") st.need_update = YES END IF CASE platformoptBUTTON: IF keyval(scDELETE) > 1 ORELSE keyval(scBACKSPACE) > 1 THEN SetChildNode(node, buttons(sub_t), 0) st.need_update = YES END IF CASE platformoptVBUTTON: IF keyval(scDELETE) > 1 ORELSE keyval(scBACKSPACE) > 1 THEN FreeNode node st.need_update = YES END IF CASE platformoptMARGINS: DIM margins as integer IF console."safe_margin".exists THEN margins = console."safe_margin".integer ELSE margins = 0 END IF IF margins = 0 ANDALSO (keyval(scDelete) > 1 ORELSE keyval(scBackspace) > 1) THEN FreeChildNode console, "safe_margin" st.need_update = YES ELSE IF intgrabber(margins, 0, 10) THEN SetChildNode console, "safe_margin", margins st.need_update = YES END IF END IF END SELECT clearpage dpage IF t = platformoptBUTTON THEN edgeprint "OUYA Controller: " & ouya_buttons(sub_t), 0, pBottom, uilook(uiSelectedDisabled), dpage END IF draw_menu menu, st, dpage SWAP vpage, dpage setvispage vpage dowait LOOP write_general_reld() END SUB SUB platform_options_append_gamepad_button_menu_items (byval gamepad as NodePtr, menu as MenuDef, byval use_dpad as bool, buttons() as string, default_scancodes() as integer) DIM ub as integer = UBOUND(buttons) '--always assumes that the dpad is at the end of the buttons() list IF NOT use_dpad THEN ub = ub - 4 FOR bnum as integer = 0 TO ub DIM b as string = buttons(bnum) DIM bnode as NodePtr = GetOrCreateChild(gamepad, b) DIM cap as string cap = "Button " & rpad(b, " ", large(LEN(b), 2)) & ": " DIM padnum as integer = 12 - large(0, LEN(b) - 2) DIM sc as integer = GetInteger(bnode) IF sc THEN cap &= rpad(scancode_to_name(sc), " ", padnum) & "(" & sc & ")" ELSE cap &= rpad(scancode_to_name(default_scancodes(bnum)), " ", padnum) & "(Default)" END IF append_menu_item menu, cap menu.last->t = platformoptBUTTON menu.last->sub_t = bnum menu.last->dataptr = gamepad NEXT bnum END SUB FUNCTION prompt_for_scancode () as integer 'Return a user-specified scancode, or -1 if cancelled. DIM result as integer = -1 DIM instructions as string = "To choose a key, press and hold the desired key until the flashing bar fills. Tap ESC briefly to cancel." DIM toomany as string = "That is too many keys, please just press one." DIM nowrelease as string = "New key confirmed! Now release it." DIM pressed as integer DIM presstime as double ' the timestamp when the key was first pressed DIM heldfor as double ' the number of seconds the key has been held DIM threshold as double = 1. DIM keylist as string DIM keyname as string DIM count as integer DIM confirmed as integer = 0 DIM bartog as bool = NO DIM root as Slice Ptr root = NewSliceOfType(slRoot) WITH *root .Fill = YES END WITH DIM box as Slice Ptr box = NewSliceOfType(slRectangle, root) WITH *box .width = 308 .height = 48 .AnchorHoriz = 1 .AnchorVert = 2 .AlignHoriz = 1 .AlignVert = 2 .paddingLeft = 8 .paddingRight = 8 .paddingTop = 8 .paddingBottom = 8 END WITH ChangeRectangleSlice box, 1 DIM infosl as Slice Ptr infosl = NewSliceofType(slText, box) infosl->Fill = YES ChangeTextSlice infosl, instructions, , , YES DIM bar as Slice Ptr bar = NewSliceOfType(slRectangle, root) WITH *bar .width = 0 .height = 10 .AnchorHoriz = 1 .AnchorVert = 0 .AlignHoriz = 1 .AlignVert = 0 END WITH DIM keybox as Slice Ptr keybox = NewSliceofType(slContainer, root) WITH *keybox .AnchorHoriz = 1 .AnchorVert = 1 .AlignHoriz = 1 .AlignVert = 1 END WITH DIM keysl as Slice Ptr keysl = NewSliceofType(slText, keybox) setkeys DO setwait 55 setkeys IF confirmed THEN 'A key has been confirmed, wait for it to be released ChangeTextSlice infosl, nowrelease bartog = NO IF keyval(pressed) = 0 THEN result = pressed EXIT DO END IF ELSE keylist = "" count = 0 FOR i as integer = 1 to 127 IF keyval(i) > 0 THEN keyname = scancode_to_name(i) IF keyname <> "" THEN count += 1 IF pressed <> i THEN presstime = TIMER END IF pressed = i keylist &= keyname & !"\n" IF count = 1 THEN keybox->width = LEN(keyname) * 8 END IF keybox->height = count * 10 END IF END IF NEXT i ChangeTextSlice keysl, keylist ChangeTextSlice infosl, instructions IF count = 0 THEN 'No buttons are being held down. IF pressed = scESC THEN EXIT DO IF pressed = scF1 THEN show_help "prompt_for_scancode" pressed = 0 bartog = NO bar->width = 0 ELSEIF count > 1 THEN 'Too manu buttons are being held down, so none of them count. ChangeTextSlice infosl, toomany pressed = 0 bartog = NO bar->width = 0 ELSE 'Just one button is being held down, so it might be valid heldfor = TIMER - presstime bar->width = int(root->width / threshold * heldfor) bartog = NOT bartog IF heldfor > threshold THEN confirmed = pressed END IF END IF END IF IF bartog THEN ChangeRectangleSlice bar, , uiTimeBar, uiTimeBarFull ELSE ChangeRectangleSlice bar, , uiTimeBarFull, uiTimeBar END IF clearpage dpage DrawSlice root, dpage SWAP vpage, dpage setvispage vpage dowait LOOP DeleteSlice @root RETURN result END FUNCTION FUNCTION scancode_to_name(byval sc as integer) as string 'Return a string name for a given scancode, or "" if it doesn't know what the scancode is. 'Only includes scancodes that gfx_sdl knows how to translate into SDL scancodes STATIC scNames(127) as string STATIC already_init as bool = NO IF NOT already_init THEN scNames(scBackspace) = "BACKSPACE" scNames(scTab) = "TAB" scNames(scEnter) = "ENTER" scNames(scPause) = "PAUSE" scNames(scEsc) = "ESC" scNames(scSpace) = "SPACE" scNames(scComma) = "," scNames(scMinus) = "-" scNames(scPeriod) = "." scNames(scSlash) = "/" scNames(sc0) = "0" scNames(sc1) = "1" scNames(sc2) = "2" scNames(sc3) = "3" scNames(sc4) = "4" scNames(sc5) = "5" scNames(sc6) = "6" scNames(sc7) = "7" scNames(sc8) = "8" scNames(sc9) = "9" scNames(scSemicolon) = ";" scNames(scEquals) = "=" scNames(scLeftBracket) = "[" scNames(scBackslash) = "\" scNames(scRightBracket) = "]" scNames(scBackquote) = "`" scNames(scA) = "A" scNames(scB) = "B" scNames(scC) = "C" scNames(scD) = "D" scNames(scE) = "E" scNames(scF) = "F" scNames(scG) = "G" scNames(scH) = "H" scNames(scI) = "I" scNames(scJ) = "J" scNames(scK) = "K" scNames(scL) = "L" scNames(scM) = "M" scNames(scN) = "N" scNames(scO) = "O" scNames(scP) = "P" scNames(scQ) = "Q" scNames(scR) = "R" scNames(scS) = "S" scNames(scT) = "T" scNames(scU) = "U" scNames(scV) = "V" scNames(scW) = "W" scNames(scX) = "X" scNames(scY) = "Y" scNames(scZ) = "Z" scNames(scDelete) = "DEL" scNames(scNumpad0) = "Numpad 0" scNames(scNumpad1) = "Numpad 1" scNames(scNumpad2) = "Numpad 2" scNames(scNumpad3) = "Numpad 3" scNames(scNumpad4) = "Numpad 4" scNames(scNumpad5) = "Numpad 5" scNames(scNumpad6) = "Numpad 6" scNames(scNumpad7) = "Numpad 7" scNames(scNumpad8) = "Numpad 8" scNames(scNumpad9) = "Numpad 9" scNames(scNumpadPeriod) = "Numpad ." scNames(scNumpadSlash) = "Numpad /" scNames(scNumpadAsterisk) = "Numpad *" scNames(scNumpadMinus) = "Numpad -" scNames(scNumpadPlus) = "Numpad +" scNames(scNumpadEnter) = "Numpad ENTER" scNames(scUp) = "UP" scNames(scDown) = "DOWN" scNames(scRight) = "RIGHT" scNames(scLeft) = "LEFT" scNames(scInsert) = "INSTER" scNames(scHome) = "HOME" scNames(scEnd) = "END" scNames(scPageup) = "PAGEUP" scNames(scPagedown) = "PAGEDOWN" scNames(scF1) = "F1" scNames(scF2) = "F2" scNames(scF3) = "F3" scNames(scF4) = "F4" scNames(scF5) = "F5" scNames(scF6) = "F6" scNames(scF7) = "F7" scNames(scF8) = "F8" scNames(scF9) = "F9" scNames(scF10) = "F10" scNames(scF11) = "F11" scNames(scF12) = "F12" scNames(scF13) = "F13" scNames(scF14) = "F14" scNames(scF15) = "F15" scNames(scNumlock) = "NUMLOCK" scNames(scCapslock) = "CAPSLOCK" scNames(scScrollLock) = "SCROLLLOCK" scNames(scRightShift) = "Right SHIFT" scNames(scLeftShift) = "Left SHIFT" scNames(scRightCtrl) = "Right CTRL" scNames(scLeftCtrl) = "Left CTRL" scNames(scRightAlt) = "Right ALT" scNames(scLeftAlt) = "Left ALT" scNames(scRightCommand) = "Right COMMAND" scNames(scLeftCommand) = "Left COMMAND" scNames(scLeftWinLogo) = "Left SUPER" scNames(scRightWinLogo) = "Right SUPER" scNames(scPrintScreen) = "PRINTSCREEN" scNames(scContext) = "CONTEXT" already_init = YES END IF return scNames(sc) END FUNCTION CONST buttonnameEXIT as integer = 0 CONST buttonnameADD as integer = 1 CONST buttonnameDATA as integer = 2 CONST buttonnameBROWSE as integer = 3 SUB edit_button_name_strings() DIM plat_key(...) as string = {"keyboard", "touchscreen", "ouya", "console"} DIM plat_detail(...) as string = {_ "Windows, Mac and Linux",_ "Any touchscreen device (Android)",_ "OUYA Console",_ "Other Android Consoles"} DIM menu as MenuDef DIM keynum as integer = 0 DIM st as MenuState st.active = YES st.need_update = YES REDIM enabled(0) as bool DIM gen_root as NodePtr = get_general_reld() '-- the buttonnames node will be created with defaults in '-- update_general_data() if it does not exist already. DIM buttonnames as NodePtr = gen_root."buttonnames".ptr DIM codenum as integer DIM freenum as integer DIM t as integer DIM node as NodePtr DIM strnode as NodePtr DIM curstr as string setkeys YES DO setwait 55 setkeys YES IF keyval(scF1) > 1 THEN show_help "edit_button_name_strings" IF keyval(scEsc) > 1 THEN EXIT DO IF st.need_update THEN st.need_update = NO InitLikeStandardMenu menu append_menu_item menu, "Previous menu..." menu.last->t = buttonnameEXIT append_menu_item menu, CHR(27) & " Platform: " & plat_key(keynum) & " " & CHR(26) menu.last->t = buttonnameBROWSE append_menu_item menu, plat_detail(keynum) menu.last->unselectable = YES menu.last->disabled = YES freenum = 0 READNODE buttonnames WITHNODE buttonnames."code" as codenode codenum = GetInteger(codenode) IF freenum <= codenum THEN freenum = codenum + 1 curstr = GetChildNodeStr(codenode, plat_key(keynum), default_button_name_for_platform(plat_key(keynum), codenum)) append_menu_item menu, "${B" & codenum & "} = " & curstr menu.last->t = buttonnameDATA menu.last->dataptr = codenode END WITHNODE END READNODE append_menu_item menu, "Add a new button name" menu.last->t = buttonnameADD init_menu_state st, menu REDIM enabled(menu.numitems - 1) as bool FOR i as integer = 0 TO UBOUND(enabled) WITH *menu.items[i] enabled(i) = NOT .unselectable END WITH NEXT i END IF '--done updating menu usemenu st, enabled() t = menu.items[st.pt]->t node = menu.items[st.pt]->dataptr IF t = buttonnameBROWSE THEN IF keyval(scLeft) > 1 THEN keynum = loopvar(keynum, 0, UBOUND(plat_key), -1) st.need_update = YES END IF IF keyval(scRight) > 1 THEN keynum = loopvar(keynum, 0, UBOUND(plat_key), 1) st.need_update = YES END IF ELSEIF t = buttonnameDATA THEN curstr = GetChildNodeStr(node, plat_key(keynum), default_button_name_for_platform(plat_key(keynum), GetInteger(node))) IF strgrabber(curstr, 16) THEN SetChildNode(node, plat_key(keynum), curstr) st.need_update = YES END IF END IF IF enter_space_click(st) THEN SELECT CASE t CASE buttonnameEXIT: EXIT DO CASE buttonnameADD: DIM newcodenode as NodePtr = AppendChildNode(buttonnames, "code", freenum) FOR i as integer = 0 TO UBOUND(plat_key) AppendChildNode(newcodenode, plat_key(i), "") NEXT i st.need_update = YES END SELECT END IF clearpage dpage draw_menu menu, st, dpage SWAP vpage, dpage setvispage vpage dowait LOOP END SUB CONST editpurchEXIT as integer = 1 CONST editpurchSTRING as integer = 2 CONST editpurchTAGSET as integer = 3 CONST editpurchADD as integer = 4 CONST editpurchPROD as integer = 5 CONST editpurchLONGSTRING as integer = 6 CONST editpurchINT as integer = 7 CONST editpurchSTOREDEFAULT as integer = 8 CONST editpurchSTOREOUYA as integer = 9 CONST editpurchTOGGLEREPEATABLE as integer = 10 'sucks that this is so specific CONST editpurchKEYDER as integer = 11 SUB edit_purchase_options () DIM menu as MenuDef DIM st as MenuState st.active = YES st.need_update = YES REDIM enabled(0) as bool DIM gen_root as NodePtr = get_general_reld() DIM purchase_root as NodePtr = GetOrCreateChild(gen_root, "purchase_root") DIM products as NodePtr = GetOrCreateChild(purchase_root, "products") DIM t as integer DIM sub_t as integer DIM node as NodePtr DIM default_stores(1) as string = {"disabled", "paypal"} DIM ouya_stores(1) as string = {"disabled", "ouya"} setkeys YES DO setwait 55 setkeys YES IF keyval(scF1) > 1 THEN show_help "edit_purchase_options" IF keyval(scEsc) > 1 THEN EXIT DO IF st.need_update THEN st.need_update = NO edit_purchase_options_rebuild_menu menu, st, purchase_root, enabled() END IF '--done updating menu usemenu st, enabled() t = menu.items[st.pt]->t sub_t = menu.items[st.pt]->sub_t node = menu.items[st.pt]->dataptr SELECT CASE t CASE editpurchSTRING: IF node THEN DIM curstr as string curstr = GetString(node) IF strgrabber(curstr, sub_t) THEN SetContent(node, curstr) st.need_update = YES END IF END IF CASE editpurchSTOREDEFAULT: IF edit_purchase_enumgrabber(node, default_stores()) THEN st.need_update = YES CASE editpurchSTOREOUYA: IF edit_purchase_enumgrabber(node, ouya_stores()) THEN st.need_update = YES END SELECT IF enter_space_click(st) THEN SELECT CASE t CASE editpurchEXIT: EXIT DO CASE editpurchADD: DIM newnode as NodePtr = AppendChildNode(products, "prod") st.need_update = YES CASE editpurchPROD: edit_purchase_details node st.need_update = YES CASE editpurchSTOREDEFAULT: IF edit_purchase_enumbrowse("Store for In-App Purchases", node, default_stores(), "edit_purchase_options") THEN st.need_update = YES CASE editpurchSTOREOUYA: IF edit_purchase_enumbrowse("Store for In-App Purchases", node, ouya_stores(), "edit_purchase_options") THEN st.need_update = YES CASE editpurchKEYDER: DIM key_der_file as string key_der_file = browse(0, "", "key.der", "edit_purchase_browse_ouya_key_der") IF key_der_file <> "" THEN IF isfile(key_der_file) THEN SetContent(node, string_from_file(key_der_file)) st.need_update = YES ELSE pop_warning "Unable to read the file """ & decode_filename(key_der_file) & """" END IF END IF END SELECT END IF IF keyval(scDelete) > 1 THEN IF yesno("Delete the selected product?", NO, NO) THEN FreeNode node st.need_update = YES END IF END IF clearpage dpage draw_menu menu, st, dpage wrapprint "Don't enable purchases unless all the content if your game is original!", 0, pBottom, uilook(uiSelectedDisabled), dpage SWAP vpage, dpage setvispage vpage dowait LOOP ClearMenuData menu END SUB SUB edit_purchase_options_rebuild_menu(menu as MenuDef, st as MenuState, byval purchase_root as NodePtr, enabled() as bool) DIM store_node as NodePtr = GetOrCreateChild(purchase_root, "stores_by_platform") DIM ouya_node as NodePtr = GetOrCreateChild(purchase_root, "ouya") DIM products as NodePtr = GetOrCreateChild(purchase_root, "products") DIM node as NodePtr DIM cap as string InitLikeStandardMenu menu append_menu_item menu, "Previous menu..." menu.last->t = editpurchEXIT append_menu_item menu, "(This menu is experimental!)" menu.last->unselectable = YES menu.last->disabled = YES append_menu_item menu, "Enabled Stores:" menu.last->unselectable = YES menu.last->disabled = YES node = GetOrCreateChild(store_node, "default") append_menu_item menu, "Windows+Mac+Linux: " & blank_default(GetString(node), "disabled") menu.last->t = editpurchSTOREDEFAULT menu.last->dataptr = node node = GetOrCreateChild(store_node, "ouya") append_menu_item menu, "OUYA Console: " & blank_default(GetString(node), "disabled") menu.last->t = editpurchSTOREOUYA menu.last->dataptr = node append_menu_item menu, "OUYA Developer ID:" menu.last->unselectable = YES menu.last->disabled = YES node = GetOrCreateChild(ouya_node, "developer_id") append_menu_item menu, "> " & GetString(node) menu.last->t = editpurchSTRING menu.last->sub_t = 36 menu.last->dataptr = node node = GetOrCreateChild(ouya_node, "key.der") DIM key_der_size as integer = LEN(GetString(node)) IF key_der_size THEN cap = "Replace OUYA key.der file (" & key_der_size & " bytes)" ELSE cap = "Import OUYA key.der file" END IF append_menu_item menu, cap menu.last->t = editpurchKEYDER menu.last->dataptr = node append_menu_item menu, "Purchaseable Products" menu.last->unselectable = YES menu.last->disabled = YES READNODE products WITHNODE products."prod" as prod DIM n as NodePtr n = GetOrCreateChild(prod, "displayname") append_menu_item menu, "Product: " & GetString(n) menu.last->t = editpurchPROD menu.last->dataptr = prod END WITHNODE END READNODE append_menu_item menu, "Add a new product" menu.last->t = editpurchADD init_menu_state st, menu REDIM enabled(menu.numitems - 1) as bool FOR i as integer = 0 TO UBOUND(enabled) WITH *menu.items[i] enabled(i) = NOT .unselectable END WITH NEXT i END SUB FUNCTION edit_purchase_enumbrowse(caption as string, byval node as NodePtr, enumstr() as string, helpkey as string) as bool DIM choice as integer DIM old as integer old = str_array_find(enumstr(), GetString(node), 0) choice = multichoice(caption, enumstr(), old, old, helpkey) IF choice <> old THEN SetContent(node, enumstr(choice)) RETURN YES END IF RETURN NO END FUNCTION FUNCTION edit_purchase_enumgrabber(byval node as NodePtr, enumstr() as string) as bool DIM choice as integer choice = str_array_find(enumstr(), GetString(node), 0) IF intgrabber(choice, 0, UBOUND(enumstr)) THEN SetContent(node, enumstr(choice)) RETURN YES END IF RETURN NO END FUNCTION SUB edit_purchase_details (byval prod as NodePtr) DIM menu as MenuDef DIM st as MenuState st.active = YES st.need_update = YES REDIM enabled(0) as bool DIM t as integer DIM sub_t as integer DIM node as NodePtr DIM n as NodePtr DIM store as NodePtr DIM buy_action as NodePtr DIM cap as string DIM num as integer setkeys YES DO setwait 55 setkeys YES IF keyval(scF1) > 1 THEN show_help "edit_purchase_details" IF keyval(scEsc) > 1 THEN EXIT DO IF st.need_update THEN st.need_update = NO InitLikeStandardMenu menu append_menu_item menu, "Previous menu..." menu.last->t = editpurchEXIT n = GetOrCreateChild(prod, "displayname") append_menu_item menu, "Name: " & GetString(n) menu.last->t = editpurchSTRING menu.last->sub_t = 128 menu.last->dataptr = n n = GetOrCreateChild(prod, "description") append_menu_item menu, "Description: " & GetString(n) menu.last->t = editpurchLONGSTRING menu.last->sub_t = 99999 menu.last->dataptr = n append_menu_item menu, "Can be purchased: " & IIF(prod."repeatable".exists, "Repeatedly", "Only Once") menu.last->t = editpurchTOGGLEREPEATABLE menu.last->dataptr = prod n = GetOrCreateChild(prod, "already") IF GetString(n) = "" THEN SetContent(n, "Already purchased") append_menu_item menu, "Already caption: " & GetString(n) menu.last->t = editpurchLONGSTRING menu.last->sub_t = 99999 menu.last->dataptr = n append_menu_item menu, "OUYA Store Settings" menu.last->unselectable = YES menu.last->disabled = YES store = GetOrCreateChild(prod, "ouya") n = GetOrCreateChild(store, "identifier") append_menu_item menu, "Identifier: " & GetString(n) menu.last->t = editpurchSTRING menu.last->sub_t = 128 menu.last->dataptr = n append_menu_item menu, "Paypal Settings" menu.last->unselectable = YES menu.last->disabled = YES store = GetOrCreateChild(prod, "paypal") n = GetOrCreateChild(store, "button_id") append_menu_item menu, "Button ID: " & GetString(n) menu.last->t = editpurchSTRING menu.last->sub_t = 128 menu.last->dataptr = n append_menu_item menu, "Purchase Action" menu.last->unselectable = YES menu.last->disabled = YES buy_action = GetOrCreateChild(prod, "buy_action") n = GetOrCreateChild(buy_action, "tag") num = GetInteger(n) cap = "Turn ON Tag " IF num > 1 THEN cap &= num & " (" & load_tag_name(num) & ")" ELSEIF num = 1 THEN cap &= num & " [Cannot be changed!]" ELSE cap &= "[None selected]" END IF append_menu_item menu, cap menu.last->t = editpurchTAGSET menu.last->dataptr = n n = GetOrCreateChild(buy_action, "global") num = GetInteger(n) cap = "+1 to Global Variable ID " & zero_default(num, "[None selected]") append_menu_item menu, cap menu.last->t = editpurchINT menu.last->sub_t = maxScriptGlobals menu.last->dataptr = n n = GetOrCreateChild(buy_action, "thanks") IF GetString(n) = "" THEN SetContent(n, "Thank you!") append_menu_item menu, "Thanks Message: " & GetString(n) menu.last->t = editpurchLONGSTRING menu.last->sub_t = 99999 menu.last->dataptr = n init_menu_state st, menu REDIM enabled(menu.numitems - 1) as bool FOR i as integer = 0 TO UBOUND(enabled) WITH *menu.items[i] enabled(i) = NOT .unselectable END WITH NEXT i END IF '--done updating menu usemenu st, enabled() t = menu.items[st.pt]->t sub_t = menu.items[st.pt]->sub_t node = menu.items[st.pt]->dataptr SELECT CASE t CASE editpurchSTRING, editpurchLONGSTRING: IF node THEN DIM curstr as string curstr = GetString(node) IF strgrabber(curstr, sub_t) THEN SetContent(node, curstr) st.need_update = YES END IF END IF CASE editpurchTAGSET: IF node THEN DIM curint as integer curint = GetInteger(node) IF intgrabber(curint, 0, 99999) THEN SetContent(node, curint) st.need_update = YES END IF END IF CASE editpurchINT: IF node THEN DIM curint as integer curint = GetInteger(node) IF intgrabber(curint, 0, sub_t) THEN SetContent(node, curint) st.need_update = YES END IF END IF CASE editpurchTOGGLEREPEATABLE: IF enter_space_click(st) ORELSE keyval(scLeft) > 1 ORELSE keyval(scRight) > 1 THEN ToggleChildNode node, "repeatable" st.need_update = YES END IF END SELECT IF enter_space_click(st) THEN SELECT CASE t CASE editpurchEXIT: EXIT DO CASE editpurchTAGSET: SetContent(node, tags_menu(GetInteger(node), YES, NO)) st.need_update = YES END SELECT END IF IF keyval(scAnyEnter) > 1 THEN SELECT CASE t CASE editpurchLONGSTRING: SetContent(node, multiline_string_editor(GetString(node))) st.need_update = YES END SELECT END IF clearpage dpage draw_menu menu, st, dpage SWAP vpage, dpage setvispage vpage dowait LOOP ClearMenuData menu END SUB SUB generate_savegame_options_menu(menu() as string, save_options_node as Node ptr) menu(0) = "Previous Menu..." menu(1) = "Number of save/load slots: " & gen(genSaveSlotCount) menu(2) = "Save script/sprite layer slices: " & yesorno(save_options_node."sprite_layer".exists) menu(3) = "Save strings: " & yesorno(save_options_node."strings".exists) END SUB SUB edit_savegame_options CONST maxMenu = 3 DIM menu(maxMenu) as string DIM menu_display(maxMenu) as string DIM selectst as SelectTypeState DIM state as MenuState WITH state .size = 24 .last = maxMenu .need_update = YES END WITH DIM gen_root as NodePtr = get_general_reld() 'Create if it doesn't exist DIM save_options_node as NodePtr = GetOrCreateChild(gen_root, "saved_games") 'Set default IF gen(genSaveSlotCount) <= 0 THEN gen(genSaveSlotCount) = 4 setkeys DO setwait 55 setkeys YES usemenu state IF keyval(scF1) > 1 THEN show_help "savegame_options" IF keyval(scEsc) > 1 THEN EXIT DO IF state.pt = 0 THEN IF enter_space_click(state) THEN EXIT DO ELSEIF state.pt = 1 THEN 'save slot count IF intgrabber(gen(genSaveSlotCount), 1, 32) THEN state.need_update = YES END IF IF keyval(scLeft) > 1 OR keyval(scRight) > 1 OR enter_space_click(state) THEN IF state.pt = 2 THEN 'save slices ToggleChildNode save_options_node, "sprite_layer" ELSEIF state.pt = 3 THEN 'save plotstrings ToggleChildNode save_options_node, "strings" END IF state.need_update = YES END IF IF state.need_update THEN state.need_update = NO generate_savegame_options_menu menu(), save_options_node END IF IF select_by_typing(selectst, NO) THEN select_on_word_boundary menu(), selectst, state END IF clearpage dpage highlight_menu_typing_selection menu(), menu_display(), selectst, state standardmenu menu_display(), state, 0, 0, dpage SWAP vpage, dpage setvispage vpage dowait LOOP write_general_reld END SUB FUNCTION npc_preview_text(byref npc as NPCType) as string 'This is certaily not an attempt to show everything one might need to 'know about an NPC, simply a way of showing a little more data so that 'NPCs with the same sprite can be easily distinguished in the editor at a glance IF npc.textbox > 0 THEN RETURN textbox_preview_line(npc.textbox) IF npc.script <> 0 THEN RETURN scriptname(npc.script) & " [SCRIPT]" IF npc.item > 0 THEN RETURN readitemname(npc.item - 1) & " [ITEM]" IF npc.vehicle > 0 THEN RETURN load_vehicle_name(npc.vehicle - 1) & " [VEHICLE]" END FUNCTION SUB mark_non_elemental_elementals () DIM g as NodePtr = get_general_reld() DIM elementals as NodePtr = GetOrCreateChild(g, "elementals") DIM non_elemental as NodePtr DIM eid as integer DIM bits(3) as integer DIM names() as string getelementnames names() FOR i as integer = 0 to UBOUND(names) names(i) = "Treat " & names(i) & " attacks as non-elemental" NEXT i READNODE elementals WITHNODE elementals."element" as e eid = GetInteger(e) IF eid >= 0 ANDALSO eid <= 63 THEN non_elemental = GetOrCreateChild(e, "non_elemental") setbit bits(), 0, eid, GetInteger(non_elemental) END IF END WITHNODE END READNODE editbitset bits(), 0, gen(genNumElements) - 1, names(), "non_elemental_elementals" DIM saved(63) as bool READNODE elementals WITHNODE elementals."element" as e eid = GetInteger(e) IF eid >= 0 ANDALSO eid <= 63 THEN non_elemental = GetOrCreateChild(e, "non_elemental") SetContent non_elemental, IIF(xreadbit(bits(), eid), 1, 0) saved(eid) = YES END IF END WITHNODE END READNODE DIM e as NodePtr FOR i as integer = 0 TO 63 IF NOT saved(i) THEN e = AppendChildNode(elementals, "element", i) SetChildNode(e, "non_elemental", IIF(xreadbit(bits(), i), 1, 0)) END IF NEXT i write_general_reld() END SUB FUNCTION custom_setoption(opt as string, arg as string) as integer IF opt = "distrib" THEN SELECT CASE arg CASE "zip", "win", "mac", "tarball", "tarball64", "tarball32", "debian", "debian64", "debian32", "all" auto_distrib = arg RETURN 2 END SELECT debug "-distrib doesn't know how to export """ & arg & """" RETURN 1 END IF RETURN 0 END FUNCTION