'OHRRPGCE GAME - Various unsorted routines '(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions 'Please read LICENSE.txt for GPL License details and disclaimer of liability #include "config.bi" #include "udts.bi" #include "misc.bi" #include "allmodex.bi" #include "common.bi" #include "gglobals.bi" #include "const.bi" #include "scrconst.bi" #include "uiconst.bi" #include "loading.bi" #include "slices.bi" #include "savegame.bi" #include "game.bi" #include "game_udts.bi" #include "walkabouts.bi" #include "scriptcommands.bi" #include "yetmore2.bi" #include "moresubs.bi" #include "menustuf.bi" #include "bmodsubs.bi" #include "purchase.bi" #include "scripting.bi" '--Local subs and functions DECLARE SUB teleporttool_load_map (map as integer, maptiles2() as TileMap, pass2 as TileMap, tilesets2() as TilesetData ptr) DECLARE SUB teleporttool_generate_minimap(byref mini as Frame Ptr, maptilesX() as TileMap, passX as TileMap, tilesetsX() as TilesetData ptr, byref zoom as integer, byref maxzoom as integer, byref mapsize as XYPair, byref minisize as XYPair, byref offset as XYPair, byref camera as XYPair, dest as XYPair) DECLARE SUB inventory_overflow_handler(byval item_id as integer, byval numitems as integer) DECLARE SUB hero_swap_menu_init(st as OrderTeamState) DECLARE SUB hero_swap_menu_display (st as OrderTeamState) DECLARE SUB hero_swap_menu_mouse_control (st as OrderTeamState) 'who is the hero id SUB addhero (who as integer, slot as integer, forcelevel as integer = -1, allow_rename as bool = YES, loading as bool = NO) DIM her as HeroDef IF who > gen(genMaxHero) THEN showerror "Can't add hero " & who & ", no such hero!" IF party_size() > 0 THEN EXIT SUB 'Adding an initial hero to the party '(upgrade() normally ensures gen(genStartHero) is valid, but if live-previewing 'things can became invalid, and upgrade() isn't called anyway) who = 0 END IF '--load hero's data loadherodata her, who '--do level forcing IF forcelevel >= 0 THEN her.def_level = forcelevel '--do average level enforcement IF her.def_level < 0 THEN her.def_level = averagelev WITH gam.hero(slot) '--formally add hero .id = who '---MUST SET DEFAULT EQUIP--- FOR i as integer = 0 TO UBOUND(.equip) .equip(i).id = -1 NEXT i .equip(0).id = her.def_weapon END WITH '--fill in stats WITH gam.hero(slot).stat FOR statnum as integer = 0 TO statLast .base.sta(statnum) = atlevel(her.def_level, her.Lev0.sta(statnum), her.LevMax.sta(statnum)) NEXT recompute_hero_max_stats slot FOR statnum as integer = 0 TO statLast .cur.sta(statnum) = .max.sta(statnum) NEXT END WITH '--put spells in spell list FOR i as integer = 0 TO 3 FOR o as integer = 0 TO 23 gam.hero(slot).spells(i, o) = 0 WITH her.spell_lists(i, o) IF .attack > 0 AND .learned - 1 <= her.def_level AND .learned > 0 THEN gam.hero(slot).spells(i, o) = .attack END IF END WITH NEXT o NEXT i '--damage from elements FOR i as integer = 0 TO gen(genNumElements) - 1 gam.hero(slot).elementals(i) = her.elementals(i) NEXT '--setup experience gam.hero(slot).exp_mult = her.exp_mult gam.hero(slot).lev = her.def_level gam.hero(slot).lev_gain = 0 gam.hero(slot).exp_cur = 0 gam.hero(slot).exp_next = exptolevel(her.def_level + 1, her.exp_mult) '--reset levelmp reset_levelmp gam.hero(slot) '--clear learnmask slots (just to be really thorough) flusharray gam.hero(slot).learnmask() '--heros are added unlocked gam.hero(slot).locked = NO '--appearance settings ' udts are self documenting WITH gam.hero(slot) .battle_pic = her.sprite .battle_pal = her.sprite_pal .pic = her.walk_sprite .pal = her.walk_sprite_pal .def_wep = her.def_weapon + 1 'default weapon FOR i as integer = 0 to 1 .hand_pos(i) = her.hand_pos(i) NEXT i .hand_pos_overridden = NO .portrait_pic = her.portrait .portrait_pal = her.portrait_pal .sl = create_hero_slices(slot) END WITH '--name gam.hero(slot).name = her.name gam.hero(slot).rename_on_status = xreadbit(her.bits(), 25) '--if renaming is permitted, do it IF allow_rename ANDALSO readbit(her.bits(), 0, 24) THEN '--add-hero rename is allowed renamehero slot, NO END IF '--update hero auto-set tags and herow party_change_updates IF gen(genAddHeroScript) > 0 THEN 'Prioritise to ensure it runs before the newgame script trigger_script gen(genAddHeroScript), 3, NO, "add hero", context_string, mainFibreGroup, 100 trigger_script_arg 0, slot, "slot" trigger_script_arg 1, who, "hero ID" trigger_script_arg 2, IIF(loading, 1, 0), "loading" END IF END SUB 'resetting_game=NO only when cleaning up while resetting/quitting. 'In that case, the caller should wipe Slice ptrs in herow()! SUB deletehero(slot as integer, resetting_game as bool = NO) IF resetting_game = NO ANDALSO gen(genRemoveHeroScript) > 0 THEN trigger_script gen(genRemoveHeroScript), 2, NO, "delete hero", context_string, mainFibreGroup, 100 trigger_script_arg 0, slot, "slot" trigger_script_arg 1, gam.hero(slot).id, "hero ID" END IF gam.hero(slot).id = -1 gam.hero(slot).name &= "(deleted)" DeleteSlice @gam.hero(slot).sl IF resetting_game = NO THEN IF active_party_size() = 0 THEN forceparty party_change_updates END IF END SUB FUNCTION averagelev () as integer DIM average as integer = 0 DIM count as integer = 0 FOR i as integer = 0 TO 3 IF gam.hero(i).id >= 0 THEN average += gam.hero(i).lev: count += 1 NEXT i IF count > 0 THEN average = average / count RETURN average END FUNCTION FUNCTION consumeitem (byval invslot as integer) as bool '--Subtracts one of an item at a location. If the item is depleted, returns true. If there are some of the item left, it returns false '--Argument is the inventory slot index, not the item ID consumeitem = 0 inventory(invslot).num -= 1 IF inventory(invslot).num <= 0 THEN inventory(invslot).used = NO consumeitem = -1 END IF update_inventory_caption invslot END FUNCTION FUNCTION countitem (byval item_id as integer) as integer DIM total as integer = 0 FOR o as integer = 0 TO last_inv_slot() IF inventory(o).used AND item_id = inventory(o).id THEN total += inventory(o).num END IF NEXT o RETURN total END FUNCTION FUNCTION count_equipped_item(byval item_id as integer) as integer DIM count as integer = 0 FOR slot as integer = 0 to 3 WITH gam.hero(slot) IF .id < 0 THEN CONTINUE FOR FOR eqslot as integer = 0 TO UBOUND(.equip) IF .equip(eqslot).id = item_id THEN count += 1 NEXT eqslot END WITH NEXT slot RETURN count END FUNCTION 'Add to inventory. item_id is not offset. 'Returns true if all items fitted in inventory (may fit just some in and return false!) 'ok_to_fail is intended for when the caller wants to handle overflow itself. FUNCTION getitem (byval item_id as integer, byval num as integer=1, byval ok_to_fail as bool=NO) as bool DIM numitems as integer = num DIM room as integer DIM stacksize as integer = get_item_stack_size(item_id) FOR i as integer = 0 TO last_inv_slot() ' Loop through all inventory slots looking for a slot that already ' contains the item we are adding. If found increment that slot room = stacksize - inventory(i).num IF inventory(i).used AND item_id = inventory(i).id AND room > 0 THEN IF room < numitems THEN inventory(i).num = stacksize update_inventory_caption i numitems -= room ELSE inventory(i).num += numitems update_inventory_caption i RETURN YES END IF END IF NEXT i FOR i as integer = 0 TO last_inv_slot() 'loop through each inventory slot looking for an empty slot to populate IF inventory(i).used = 0 THEN inventory(i).used = -1 inventory(i).id = item_id inventory(i).num = small(numitems, stacksize) numitems -= inventory(i).num update_inventory_caption i IF numitems = 0 THEN RETURN YES END IF NEXT 'No slot was found to put this item into! IF ok_to_fail = NO THEN inventory_overflow_handler item_id, numitems END IF RETURN NO END FUNCTION SUB inventory_overflow_handler(byval item_id as integer, byval numitems as integer) debug "Didn't have room for " & readitemname(item_id) & "x" & numitems END SUB FUNCTION room_for_item (byval item_id as integer, byval num as integer = 1) as bool DIM room as integer DIM stacksize as integer = get_item_stack_size(item_id) FOR i as integer = 0 TO last_inv_slot() ' Loop through all inventory slots looking for a slot that already contains the item room = stacksize - inventory(i).num IF inventory(i).used AND item_id = inventory(i).id AND room > 0 THEN IF room >= num THEN RETURN YES END IF num -= room END IF NEXT FOR i as integer = 0 TO last_inv_slot() 'loop through each inventory slot looking for an empty slot to populate IF inventory(i).used = NO THEN IF num <= stacksize THEN RETURN YES END IF num -= stacksize END IF NEXT RETURN NO END FUNCTION SUB delitem (byval item_id as integer, byval amount as integer=1) FOR o as integer = 0 TO last_inv_slot() IF inventory(o).used AND item_id = inventory(o).id THEN IF inventory(o).num <= amount THEN amount -= inventory(o).num inventory(o).num = 0 inventory(o).id = 0 inventory(o).used = NO ELSE inventory(o).num -= amount amount = 0 END IF update_inventory_caption o IF amount = 0 THEN EXIT FOR END IF NEXT o END SUB 'This only needs to be called from doswap LOCAL SUB update_HeroSliceContext(byval party_slot as integer) WITH gam.hero(party_slot) IF .sl ANDALSO *.sl->Context IS HeroSliceContext THEN CAST(HeroSliceContext ptr, .sl->Context)->slot = party_slot END IF END WITH END SUB 'Swap two party slots, occupied or not. There's no significance to the order of the args. SUB doswap (byval s as integer, byval d as integer) IF s = d THEN EXIT SUB 'Because this sub can swap between the active party and the reserve party, 'we also need to prepare to swap the parentage of the hero slice between 'the hero layer and the reserve layer DIM s_par as Slice Ptr = NULL DIM d_par as Slice Ptr = NULL IF gam.hero(s).sl THEN s_par = gam.hero(s).sl->Parent IF gam.hero(d).sl THEN d_par = gam.hero(d).sl->Parent '---hero state and stats SWAP gam.hero(s), gam.hero(d) 'Restore the correct parentage of the hero slice, active to the hero layer 'and reserve to the reserve layer IF gam.hero(s).sl THEN SetSliceParent gam.hero(s).sl, s_par IF gam.hero(d).sl THEN SetSliceParent gam.hero(d).sl, d_par '--The hero slice tells which slot the hero is in, needs updating update_HeroSliceContext s update_HeroSliceContext d '--set tags, reload hero pictures and palettes, update herow(), etc party_change_updates '--Trigger scripts IF gen(genMoveHeroScript) > 0 THEN FOR i as integer = 0 TO 1 IF gam.hero(s).id >= 0 THEN trigger_script gen(genMoveHeroScript), 2, NO, "move hero", context_string, mainFibreGroup, 100 trigger_script_arg 0, s, "slot" trigger_script_arg 1, d, "previous slot" END IF SWAP s, d NEXT i END IF END SUB SUB update_textbox () STATIC tog as integer tog = tog XOR 1 'On the first tick, show_lines is -1 IF txt.fully_shown = NO THEN txt.show_lines += 1 '--play sounds for non-blank lines IF TRIM(txt.box.text(txt.show_lines)) <> "" THEN IF txt.box.line_sound > 0 THEN ' Don't subtract 1, since menusound takes sfx id + 1 menusound txt.box.line_sound ELSEIF txt.box.line_sound = 0 THEN ' Default menusound gen(genTextboxLine) ELSEIF txt.box.line_sound < 0 THEN ' Silence END IF END IF '--note when the display of lines is done IF txt.show_lines >= UBOUND(txt.box.text) THEN txt.fully_shown = YES END IF '--update the slice to show the right number of lines DIM text_sl As Slice Ptr text_sl = LookupSlice(SL_TEXTBOX_TEXT, txt.sl) IF text_sl THEN DIM dat as TextSliceData Ptr dat = text_sl->SliceData IF dat THEN dat->line_limit = txt.show_lines IF txt.fully_shown THEN dat->line_limit = -1 'Show all END IF END IF END IF END IF IF txt.box.choice_enabled THEN '--Make the selected choice flash DIM choice_sl(1) as Slice Ptr choice_sl(0) = LookupSlice(SL_TEXTBOX_CHOICE0, SliceTable.Root) choice_sl(1) = LookupSlice(SL_TEXTBOX_CHOICE1, SliceTable.Root) DIM col as integer IF choice_sl(0) <> 0 AND choice_sl(1) <> 0 THEN FOR i as integer = 0 TO 1 col = uilook(uiMenuItem) IF txt.choicestate.hover = i THEN col = uilook(uiMouseHoverItem) IF txt.choicestate.pt = i THEN col = uilook(uiSelectedItem + tog) ChangeTextSlice choice_sl(i), ,col NEXT i END IF END IF END SUB SUB choicebox_controls() usemenusounds 'Keyboard controls usemenu txt.choicestate 'Mouse controls IF get_gen_bool("/mouse/mouse_menus") THEN DIM choice_sl(1) as Slice Ptr choice_sl(0) = LookupSlice(SL_TEXTBOX_CHOICE0, SliceTable.Root) choice_sl(1) = LookupSlice(SL_TEXTBOX_CHOICE1, SliceTable.Root) txt.choicestate.hover = -1 FOR i as integer = 0 TO UBOUND(choice_sl) IF SliceCollidePoint(choice_sl(i), readmouse.pos) THEN txt.choicestate.hover = i END IF NEXT i IF (readmouse.buttons AND mouseLeft) ANDALSO txt.choicestate.hover >= 0 THEN txt.choicestate.pt = txt.choicestate.hover END IF END IF END SUB FUNCTION use_touch_textboxes() as bool 'Also applies to mouse-click textboxes IF running_on_mobile() THEN IF get_gen_bool("/mobile_options/touch_textboxes/enabled") THEN RETURN YES END IF IF get_gen_bool("/mouse/click_textboxes") THEN RETURN YES END IF RETURN NO END FUNCTION FUNCTION user_textbox_advance() as bool 'Return YES if the player wants to advance the textbox using built-in controls IF use_touch_textboxes() THEN 'Mouse and touch controls IF readmouse().release AND mouseLeft THEN IF NOT txt.box.choice_enabled THEN 'No choicebox, so accept clicks anywhere on the whole screen RETURN YES END IF 'Only accept clicks on the choicebox itself DIM box_sl as slice ptr = LookupSlice(SL_TEXTBOX_CHOICE_BOX, SliceTable.Root) IF box_sl = 0 THEN 'No choicebox was found at all (which might be a sign that a script messed with it?) 'Go ahead and advance the textbox to avoid getting stuck. RETURN YES END IF IF SliceCollidePoint(box_sl, readmouse.pos) THEN RETURN YES END IF END IF 'Keyboard/joystick textbox advance IF carray(ccUse) > 1 THEN RETURN YES RETURN NO END FUNCTION SUB evalherotags () 'Note: this funciton can be called in a middle of changing a party, with no heroes in the active party DIM leaderid as integer = herobyrank(0) FOR i as integer = 0 TO small(gen(genMaxHero), UBOUND(herotags)) '--for each available hero 'unset all tags, including ones used on heroes not in the party WITH herotags(i) settag .have_tag, NO settag .alive_tag, NO settag .leader_tag, NO settag .active_tag, NO FOR j as integer = 0 TO v_len(.checks) - 1 settag .checks[j].tag, NO NEXT j END WITH NEXT i 'scan party FOR i as integer = 0 TO UBOUND(gam.hero) DIM byref hero as HeroState = gam.hero(i) 'Don't crash if the hero definition has been deleted since! IF hero.id >= 0 ANDALSO hero.id <= UBOUND(herotags) THEN WITH herotags(hero.id) settag .have_tag, YES IF hero.stat.cur.hp > 0 THEN settag .alive_tag, YES IF hero.id = leaderid THEN settag .leader_tag, YES IF i < active_party_slots THEN settag .active_tag, YES FOR j as integer = 0 TO v_len(.checks) - 1 WITH .checks[j] SELECT CASE .kind CASE TagRangeCheckKind.level IF hero.lev >= .min ANDALSO hero.lev <= .max THEN settag .tag, YES END IF CASE ELSE END SELECT END WITH NEXT j END WITH END IF NEXT i END SUB 'Copy data from party list (gam.hero()) to caterpillar (herow()) LOCAL SUB update_herow_on_party_change() DIM as integer rank, slot FOR rank = 0 TO UBOUND(herow) herow(rank).sl = NULL herow(rank).party_slot = -1 NEXT rank = 0 FOR slot = 0 TO active_party_slots - 1 IF gam.hero(slot).id >= 0 THEN herow(rank).sl = gam.hero(slot).sl herow(rank).party_slot = slot rank += 1 END IF NEXT END SUB 'Call this after a change to the party SUB party_change_updates update_herow_on_party_change evalherotags evalitemtags 'Because of items with 'actively equipped' tags vishero tag_updates END SUB SUB evalitemtags DIM as integer id FOR i as integer = 0 TO small(gen(genMaxItem), UBOUND(itemtags)) WITH itemtags(i) 'clear all four tags settag .have_tag, NO settag .in_inventory_tag, NO settag .is_equipped_tag, NO settag .is_actively_equipped_tag, NO END WITH NEXT i 'search inventory slots FOR j as integer = 0 TO last_inv_slot() 'get item ID id = inventory(j).id IF inventory(j).used ANDALSO id <= UBOUND(itemtags) THEN 'there is an item in this slot WITH itemtags(id) settag .have_tag, YES settag .in_inventory_tag, YES END WITH END IF NEXT j FOR j as integer = 0 TO UBOUND(gam.hero) 'search hero list FOR k as integer = 0 TO UBOUND(gam.hero(j).equip) 'search equipment slots id = gam.hero(j).equip(k).id IF id >= 0 ANDALSO id <= UBOUND(itemtags) THEN ' there is an item equipped in this slot WITH itemtags(id) settag .have_tag, YES settag .is_equipped_tag, YES IF j < active_party_slots THEN settag .is_actively_equipped_tag, YES END WITH END IF NEXT k NEXT j END SUB SUB hero_swap_menu (byval reserve_too as bool) DIM st as OrderTeamState st.show_reserve = reserve_too st.reserve.pt = -1 st.reserve.last = -1 st.swapme = -1 st.party.need_update = YES '--Preserve background for display beneath the hero swapper DIM holdscreen as integer st.page = vpage holdscreen = allocatepage copypage st.page, holdscreen hero_swap_menu_init st show_virtual_gamepad() MenuSound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys st.party.tog XOR= 1 playtimer IF get_gen_bool("/mouse/mouse_menus") THEN hero_swap_menu_mouse_control st END IF IF carray(ccCancel) > 1 THEN IF st.swapme >= 0 THEN MenuSound gen(genCancelSFX) st.swapme = -1 ELSE st.do_quit = YES END IF END IF IF st.show_reserve THEN IF carray(ccUp) > 1 THEN MenuSound gen(genCursorSFX) IF st.reserve.pt < 0 THEN st.reserve.pt = st.reserve.last st.reserve.need_update = YES ELSE loopvar st.reserve.pt, -1, st.reserve.last, -1 st.reserve.need_update = YES END IF END IF IF carray(ccDown) > 1 THEN MenuSound gen(genCursorSFX) IF st.reserve.pt < 0 THEN st.reserve.pt = 0 st.reserve.need_update = YES ELSE loopvar st.reserve.pt, -1, st.reserve.last, 1 st.reserve.need_update = YES END IF END IF END IF IF st.reserve.need_update THEN st.reserve.need_update = NO IF st.reserve.pt < st.reserve.top THEN st.reserve.top = large(st.reserve.pt, 0) IF st.reserve.pt > st.reserve.top + 7 THEN st.reserve.top = st.reserve.pt - 7 st.party.need_update = YES END IF IF carray(ccLeft) > 1 AND st.reserve.pt < 0 THEN MenuSound gen(genCursorSFX) loopvar st.party.pt, 0, 3, -1 st.party.need_update = YES END IF IF carray(ccRight) > 1 AND st.reserve.pt < 0 THEN MenuSound gen(genCursorSFX) loopvar st.party.pt, 0, 3, 1 st.party.need_update = YES END IF IF carray(ccUse) > 1 THEN st.do_pick = YES IF st.do_quit THEN EXIT DO IF st.do_pick THEN st.do_pick = NO DO IF prefbit(20) THEN '"Locked heroes can't be re-ordered" IF (gam.hero(st.party.pt).locked ANDALSO gam.hero(st.party.pt).id >= 0) ORELSE (st.swapme >= 0 ANDALSO gam.hero(st.swapme).locked ANDALSO gam.hero(st.swapme).id >= 0) THEN MenuSound gen(genCancelSFX) EXIT DO END IF END IF IF st.swapme = -1 THEN MenuSound gen(genAcceptSFX) IF st.reserve.pt < 0 THEN st.swapme = st.party.pt ELSE st.swapme = 4 + st.reserve.pt END IF ELSE MenuSound gen(genAcceptSFX) DIM swap1 as integer DIM swap2 as integer DO IF st.swapme < 4 THEN IF active_party_size() <= 1 ANDALSO st.reserve.pt = st.reserve.last THEN EXIT DO IF gam.hero(st.swapme).locked ANDALSO gam.hero(st.swapme).id >= 0 ANDALSO st.reserve.pt > -1 THEN EXIT DO ELSE IF st.swapme - 4 = st.reserve.last AND st.reserve.pt = -1 AND active_party_size() <= 1 THEN EXIT DO IF gam.hero(st.party.pt).locked ANDALSO gam.hero(st.party.pt).id >= 0 ANDALSO st.reserve.pt = -1 THEN EXIT DO END IF '---IDENTIFY DESTINATION--- IF st.reserve.pt < 0 THEN swap1 = st.party.pt ELSE swap1 = st.swindex(st.reserve.pt) END IF '---IDENTIFY SOURCE--- IF st.swapme < 4 THEN swap2 = st.swapme ELSE swap2 = st.swindex(st.swapme - 4) END IF doswap swap1, swap2 st.swapme = -1 hero_swap_menu_init st EXIT DO LOOP '--this loop just exists for convenient breaking with EXIT DO END IF EXIT DO LOOP '--this loop just exists for convenient breaking with EXIT DO END IF IF st.party.need_update THEN st.party.need_update = NO IF gam.hero(st.party.pt).id >= 0 AND st.reserve.pt < 0 THEN st.info = gam.hero(st.party.pt).name ELSE st.info = "" END IF copypage holdscreen, st.page hero_swap_menu_display st setvispage st.page dowait LOOP clearkeys MenuSound gen(genCancelSFX) freepage holdscreen party_change_updates END SUB SUB hero_swap_menu_mouse_control (st as OrderTeamState) DIM old_party_pt as integer = st.party.pt DIM old_reserve_pt as integer = st.reserve.pt DIM old_swapme as integer = st.swapme DIM centerx as integer = vpages(st.page)->w \ 2 DIM centery as integer = vpages(st.page)->h \ 2 DIM order_box as RectType = XYWH(centerx - 130\2, centery - 38\2 - 34, 130, 38) DIM reserve_w as integer = st.charsize.x * 8 + 16 DIM reserve_h as integer = small(st.charsize.y, 8) * 10 + 10 DIM reserve_box as RectType = XYWH(centerx - reserve_w\2, centery - reserve_h\2 + small(st.charsize.y, 8) * 5, reserve_w, reserve_h) IF rect_collide_point(order_box, readmouse.pos) THEN 'In the order box DIM hover as integer = -1 FOR i as integer = 0 TO 3 DIM as integer xpos = centerx - 55 + (30 * i), ypos = centery - 40 IF rect_collide_point(XYWH(xpos, ypos, 30, 30), readmouse.pos) THEN hover = i NEXT i IF hover >= 0 THEN IF (readmouse.release AND mouseLeft) THEN 'Clicked on a slot in the active party IF st.party.pt = hover THEN 'Pick selected st.do_pick = YES st.reserve.pt = -1 ELSE 'Select hovered st.party.pt = hover st.reserve.pt = -1 IF st.swapme >= 0 THEN st.do_pick = YES END IF END IF END IF ELSEIF st.show_reserve ANDALSO rect_collide_point(reserve_box, readmouse.pos) THEN 'In the reserve box DIM r_hover as integer = -1 FOR i as integer = st.reserve.top TO small(st.reserve.top + 7, st.reserve.last) IF rect_collide_point(XYWH(centerx - reserve_w\2, centery + (i - st.reserve.top) * 10, reserve_w, 10), readmouse.pos) THEN r_hover = i NEXT i IF r_hover >= 0 THEN IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 THEN IF st.reserve.pt = r_hover THEN 'Pick selected st.do_pick = YES ELSE 'Select hovered st.reserve.pt = r_hover IF st.swapme >= 0 THEN st.do_pick = YES END IF END IF END IF mouse_scroll_menu(st.reserve) mouse_drag_menu(st.reserve) ELSE 'Anywhere outside the boxes IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 THEN st.do_quit = YES END IF END IF IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN IF st.swapme >= 0 THEN MenuSound gen(genCancelSFX) st.swapme = -1 ELSE st.do_quit = YES END IF END IF IF old_party_pt <> st.party.pt ORELSE old_reserve_pt <> st.reserve.pt ORELSE old_swapme <> st.swapme THEN MenuSound gen(genCursorSFX) st.party.need_update = YES st.reserve.need_update = YES END IF END SUB SUB hero_swap_menu_display (st as OrderTeamState) '--DRAWS SWAP MENU AND CURRENT SELECTION DIM centerx as integer = vpages(st.page)->w \ 2 DIM centery as integer = vpages(st.page)->h \ 2 centerbox centerx, centery - 34, 130, 38, 1, st.page FOR i as integer = 0 TO active_party_slots - 1 DIM as integer xpos = centerx - 55 + (30 * i), ypos = centery - 40 IF i = st.swapme OR gam.hero(i).id >= 0 THEN rectangle xpos, ypos, 20, 20, boxlook(0).bgcol, st.page IF gam.hero(i).id >= 0 THEN set_walkabout_frame gam.hero(i).sl, dirDown, 0 IF i = st.swapme THEN ypos -= 6 DrawSliceAt LookupSliceSafe(SL_WALKABOUT_SPRITE_COMPONENT, gam.hero(i).sl), xpos, ypos, 20, 20, st.page, YES END IF NEXT i IF st.reserve.pt < 0 THEN edgeprint CHR(24), centerx - 49 + 30 * st.party.pt, centery - 48, uilook(uiSelectedItem + st.party.tog), st.page IF st.show_reserve THEN DIM reserve_box as RectType WITH reserve_box .wide = st.charsize.x * 8 + 16 .high = small(st.charsize.y, 8) * 10 + 10 .x = centerx - .wide \ 2 .y = centery + small(st.charsize.y, 8) * 5 - .high \ 2 END WITH edgeboxstyle reserve_box, 0, st.page draw_scrollbar st.reserve, reserve_box, , st.page FOR i as integer = st.reserve.top TO small(st.reserve.top + 7, st.reserve.last) 'Some of the colours are a bit bizarre, here, especially the time bar stuff below DIM menu_color as integer = uilook(uiMenuItem) IF st.swapme = i + 4 THEN menu_color = uilook(uiSelectedDisabled) IF st.reserve.pt = i THEN menu_color = uilook(uiSelectedItem + st.party.tog) IF st.swapme = i + 4 THEN menu_color = uilook(uiSelectedDisabled + st.party.tog) END IF IF st.swapme > -1 AND st.swapme < 4 THEN IF (active_party_size() <= 1 ANDALSO i = st.reserve.last) ORELSE (gam.hero(st.party.pt).locked ANDALSO gam.hero(st.party.pt).id >= 0) THEN menu_color = uilook(uiTimeBar + ((st.reserve.pt = i) * st.party.tog)) END IF END IF edgeprint st.swname(i), pCentered, centery + (i - st.reserve.top) * 10, menu_color, st.page NEXT i END IF IF LEN(st.info) THEN centerbox centerx, centery - 56, (LEN(st.info) + 2) * 8, 14, 1, st.page edgeprint st.info, pCentered, centery - 61, uilook(uiText), st.page END IF END SUB SUB hero_swap_menu_init(st as OrderTeamState) '--MAPS OUT ONLY VALID SWAPABLE HEROS PLUS A BLANK st.reserve.last = -1 st.charsize.x = 0 FOR i as integer = 4 TO 40 IF gam.hero(i).locked = NO ANDALSO gam.hero(i).id >= 0 THEN st.reserve.last = st.reserve.last + 1 st.swindex(st.reserve.last) = i st.swname(st.reserve.last) = gam.hero(i).name st.charsize.x = large(st.charsize.x, LEN(st.swname(st.reserve.last))) END IF NEXT i st.reserve.last += 1 FOR i as integer = 40 TO 4 STEP -1 IF gam.hero(i).id = -1 THEN st.swindex(st.reserve.last) = i st.swname(st.reserve.last) = readglobalstring(48, "REMOVE", 10) st.charsize.x = large(st.charsize.x, 7) END IF NEXT i st.reserve.size = small(7, st.reserve.last) st.reserve.spacing = 10 st.charsize.y = small(8, st.reserve.last + 1) st.party.need_update = YES END SUB /' Not used yet FUNCTION check_condition(cond as Condition) as bool IF cond.comp = compTag THEN RETURN istag(tag(), cond.tag, , NO) ELSE IF cond.varnum < 0 ORELSE cond.varnum > UBOUND(global) THEN RETURN NO DIM globalvar as integer = global(cond.varnum) SELECT CASE cond.comp CASE compEq : RETURN globalvar = cond.value CASE compNe : RETURN globalvar <> cond.value CASE compLt : RETURN globalvar < cond.value CASE compLe : RETURN globalvar <= cond.value CASE compGt : RETURN globalvar > cond.value CASE compGe : RETURN globalvar >= cond.value CASE ELSE : debugerror strprintf("Invalid Condition (%d,%d,%d)", cond.comp, cond.varnum, cond.value) END SELECT END IF END FUNCTION '/ 'Either pass a tag number and specify YES/NO, or pass just a tag number; +ve/-ve indicates value SUB settag (byval tagnum as integer, byval value as integer = 4444) IF ABS(tagnum) <= max_tag() THEN settag tag(), tagnum, value ELSE settag onetime(), tagnum - (max_tag()+1) * SGN(tagnum), value END IF END SUB FUNCTION istag (num as integer, zero as bool=NO) as bool IF ABS(num) <= max_tag() THEN RETURN istag(tag(), num, zero) ELSE RETURN istag(onetime(), num - (max_tag()+1) * SGN(num), zero) END IF END FUNCTION 'Either pass a tag number and specify YES/NO, or pass just a tag number; +ve/-ve indicates value SUB settag (tagbits() as integer, byval tagnum as integer, byval value as integer = 4444) IF value <> 4444 THEN IF ABS(tagnum) > 1 THEN setbit tagbits(), 0, ABS(tagnum), value ELSEIF tagnum < -1 THEN setbit tagbits(), 0, ABS(tagnum), NO ELSEIF tagnum > 1 THEN setbit tagbits(), 0, tagnum, YES END IF END SUB FUNCTION istag (tagbits() as integer, num as integer, zero as bool=NO) as bool IF num = 0 THEN RETURN zero 'why go through all that just to return defaults? IF num = 1 THEN RETURN NO IF num = -1 THEN RETURN YES IF ABS(num) >= UBOUND(tagbits) * 16 + 16 THEN RETURN zero ' use default in case of an invalid tag DIM ret as integer = readbit(tagbits(), 0, ABS(num)) 'raw bit: 0 or 1 IF num > 0 AND ret <> 0 THEN RETURN YES IF num < 0 AND ret = 0 THEN RETURN YES RETURN NO END FUNCTION 'The minimap, indicating hero position 'playerpos is in pixels SUB minimap (playerpos as XYPair) IF gen(genMinimapAlgorithm) = minimapScaled THEN switch_to_32bit_vpages DIM mini as Frame Ptr DIM zoom as integer = -1 mini = createminimap(maptiles(), tilesets(), @pass, zoom, CAST(MinimapAlgorithmEnum, gen(genMinimapAlgorithm))) DIM offset as XYPair offset.x = rCenter - mini->w / 2 offset.y = rCenter - mini->h / 2 edgeboxstyle offset.x - 2, offset.y - 2, mini->w + 4, mini->h + 4, 0, vpage frame_draw mini, NULL, offset.x, offset.y, NO, vpage frame_unload @mini MenuSound gen(genAcceptSFX) DIM indicator as XYPair = offset + (playerpos / 20) * zoom DIM tog as integer show_virtual_gamepad() setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer rectangle indicator.x, indicator.y, zoom, zoom, uilook(uiSelectedItem) * tog, vpage setvispage vpage dowait 'Exit on keypress after setvispage, because it does F12 screenshot check LOOP UNTIL anykeypressed(YES, YES) 'inc. joystick and mouse setkeys MenuSound gen(genCancelSFX) IF gen(gen32bitMode) THEN switch_to_32bit_vpages ELSE switch_to_8bit_vpages END SUB 'The map-teleport debug menu. 'Sets gam.map.id and hero position and returns true if the player wants to teleport. FUNCTION teleporttool () as bool 'The maptiles, passmap and tilesets for displayed map, if not the same as the current in-game map REDIM maptiles2(0) as TileMap DIM pass2 as TileMap DIM tilesets2(maplayerMax) as TilesetData ptr DIM mini as Frame Ptr DIM zoom as integer = -1 'pixels per tile DIM maxzoom as integer DIM mapsize as XYPair 'In tiles DIM minisize as XYPair 'Minimap size in pixels DIM offset as XYPair 'Top-left position where the on-screen map viewport starts DIM camera as XYPair 'In pixels DIM dest as XYPair = herotpos(0) 'Switch unconditionally, in-case live-previewing and genMinimapAlgorithm is changed switch_to_32bit_vpages ensure_normal_palette 'Preserve background for display beneath DIM holdscreen as integer holdscreen = duplicatepage(vpage) 'Initially use the real map's maptiles() and tilesets(), which has 'the advantage of making the map look more correct. teleporttool_generate_minimap mini, maptiles(), pass, tilesets(), zoom, maxzoom, mapsize, minisize, offset, camera, dest DIM state as MenuState state.pt = 0 state.top = 0 state.size = 22 state.first = 0 state.last = 1 state.need_update = YES DIM menuopts as MenuOptions menuopts.edged = YES DIM preview_delay as integer = 0 'Ticks before loading new map DIM pickpoint as bool = NO DIM destmap as integer = gam.map.id DIM currentmap as integer = gam.map.id DIM toggle as integer DIM menu(1) as string menu(0) = CHR(27) & "Map " & destmap & CHR(26) & " " & getmapname(destmap) menu(1) = "Position X = " & dest.x & " Y = " & dest.y teleporttool = NO MenuSound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys toggle XOR= 1 'Cap to a maximum zoom because createminimap draws the whole map and is really slow IF keyval(scMinus) > 1 THEN zoom = large(1, zoom - 1) state.need_update = YES END IF IF keyval(scPlus) > 1 THEN zoom = small(maxzoom, zoom + 1) state.need_update = YES END IF IF preview_delay > 0 THEN preview_delay -= 1 IF preview_delay = 0 THEN teleporttool_load_map destmap, maptiles2(), pass2, tilesets2() state.need_update = YES zoom = -1 'Back to default END IF END IF IF state.need_update THEN state.need_update = NO IF destmap = currentmap THEN teleporttool_generate_minimap mini, maptiles(), pass, tilesets(), zoom, maxzoom, mapsize, minisize, offset, camera, dest ELSE teleporttool_generate_minimap mini, maptiles2(), pass2, tilesets2(), zoom, maxzoom, mapsize, minisize, offset, camera, dest END IF END IF IF pickpoint = NO THEN IF carray(ccCancel) > 1 THEN EXIT DO END IF IF intgrabber(destmap, 0, gen(genMaxMap)) THEN preview_delay = 8 menu(0) = CHR(27) & "Map " & destmap & CHR(26) & " " & getmapname(destmap) END IF IF enter_or_space() THEN pickpoint = YES ELSE IF carray(ccUse) > 1 THEN 'confirm and teleport IF gam.map.id <> destmap THEN teleporttool = YES gam.map.id = destmap (heropos(0)) = dest * 20 EXIT DO END IF IF carray(ccCancel) > 1 THEN pickpoint = NO DIM xrate as integer 'Movement rate DIM yrate as integer IF keyval(scShift) > 0 THEN xrate = 8 yrate = 5 ELSE xrate = 1 yrate = 1 END IF IF slowkey(ccUp, 45) THEN dest.y = large(dest.y - yrate, 0) IF slowkey(ccDown, 45) THEN dest.y = small(dest.y + yrate, mapsize.y - 1) IF slowkey(ccLeft, 45) THEN dest.x = large(dest.x - xrate, 0) IF slowkey(ccRight, 45) THEN dest.x = small(dest.x + xrate, mapsize.x - 1) DIM temp as XYPair = camera camera.x = bound(camera.x, (dest.x + 1) * zoom + 40 - minisize.x, dest.x * zoom - 40) 'follow dest camera.y = bound(camera.y, (dest.y + 1) * zoom + 30 - minisize.y, dest.y * zoom - 30) camera.x = bound(camera.x, 0, mapsize.x * zoom - minisize.x) 'bound to map edges camera.y = bound(camera.y, 0, mapsize.y * zoom - minisize.y) END IF menu(1) = "Position X = " & dest.x & " Y = " & dest.y ' Draw screen copypage holdscreen, vpage edgeboxstyle offset.x - 2, offset.y - 2, minisize.x + 4, minisize.y + 4, 0, vpage frame_draw mini, NULL, offset.x - camera.x, offset.y - camera.y, NO, vpage 'Hero cursor DIM cursor as XYPair = offset + dest * zoom - camera rectangle cursor.x, cursor.y, zoom, zoom, uilook(uiSelectedItem) * toggle, vpage state.pt = IIF(pickpoint, 1, 0) standardmenu menu(), state, 0, vpages(vpage)->h - 29, vpage, menuopts DIM caption as string = IIF(pickpoint, "Arrows/SHIFT move; ENTER to teleport", "Select map; ENTER to select position") edgeprint caption, 0, pBottom, uilook(uiText), vpage edgeprint "+/- to zoom", pRight - 4, pBottom - 9, uilook(uiText), vpage setvispage vpage dowait LOOP frame_unload @mini freepage holdscreen unloadtilemaps maptiles2() unloadtilemap pass2 unloadmaptilesets tilesets2() restore_previous_palette setkeys MenuSound gen(genCancelSFX) IF gen(gen32bitMode) THEN switch_to_32bit_vpages ELSE switch_to_8bit_vpages END FUNCTION 'Load enough of a map's data to display it SUB teleporttool_load_map (map as integer, maptiles2() as TileMap, pass2 as TileMap, tilesets2() as TilesetData ptr) DIM gmap2(dimbinsize(binMAP)) as integer loadrecord gmap2(), game + ".map", getbinsize(binMAP) \ 2, map loadtilemaps maptiles2(), maplumpname(map, "t") loadmaptilesets tilesets2(), gmap2() loadtilemap pass2, maplumpname(map, "p") END SUB 'Generate the minimap given a zoom factor, and determinate viewport and camera positions SUB teleporttool_generate_minimap(byref mini as Frame Ptr, maptilesX() as TileMap, passX as TileMap, tilesetsX() as TilesetData ptr, byref zoom as integer, byref maxzoom as integer, byref mapsize as XYPair, byref minisize as XYPair, byref offset as XYPair, byref camera as XYPair, dest as XYPair) mapsize = maptilesX(0).size DIM screenw as integer = vpages(vpage)->w DIM screenh as integer = vpages(vpage)->h DIM screen_ratio as double = small(screenw / maptilesX(0).wide, screenh / maptilesX(0).high) IF zoom = -1 THEN 'minimum zoom level to make tiles easy to pick zoom = bound(INT(screen_ratio), 4, 20) END IF maxzoom = bound(INT(2.5 * screen_ratio), 4, 20) frame_unload @mini mini = createminimap(maptilesX(), tilesetsX(), @passX, zoom, CAST(MinimapAlgorithmEnum, gen(genMinimapAlgorithm))) offset.x = large(screenw \ 2 - mini->w \ 2, 0) offset.y = large(screenh \ 2 - mini->h \ 2, 0) minisize.x = screenw - offset.x * 2 minisize.y = screenh - offset.y * 2 camera.x = bound(dest.x * zoom - minisize.x \ 2, 0, mapsize.x * zoom - minisize.x) camera.y = bound(dest.y * zoom - minisize.y \ 2, 0, mapsize.y * zoom - minisize.y) END SUB ' This is the hero picker (for the active party only). ' Returns party slot or -1 if cancelled. ' If there is only one hero, return immediately if skip_if_alone. FUNCTION onwho (caption as string, skip_if_alone as bool = YES) as integer '-- pre-select the first hero DIM who as integer = rank_to_party_slot(0) IF skip_if_alone ANDALSO active_party_size() <= 1 THEN setkeys RETURN who END IF menusound gen(genAcceptSFX) copypage vpage, dpage DIM page as integer = compatpage DIM tog as integer DIM wtog as integer show_virtual_gamepad() setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer loopvar wtog, 0, max_wtog() DIM do_exit as bool = NO IF carray(ccCancel) > 1 THEN do_exit = YES IF carray(ccLeft) > 1 THEN who = loop_active_party_slot(who, -1) menusound gen(genCursorSFX) END IF IF carray(ccRight) > 1 THEN who = loop_active_party_slot(who, 1) menusound gen(genCursorSFX) END IF IF carray(ccUse) > 1 THEN EXIT DO IF get_gen_bool("/mouse/mouse_menus") THEN DIM old_who as integer = who DIM hover as integer = -1 FOR i as integer = 0 TO 3 IF rect_collide_point(XYWH(100 + i * 30 - 5, 100 - 5, 30, 30), readmouse.pos) THEN hover = i END IF NEXT i IF (readmouse.release AND mouseRight) THEN do_exit = YES IF (readmouse.buttons AND mouseLeft) THEN IF hover >= 0 ANDALSO gam.hero(hover).id >= 0 THEN who = hover END IF END IF IF (readmouse.release AND mouseLeft) THEN IF hover >= 0 THEN IF gam.hero(hover).id >= 0 THEN IF who = hover THEN 'Already selected, confirm EXIT DO ELSE who = hover END IF ELSE 'Can't pick an empty slot menusound gen(genCancelSFX) END IF ELSE IF NOT rect_collide_point(Type(90, 74, 140, 52), readmouse.pos) THEN 'left clicked outside the rectangle do_exit = YES END IF END IF END IF IF who <> old_who THEN menusound gen(genCursorSFX) END IF IF do_exit THEN who = -1 menusound gen(genCancelSFX) EXIT DO END IF centerbox 160, 100, 140, 52, 1, page FOR party_slot as integer = 0 TO active_party_slots - 1 IF gam.hero(party_slot).id >= 0 THEN DIM walkframe as integer IF who = party_slot THEN walkframe = wtog_to_frame(wtog) ELSE walkframe = 0 set_walkabout_frame gam.hero(party_slot).sl, dirDown, walkframe DrawSliceAt LookupSliceSafe(SL_WALKABOUT_SPRITE_COMPONENT, gam.hero(party_slot).sl), 100 + party_slot * 30, 100, 20, 20, page, YES END IF NEXT edgeprint CHR(25), 106 + who * 30, 90, uilook(uiSelectedItem + tog), page wrapprint caption, pCentered, 80, uilook(uiText), page setvispage vpage dowait LOOP freepage page setkeys RETURN who END FUNCTION FUNCTION renamehero (byval who as integer, byval escapable as bool) as bool 'If escapable is true, then ESC exits, otherwise it resets the entered name 'Returns true if the player didn't cancel. DIM ret as bool DIM her as HeroDef loadherodata her, gam.hero(who).id DIM limit as integer = her.max_name_len IF limit = 0 THEN limit = 16 DIM prompt as string = readglobalstring(137, "Name the Hero", 20) DIM entry_box_width as integer = large(limit, LEN(gam.hero(who).name)) * 8 DIM remember as string = gam.hero(who).name DIM need_fade_out as bool IF faded_in = NO THEN setvispage vpage fadein need_fade_out = YES END IF DIM holdscreen as integer holdscreen = allocatepage copypage vpage, holdscreen IF running_on_console() THEN 'native virtual keyboard does not work on OUYA right now, so do this instead. gam.hero(who).name = gamepad_virtual_keyboard(gam.hero(who).name, limit) ret = YES ELSEIF running_on_mobile() THEN hide_virtual_gamepad() gam.pad.being_shown = NO gam.hero(who).name = touch_virtual_keyboard(gam.hero(who).name, limit, prompt) ret = YES ELSE 'On platforms that need it (currently just Android) show the virtual keyboard. 'This does nothing on platforms that don't need it. ' 'FIXME: this is obsoleted by touch_virtual_keyboard() but we may want it again ' in the future when the Android virtual keyboard is working again show_virtual_keyboard '--Wait while the player types a new name setkeys YES DO setwait speedcontrol setkeys YES playtimer IF carray(ccUse) > 1 AND keyval(scSpace) = 0 THEN IF LEN(gam.hero(who).name) > 0 THEN ret = YES EXIT DO ELSE menusound gen(genCancelSFX) END IF END IF IF carray(ccCancel) > 1 THEN gam.hero(who).name = remember menusound gen(genCancelSFX) IF escapable THEN EXIT DO END IF strgrabber gam.hero(who).name, limit copypage holdscreen, vpage centerbox rCenter, rCenter, 168, 32, 1, vpage edgeprint prompt, pCentered, rCenter - 10, uilook(uiText), vpage rectangle pCentered, rCenter + 1, entry_box_width, 10, uilook(uiHighlight), vpage edgeprint gam.hero(who).name, pCentered, rCenter, uilook(uiMenuItem), vpage setvispage vpage dowait LOOP hide_virtual_keyboard END IF IF ret THEN menusound gen(genAcceptSFX) IF need_fade_out THEN fadeout uilook(uiFadeoutNewGame) 'Usually happens if the starting hero is renameable END IF freepage holdscreen RETURN ret END FUNCTION SUB resetgame () 'Warning: don't put necessary initialisation in here, 'as this is ONLY called AFTER playing a game, on gameover/resetgame/loadgame/quit. 'initgamedefaults is probably a better place for that. 'In order words, be suspicious of anything in here that resets 'to a value other than zero/empty string/whatever the UDT's default value is. gam.map.id = 0 (herox(0)) = 0 (heroy(0)) = 0 (heroz(0)) = 0 (herodir(0)) = 0 gam.getinputtext_enabled = NO script_log_resetgame 'leader = 0 mapx = 0 mapy = 0 gold = 0 gam.showstring = "" gam.debug_camera_pan = NO gam.paused = NO '--return gen to defaults xbload game + ".gen", gen(), "General data is missing from " + sourcerpg CleanNPCL npc() flusharray tag() flusharray onetime() FOR i as integer = 0 TO 40 'TODO: use HeroState.Constructor instead deletehero i, YES 'Don't forceparty or run triggers gam.hero(i).locked = NO FOR j as integer = 0 TO statLast gam.hero(i).stat.cur.sta(j) = 0 gam.hero(i).stat.max.sta(j) = 0 gam.hero(i).stat.base.sta(j) = 0 NEXT j gam.hero(i).lev = 0 gam.hero(i).lev_gain = 0 gam.hero(i).battle_pic = 0 gam.hero(i).battle_pal = 0 gam.hero(i).pic = 0 gam.hero(i).pal = 0 gam.hero(i).def_wep = 0 gam.hero(i).rename_on_status = NO 'Hero elementals are actually initialised (to 1 = 100% damage) in addhero. FOR j as integer = 0 TO maxElements - 1 gam.hero(i).elementals(j) = .0f NEXT FOR j as integer = 0 to 1 gam.hero(i).hand_pos(j).x = 0 gam.hero(i).hand_pos(j).y = 0 NEXT j gam.hero(i).hand_pos_overridden = NO FOR o as integer = 0 TO 3 FOR j as integer = 0 TO 23 gam.hero(i).spells(o, j) = 0 NEXT j NEXT o FOR o as integer = 0 TO maxMPLevel gam.hero(i).levelmp(o) = 0 NEXT o gam.hero(i).exp_cur = 0 gam.hero(i).exp_next = 0 gam.hero(i).name = "" FOR o as integer = 0 TO UBOUND(gam.hero(i).equip) gam.hero(i).equip(o).id = -1 NEXT o NEXT i CleanInventory(inventory()) flusharray global(), maxScriptGlobals, 0 reset_vehicle vstate cleanup_text_box FOR i as integer = 0 TO UBOUND(plotstr) WITH plotstr(i) .s = "" .col = -1 'default to uilook(uiText) .BGCol = 0 .X = 0 .Y = 0 .Bits = 0 END WITH NEXT i ERASE menus ERASE mstates topmenu = -1 'delete temp files that are part of the game state deletetemps 'doesn't unload scripts: not needed killallscripts 'This only happens when using Test Game IF lump_reloading.hsp.changed THEN reload_scripts REDIM timers(numInitialTimers - 1) close_persist_reld() cleanup_game_slices() 'plotslices() should now be all zeroed out. Rather than checking, check slicedebug() (if enabled) SliceDebugDump YES SetupGameSlices reset_game_state() END SUB FUNCTION gamepadmap_from_reload(gamepad as NodePtr, byval use_dpad as bool=NO) as GamePadMap DIM gp as GamePadMap IF use_dpad THEN gp.Ud = gamepad."UP".integer gp.Rd = gamepad."RIGHT".integer gp.Dd = gamepad."DOWN".integer gp.Ld = gamepad."LEFT".integer END IF gp.A = gamepad."A".integer gp.B = gamepad."B".integer gp.X = gamepad."X".integer gp.Y = gamepad."Y".integer gp.L1 = gamepad."L1".integer gp.R1 = gamepad."R1".integer gp.L2 = gamepad."L2".integer gp.R2 = gamepad."R2".integer RETURN gp END FUNCTION DESTRUCTOR HeroWalkabout () v_free curzones END DESTRUCTOR 'Unlike resetgame(), this is called before a game first is played, 'in addition to being called after playing (resetgame/gameover/loadgame/quit). SUB reset_game_state () reset_map_state(gam.map) gam.wonbattle = NO gam.remembermusic = -1 gam.random_battle_countdown = range(100, 60) gam.mouse_enabled = NO defaultmousecursor 'init mouse state gam.pad.being_shown = NO 'Resize these after we have loaded gen() REDIM gam.stock(gen(genMaxShop), 49) REDIM gam.original_stock(gen(genMaxShop), 49) DIM root as NodePtr = get_general_reld() DIM gamepad as NodePtr = root."gamepad".ptr remap_android_gamepad 0, gamepadmap_from_reload(gamepad, NO) FOR i as integer = 1 TO 3 'Default the other gamepads to be the same as the first one '(this might be overridden in a moment) remap_android_gamepad i, gamepadmap_from_reload(gamepad, NO) NEXT i DIM player as integer IF NOT root."multiplayer_gamepads".exists THEN SetChildNode root, "multiplayer_gamepads" END IF READNODE root."multiplayer_gamepads" as multi WITHNODE multi."player" as playernode player = GetInteger(playernode) SELECT CASE player CASE 1 TO 3: remap_android_gamepad player, gamepadmap_from_reload(playernode, YES) CASE ELSE: debug "Ignore invalid multiplayer_gamepads.player id of " & player END SELECT END WITHNODE END READNODE remap_virtual_gamepad("virtual_gamepad") END SUB SUB remap_virtual_gamepad(nodename as string) DIM root as NodePtr = get_general_reld() DIM mobile as NodePtr = root."mobile_options".ptr IF mobile = 0 THEN mobile = SetChildNode(root, "mobile_options") DIM vgp as NodePtr = GetChildByName(mobile, nodename) IF vgp = 0 THEN 'node does not exist, create defaults vgp = SetChildNode(mobile, nodename) AppendChildNode(vgp, "button", scEnter) AppendChildNode(vgp, "button", scESC) END IF DIM button_id as integer = 0 READNODE vgp, ignoreall WITHNODE vgp."button" as but remap_touchscreen_button button_id, GetInteger(but) button_id += 1 END WITHNODE END READNODE FOR i as integer = button_id TO 5 remap_touchscreen_button i, 0 NEXT i update_virtual_gamepad_display() END SUB FUNCTION should_disable_virtual_gamepad() as bool IF NOT running_on_mobile() THEN RETURN NO DIM root as NodePtr = get_general_reld() DIM mobile as NodePtr = root."mobile_options".ptr IF mobile = 0 THEN mobile = SetChildNode(root, "mobile_options") RETURN mobile."disable_virtual_gamepad".exists END FUNCTION FUNCTION should_hide_virtual_gamepad_when_suspendplayer() as bool IF NOT running_on_mobile() THEN RETURN NO DIM root as NodePtr = get_general_reld() RETURN root."mobile_options"."hide_virtual_gamepad_when_suspendplayer".integer.default(NO) <> 0 END FUNCTION SUB reset_map_state (map as MapModeState) map.id = gen(genStartMap) map.lastmap = -1 map.same = NO map.name = "" END SUB 'Calculate the maximum (full) amount of level MP points 'UBOUND(ret) should be maxMPLevel SUB get_max_levelmp (ret() as integer, byval hero_level as integer) FOR mplevel as integer = 0 TO maxMPLevel ret(mplevel) = 0 NEXT DIM nextlevel as integer = 0 DIM turn_point as integer = 0 FOR mplevel as integer = 0 TO hero_level ret(nextlevel) += 1 nextlevel += 1 IF nextlevel > turn_point THEN nextlevel = 0: turn_point += 1 IF turn_point > maxMPLevel THEN turn_point = 0 NEXT END SUB 'Reset a hero's level mp to maximum amounts SUB reset_levelmp (byref hero as HeroState) get_max_levelmp(hero.levelmp(), hero.lev) END SUB SUB shop (byval id as integer) DIM storebuf(40) as integer DIM menu(10) as string DIM menuid(10) as integer DIM sn as string DIM autopick as bool DIM w as integer DIM tog as integer DIM inn as integer DIM rsr as integer DIM c as integer DIM t as integer DIM o as integer DIM page as integer DIM holdscreen as integer DIM st as MenuState FOR i as integer = 0 TO 7 menuid(i) = i NEXT i menu(0) = readglobalstring(70, "Buy", 10) menu(1) = readglobalstring(71, "Sell", 10) menu(2) = readglobalstring(73, "Hire", 10) menu(3) = readglobalstring(72, "Inn", 10) menu(4) = readglobalstring(63, "Equip", 10) menu(5) = readglobalstring(66, "Save", 10) menu(6) = readglobalstring(68, "Map", 10) menu(7) = readglobalstring(65, "Team", 10) st.size = 22 st.pt = 0 st.last = -1 '--initshop loadrecord storebuf(), game + ".sho", 40 \ 2, id sn = readbadbinstring(storebuf(), 0, 15, 0) o = 0 FOR i as integer = 0 TO 7 IF readbit(storebuf(), 17, i) THEN SWAP menu(i), menu(o) SWAP menuid(i), menuid(o) st.last = o o = o + 1 END IF NEXT i IF st.last = -1 THEN EXIT SUB IF st.last = 0 THEN autopick = YES st.last += 1 menu(st.last) = readglobalstring(74, "Exit", 10) menuid(st.last) = 8 DIM box as RectType WITH box .wide = 96 .high = (st.last + 2) * 10 .x = 160 - .wide \ 2 .y = 104 END WITH menusound gen(genAcceptSFX) '--Preserve background for display beneath top-level shop menu holdscreen = duplicatepage(vpage) page = compatpage show_virtual_gamepad() setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer usemenu st usemenusounds DIM do_quit as bool = NO DIM do_pick as bool = NO IF carray(ccCancel) > 1 THEN do_quit = YES IF carray(ccUse) > 1 THEN do_pick = YES IF get_gen_bool("/mouse/mouse_menus") ANDALSO autopick = NO THEN IF rect_collide_point(box, readmouse.pos) THEN DIM hover as integer = -1 FOR i as integer = 0 TO st.last IF rect_collide_point(XYWH(box.x, box.y + 5 + i * 10, box.wide, 10), readmouse.pos) THEN hover = i NEXT i IF hover >= 0 THEN IF (readmouse.buttons AND mouseLeft) THEN st.pt = hover END IF IF (readmouse.release AND mouseLeft) THEN do_pick = YES END IF END IF ELSE IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 THEN do_quit = YES END IF END IF IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN do_quit = YES END IF END IF IF do_quit THEN menusound gen(genCancelSFX) : EXIT DO IF do_pick ORELSE autopick THEN IF menuid(st.pt) = 8 THEN '--EXIT menusound gen(genCancelSFX) EXIT DO END IF IF menuid(st.pt) = 0 THEN '--BUY buystuff id, 0, storebuf() END IF IF menuid(st.pt) = 1 THEN '--SELL sellstuff id, storebuf() END IF IF menuid(st.pt) = 2 THEN '--HIRE buystuff id, 1, storebuf() END IF IF menuid(st.pt) = 6 THEN '--MAP minimap heropos(0) END IF IF menuid(st.pt) = 7 THEN '--TEAM hero_swap_menu 1 END IF IF menuid(st.pt) = 4 THEN '--EQUIP w = onwho(readglobalstring(108, "Equip Who?", 20)) IF w >= 0 THEN equip_menu w END IF END IF IF menuid(st.pt) = 5 THEN '--SAVE DIM slot as integer = picksave() IF slot >= 0 THEN savegame slot END IF IF menuid(st.pt) = 3 THEN '--INN IF useinn(storebuf(18), holdscreen) THEN innRestore IF storebuf(19) > 0 THEN '--Run animation for Inn trigger_script storebuf(19), 0, NO, "inn", "ID " & id, mainFibreGroup EXIT DO ELSE '--Inn has no script, do simple fade fadeout uilook(uiFadeoutInn) queue_fade_in END IF END IF copypage holdscreen, vpage END IF IF autopick THEN EXIT DO 'Re-show the virtual gamepad, in case a special screen was triggered that hid it show_virtual_gamepad() END IF copypage holdscreen, vpage edgeboxstyle box, 0, page centerbox 160, 90, LEN(sn) * 8 + 8, 16, 1, page edgeprint sn, pCentered, 85, uilook(uiText), page FOR i as integer = 0 TO st.last c = uilook(uiMenuItem): IF st.pt = i THEN c = uilook(uiSelectedItem + tog) edgeprint menu(i), pCentered, 109 + i * 10, c, page NEXT i setvispage vpage check_for_queued_fade_in dowait LOOP clearkeys freepage page freepage holdscreen evalitemtags party_change_updates END SUB 'Returns true if the player decided to sleep at the inn 'holdscreen is a copy of vpage (not a compatpage) FUNCTION useinn (byval price as integer, byval holdscreen as integer) as bool DIM menu(1) as string DIM page as integer page = compatpage DIM tog as integer DIM state as MenuState state.last = UBOUND(menu) state.size = 2 useinn = NO 'default return value menu(0) = readglobalstring(49, "Pay", 10) menu(1) = readglobalstring(50, "Cancel", 10) DIM inncost as string = readglobalstring(143, "THE INN COSTS", 20) DIM youhave as string = readglobalstring(145, "You have", 20) menusound gen(genAcceptSFX) show_virtual_gamepad() setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer DIM do_pick as bool = NO DIM do_quit as bool = NO IF carray(ccCancel) > 1 THEN do_quit = YES usemenusounds usemenu state 'alternatively IF carray(ccLeft) > 1 OR carray(ccRight) > 1 THEN menusound gen(genCursorSFX) state.pt = state.pt XOR 1 END IF IF carray(ccUse) > 1 THEN do_pick = YES IF get_gen_bool("/mouse/mouse_menus") THEN IF rect_collide_point(Type(60, 60, 200, 60), readmouse.pos) THEN DIM hover as integer = -1 FOR i as integer = 0 TO 1 IF rect_collide_point(XYWH(160 - 60\2, 94 + i * 8, 60, 8), readmouse.pos) THEN hover = i NEXT i IF hover >= 0 THEN IF (readmouse.buttons AND mouseLeft) THEN state.pt = hover IF (readmouse.release AND mouseLeft) THEN do_pick = YES END IF ELSE IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 THEN do_quit = YES END IF IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN do_quit = YES END IF IF do_quit THEN menusound gen(genCancelSFX) EXIT DO END IF IF do_pick THEN IF state.pt = 0 AND gold >= price THEN 'Pay gold -= price useinn = YES menusound gen(genAcceptSFX) EXIT DO ELSE menusound gen(genCancelSFX) END IF IF state.pt = 1 THEN EXIT DO 'Cancel END IF 'Draw screen copypage holdscreen, vpage edgeboxstyle 0, 3, 218, active_party_size() * 10 + 4, 0, page DIM y as integer = 0 FOR i as integer = 0 TO active_party_slots - 1 WITH gam.hero(i) IF .id >= 0 THEN DIM col as integer = uilook(uiText) edgeprint .name, 128 + ancRight, 5 + y * 10, col, page edgeprint .stat.cur.hp & "/" & .stat.max.hp, 136, 5 + y * 10, col, page y += 1 END IF END WITH NEXT i centerfuz 160, 90, 200, 60, 1, page DIM menuwidth as integer = large(textwidth(menu(0)), textwidth(menu(1))) + 8 rectangle 160 + ancCenter, 92, menuwidth, 22, uilook(uiHighlight), page 'orig colour 20 edgeprint inncost & " " & price_string(price), pCentered, 70, uilook(uiText), page edgeprint youhave & " " & price_string(gold), pCentered, 80, uilook(uiText), page FOR i as integer = 0 TO 1 DIM col as integer col = uilook(uiMenuItem) IF state.pt = i THEN col = uilook(uiSelectedItem + tog) edgeprint menu(i), pCentered, 94 + i * 8, col, page NEXT i setvispage vpage check_for_queued_fade_in dowait LOOP freepage page party_change_updates END FUNCTION 'Tag debug menu SUB tagdisplay (page as integer) STATIC st as MenuState DIM lineh as integer = lineheight() 'It's possible to set other tags with "set tag", so don't limit to named tags st.last = max_tag() 'gen(genMaxTagName) IF gam.debug_showtags = 2 THEN st.size = small(st.last, get_resolution().h \ lineh - 1) ELSE st.size = 6 END IF 'Controls IF keyval(scCtrl) > 0 ORELSE keyval(scShift) > 0 THEN IF keyval(scNumpadMinus) > 1 OR keyval(scMinus) > 1 THEN settag st.pt, NO tag_updates END IF IF keyval(scNumpadPlus) > 1 OR keyval(scPlus) > 1 THEN settag st.pt, YES tag_updates END IF ELSE IF keyval(scEnd) > 1 THEN 'Go to last named tag instead of tag 15999 st.pt = gen(genMaxTagName) correct_menu_state st ELSEIF usemenu(st, scMinus, scPlus) = NO THEN 'little bit hacky... usemenu(st, scNumpadMinus, scNumpadPlus) END IF END IF IF keyval(scTab) > 1 THEN settag st.pt, NOT istag(st.pt, 0) tag_updates END IF ''Draw the menu 'Help message, and a fuzzy box behind it DIM helpmsg as string = !"To scroll:\n+/-/pgup/down\nTAB to toggle\nF4 expand/quit" DIM helpbox as XYPair = textsize(helpmsg, , fontEdged) fuzzyrect pRight, pTop, helpbox.w, helpbox.h, uilook(uiOutline), page wrapprint helpmsg, pRight, 0, uilook(uiMenuItem), page 'This fuzzy box is exactly wide enough for the max tag name length fuzzyrect 0, 0, 208, (st.size + 1) * lineh, uilook(uiOutline), page DIM as integer col0 = findrgb(140,140,140), col1 = findrgb(230,230,230) FOR i as integer = st.top TO st.top + st.size DIM temp as string = i & IIF(istag(i, 0), " +", " -") SELECT CASE i CASE 0, 1 temp += "Reserved Tag" CASE IS > 1 temp += load_tag_name(i) IF tag_is_autoset(i) THEN temp &= " [AUTOSET]" IF i = gen(genMaxTagName) + 1 THEN temp &= "[NO MORE NAMED TAGS]" END SELECT DIM col as integer IF istag(i, 0) THEN col = col1 ELSE col = col0 DIM y as integer = (i - st.top) * lineh edgeprint temp, 8, y, col, page IF i = st.pt THEN edgeprint CHR(26), 0, y, uilook(uiText), page NEXT i END SUB FUNCTION default_margin() as integer IF running_on_console() THEN 'On OUYA, default to a safe 8% margin RETURN 8 END IF ' on all other platforms, default to no margin RETURN 0 END FUNCTION FUNCTION default_margin_for_game() as integer 'Only for use when a game is currently loaded DIM root as NodePtr root = get_general_reld() RETURN root."console_options"."safe_margin".default(default_margin()) END FUNCTION '========================================================================================== ' Playing time '========================================================================================== FUNCTION playtime (byval d as integer, byval h as integer, byval m as integer) as string DIM s as string = "" SELECT CASE d CASE 1 s = s & d & " " & readglobalstring(154, "day", 10) & " " CASE IS > 1 s = s & d & " " & readglobalstring(155, "days", 10) & " " END SELECT SELECT CASE h CASE 1 s = s & h & " " & readglobalstring(156, "hour", 10) & " " CASE IS > 1 s = s & h & " " & readglobalstring(157, "hours", 10) & " " END SELECT SELECT CASE m CASE 1 s = s & m & " " & readglobalstring(158, "minute", 10) & " " CASE IS > 1 s = s & m & " " & readglobalstring(159, "minutes", 10) & " " END SELECT RETURN s END FUNCTION SUB playtimer STATIC n as double IF TIMER >= n + 1 OR n - TIMER > 3600 THEN n = INT(TIMER) gen(genSeconds) = gen(genSeconds) + 1 WHILE gen(genSeconds) >= 60 gen(genSeconds) = gen(genSeconds) - 60 gen(genMinutes) = gen(genMinutes) + 1 WEND WHILE gen(genMinutes) >= 60 gen(genMinutes) = gen(genMinutes) - 60 gen(genHours) = gen(genHours) + 1 refresh_keepalive_file WEND WHILE gen(genHours) >= 24 gen(genHours) = gen(genHours) - 24 IF gen(genDays) < 32767 THEN gen(genDays) = gen(genDays) + 1 WEND END IF IF autosnap > 0 ANDALSO (get_tickcount MOD autosnap) = 0 THEN write_checkpoint END IF END SUB