'OHRRPGCE GAME - Various builtin menus '(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and the OHRRPGCE Developers 'Dual licensed under the GNU GPL v2+ and MIT Licenses. Read LICENSE.txt for terms and disclaimer of liability. #include "config.bi" #include "allmodex.bi" #include "common.bi" #include "loading.bi" #include "gglobals.bi" #include "const.bi" #include "uiconst.bi" #include "game_udts.bi" #include "savegame.bi" #include "sliceedit.bi" #include "bcommon.bi" #include "game.bi" #include "scriptcommands.bi" #include "yetmore2.bi" #include "walkabouts.bi" #include "moresubs.bi" #include "menustuf.bi" #include "bmodsubs.bi" #include "bmod.bi" #include "plankmenu.bi" '--SUBs and FUNCTIONS only used locally DECLARE SUB buystuff_make_slices(buyst as ShopBuyState) DECLARE SUB buystuff_refresh_menu (buyst as ShopBuyState, menu as MenuDef, st as MenuState) DECLARE SUB buystuff_refresh_selected (buyst as ShopBuyState, byval thing as NodePtr) DECLARE SUB buystuff_refresh_empty (buyst as ShopBuyState) DECLARE SUB buystuff_init_info_menu(buyst as ShopBuyState) DECLARE SUB buystuff_do_purchase(buyst as ShopBuyState, byval thing as NodePtr, byval thing_slot as integer) DECLARE FUNCTION buystuff_can_show(byval slot as NodePtr, byval shop_id as integer) as bool DECLARE FUNCTION buystuff_can_buy(byval thing as NodePtr, buyst as ShopBuyState, byval sound_and_alert as bool=NO) as bool DECLARE SUB buystuff_set_alert(buyst as ShopBuyState, alert as string, byval ticks as integer = 18) DECLARE SUB equip_menu_setup (byref st as EquipMenuState) DECLARE SUB equip_menu_do_equip(byval item as integer, byref st as EquipMenuState) DECLARE SUB equip_menu_back_to_menu(byref st as EquipMenuState) DECLARE SUB equip_menu_stat_bonus(byref st as EquipMenuState) DECLARE FUNCTION equip_menu_available_item_id(st as EquipMenuState) as integer DECLARE FUNCTION equip_menu_available_item_caption(st as EquipMenuState, byval menuslot as integer) as string DECLARE FUNCTION equip_menu_equipped_item_info(st as EquipMenuState) as string DECLARE FUNCTION equip_menu_available_item_info(st as EquipMenuState) as string DECLARE FUNCTION menu_attack_targ_picker(byval attack_id as integer, byval learn_id as integer, byval attacker as integer, use_caption as string, byval x_offset as integer=0, byval really_use_attack as bool=YES) as bool DECLARE FUNCTION menu_attack_targ_picker_make_slices(x_offset as integer) as Slice Ptr DECLARE SUB menu_targpick_plank_state_callback (byval sl as Slice Ptr, byval state as PlankItemState) DECLARE FUNCTION getOOBtarg (byval search_direction as integer, byval attacker as integer, byref target as integer, byval atk as integer) as bool DECLARE SUB spells_menu_refresh_list(sp as SpellsMenuState) DECLARE SUB spells_menu_refresh_hero(sp as SpellsMenuState) DECLARE SUB spells_menu_control(sp as SpellsMenuState) DECLARE SUB spells_menu_paint (byref sp as SpellsMenuState) DECLARE FUNCTION picksaveload (byval loading as bool, byval newgame_opt as bool = YES, byval beep_if_no_saves as bool = NO) as integer DECLARE SUB picksave_draw(menu() as string, byval loading as bool, byval newgame_opt as bool, disable_exit as bool=NO, sprites() as GraphicPair, pv() as SaveSlotPreview, mapname() as string, lev() as string, byref st as MenuState, byval page as integer) DECLARE FUNCTION picksave_confirm(menu() as string, byval loading as bool, byval newgame_opt as bool, exit_load_disabled as bool=NO, sprites() as GraphicPair, pv() as SaveSlotPreview, mapname() as string, lev() as string, byref st as MenuState, byval holdscreen as integer, byval page as integer) as bool DECLARE SUB picksave_update_mouse(st as MenuState, byval newgame_opt as bool, byref drag_start_top as integer) DECLARE SUB sellstuff_refresh(byval last_stuff as integer, byval recordsize as integer, permask() as integer, price() as integer, stuffdata() as integer) DECLARE SUB sellstuff_infostr(byval ic as integer, info as string, b() as integer, permask() as integer, price() as integer, byval stuff_count as integer, byval recordsize as integer) DECLARE SUB get_virtual_keyboard_buttons (byval sl as Slice Ptr, byref arr as any ptr vector) ENUM ShopMenuItemType shopmtypeItem = mtypeLAST + 1 'Don't overlap with MenuItemType values to avoid draw_menu glitches shopmtypeHero END ENUM SUB buystuff (byval shop_id as integer, byval shoptype as integer, storebuf() as integer) 'shoptype is 0 for items and 1 for heroes DIM buyst as ShopBuyState WITH buyst .shop_id = shop_id .shoptype = shoptype END WITH DIM stuff_doc as DocPtr stuff_doc = CreateDocument() buyst.stuff_list = CreateNode(stuff_doc, "stuff_list") SetRootNode(stuff_doc, buyst.stuff_list) load_shop_stuff shop_id, buyst.stuff_list DIM holdscreen as integer = duplicatepage(vpage) '--walking frame numbers used for animating heroes DIM walks(15) as integer FOR i as integer = 0 TO 10 STEP 2 walks(i) = frameSTEP walks(i+1) = frameSTAND NEXT i walks(11) = frameATTACKA walks(12) = frameATTACKA walks(13) = frameATTACKB walks(14) = frameATTACKB buystuff_make_slices buyst DIM panel as Slice Ptr = LookupSlice(SL_SHOP_BUY_INFO_PANEL, buyst.root_sl) IF panel = 0 THEN debug "Shop Buy Slice Collection is missing its Info Panel" '--Create the menu DIM menu as MenuDef DIM st as MenuState st.active = YES st.select_by_mouse_release = YES buystuff_refresh_menu buyst, menu, st IF menu.numitems = 0 THEN 'The menu will immediately quit menusound gen(genCancelSFX) ELSE show_virtual_gamepad() menusound gen(genAcceptSFX) END IF setkeys DO setwait speedcontrol setkeys buyst.tog = buyst.tog XOR 1 IF buyst.tog THEN loopvar buyst.walk, 0, 15 DIM do_quit as bool = NO DIM do_pick as bool = NO playtimer IF get_gen_bool("/mouse/mouse_menus") THEN IF panel ANDALSO SliceCollidePoint(panel, readmouse.pos) THEN 'Ignore most clicks on the info panel IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN do_quit = YES ELSEIF rect_collide_point(st.rect, readmouse.pos) THEN 'In the list of stuff IF st.hover >= 0 THEN IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 ANDALSO st.pt = st.hover THEN do_pick = YES END IF mouse_drag_menu st ELSE 'Anywhere else IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 THEN do_quit = YES IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN do_quit = YES END IF END IF usemenusounds IF usemenu(st) THEN IF menu.numitems > 0 THEN buystuff_refresh_selected buyst, menu.items[st.pt]->dataptr END IF END IF IF game_check_cancel_key() THEN do_quit = YES IF game_check_use_key() THEN do_pick = YES buyst.alert_ticks = large(0, buyst.alert_ticks - 1) IF buyst.alert_ticks <= 0 THEN buyst.alert_box->Visible = NO END IF IF menu.numitems > 0 THEN buyst.selected = menu.items[st.pt]->dataptr buyst.curslot = menu.items[st.pt]->sub_t END IF IF keyval(scF8) > 1 THEN slice_editor buyst.root_sl st.need_update = YES END IF IF do_quit THEN menusound gen(genCancelSFX) EXIT DO END IF IF do_pick ANDALSO menu.numitems > 0 THEN IF buystuff_can_buy(buyst.selected, buyst, YES) THEN buystuff_do_purchase(buyst, buyst.selected, buyst.curslot) st.need_update = YES END IF END IF IF st.need_update THEN buystuff_refresh_menu buyst, menu, st st.need_update = NO END IF 'Auto-quit when no items left. Don't play CancelSFX, as we might have just bought something IF menu.numitems = 0 ANDALSO buyst.alert_ticks = 0 THEN EXIT DO 'Animate hero walking to indicate equipability DIM hero_sl as Slice Ptr FOR i as integer = 0 TO 3 IF buyst.party_sl(i)->Extra(0) THEN ChangeSpriteSlice buyst.party_sl(i), , , , walks(buyst.walk) ELSE ChangeSpriteSlice buyst.party_sl(i), , , , 0 END IF NEXT i ChangeSpriteSlice buyst.hire_sl, , , , walks(buyst.walk) 'Draw the screen copypage holdscreen, vpage 'Draw menu first so it goes below pricebox in case of overlap draw_menu menu, st, vpage DrawSlice buyst.root_sl, vpage draw_menu buyst.info, buyst.info_st, vpage setvispage vpage dowait LOOP '--Restore the saved screen freepage holdscreen DeleteSlice @(buyst.root_sl) FreeDocument stuff_doc END SUB SUB buystuff_do_purchase(buyst as ShopBuyState, byval thing as NodePtr, byval thing_slot as integer) IF thing = 0 THEN debug "buystuff_do_purchase null thing ptr" : EXIT SUB DIM thing_kind as string thing_kind = NodeName(thing) DIM buy as NodePtr IF thing_kind = "hero" THEN buy = thing."hire".ptr menusound gen(genHireSFX) DIM hero_id as integer = GetInteger(thing) DIM party_slot as integer = first_free_slot_in_party() DIM her as HeroDef loadherodata her, hero_id DIM hire_level as integer IF thing."level".exists THEN hire_level = thing."level".integer ELSE hire_level = her.def_level IF hire_level < 0 THEN hire_level = averagelev() END IF addhero hero_id, party_slot, hire_level buystuff_set_alert buyst, gam.hero(party_slot).name & " " & readglobalstring(95, "Joined!", 20) ELSEIF thing_kind = "item" THEN buy = thing."buy".ptr menusound gen(genBuySFX) DIM item_id as integer = GetInteger(thing) getitem item_id, 1 buystuff_set_alert buyst, readglobalstring(93, "Purchased", 20) & " " & readitemname(item_id) ELSE debug "buystuff_do_purchase: invalid thing """ & thing_kind & """" menusound gen(genCantBuySFX) EXIT SUB END IF 'deduct stock from gam.stock() (-1 is infinite, 0 is unloaded, 1+ is actual stock +1) DIM stockidx as integer = thing."stockidx".default(thing_slot) IF gam.stock(buyst.shop_id, stockidx) > 1 THEN gam.stock(buyst.shop_id, stockidx) -= 1 END IF DIM set_tag as integer = buy."set_tag".default(0).integer IF ABS(set_tag) >= 2 THEN IF set_tag > 0 THEN settag set_tag, YES IF set_tag < 0 THEN settag ABS(set_tag), NO END IF gold -= buy."price".default(0).integer READNODE buy, ignoreall WITHNODE buy."trade" as trade delitem GetInteger(trade), trade."count".integer END WITHNODE END READNODE 'the last thing to do is re-eval the item and hero tags in case 'something changed: we do this immediately because the tags affect 'purchaseability of other items evalherotags evalitemtags tag_updates END SUB 'thing is either "hero" or "item" node of a shop thing, in the new RELOAD respresentation of STF SUB buystuff_refresh_selected (buyst as ShopBuyState, byval thing as NodePtr) ChangeTextSlice buyst.money_sl, readglobalstring(145, "You have", 20) & " " & price_string(gold) DIM thing_kind as string = NodeName(thing) DIM thing_id as integer = GetInteger(thing) DIM buy as NodePtr DIM info as string DIM itembuf(dimbinsize(binITM)) as integer DIM trade_str as string buystuff_init_info_menu buyst IF thing_kind = "hero" THEN buy = thing."hire".ptr trade_str = readglobalstring(87, "Joins for", 20) DIM her as HeroDef loadherodata her, thing_id append_menu_item buyst.info, her.name buyst.info.last->disabled = YES DIM hire_level as integer IF thing."level".exists THEN hire_level = thing."level".integer ELSE hire_level = her.def_level IF hire_level < 0 THEN hire_level = averagelev() END IF loaditemdata itembuf(), her.def_weapon append_menu_item buyst.info, (atlevel(hire_level, her.lev0.hp, her.levMax.hp) + itembuf(54 + 0)) & " " & statnames(statHP) buyst.info.last->disabled = YES buyst.hero_box->Visible = NO buyst.hire_box->Visible = YES ChangeSpriteSlice buyst.hire_sl, , her.sprite, her.sprite_pal IF her.portrait >= 0 THEN buyst.portrait_box->Visible = YES ChangeSpriteSlice buyst.portrait_sl, , her.portrait, her.portrait_pal ELSE buyst.portrait_box->Visible = NO END IF ELSEIF thing_kind = "item" THEN buy = thing."buy".ptr trade_str = readglobalstring(85, "Trade for", 20) append_menu_item buyst.info, readitemname(thing_id) DIM desc_str as string = wordwrap(readitemdescription(thing_id), buyst.info_sl->Width / 8) REDIM spl() as string split desc_str, spl() FOR i as integer = 0 TO UBOUND(spl) append_menu_item buyst.info, spl(i) buyst.info.last->col = uilook(uiDisabledItem) NEXT i buyst.hire_box->Visible = NO buyst.portrait_box->Visible = NO loaditemdata itembuf(), thing_id IF item_is_equippable(itembuf()) THEN 'This item is equippable buyst.hero_box->Visible = YES DIM eqprefix as string = readglobalstring(99, "Equip:", 10) IF item_is_equippable_in_slot(itembuf(), 0) THEN append_menu_item buyst.info, eqprefix & " " & readglobalstring(38, "Weapon", 10) FOR i as integer = 1 to 4 IF item_is_equippable_in_slot(itembuf(), i) THEN append_menu_item buyst.info, eqprefix & " " & readglobalstring(24 + i, "Armor" & i) NEXT i DIM bonus as integer FOR i as integer = 0 to 11 bonus = itembuf(54 + i) IF bonus <> 0 THEN IF bonus > 0 THEN append_menu_item buyst.info, "+" & bonus & " " & statnames(i) buyst.info.last->col = uilook(uiSelectedItem) END IF IF bonus < 0 THEN append_menu_item buyst.info, bonus & " " & statnames(i) buyst.info.last->col = uilook(uiSelectedDisabled) END IF END IF NEXT i DIM hero_id as integer FOR i as integer = 0 TO 3 buyst.party_sl(i)->Extra(0) = NO ChangeRectangleSlice buyst.party_box(i), 0, , , , transOpaque hero_id = gam.hero(i).id IF hero_id >= 0 THEN IF item_read_equipbit(itembuf(), hero_id) THEN buyst.party_sl(i)->Extra(0) = YES ChangeRectangleSlice buyst.party_box(i), 3, , , , transOpaque END IF END IF ChangeRectangleSlice buyst.party_box(i), , , , borderLine NEXT i ELSE 'Not equippable buyst.hero_box->Visible = NO END IF END IF '--show stock ' There's no real need to provide a default for stockidx; it should always exist DIM stockamount as integer = gam.stock(buyst.shop_id, thing."stockidx") IF stockamount > 1 THEN append_menu_item buyst.info, stockamount - 1 & " " & readglobalstring(97, "in stock", 20) END IF DIM already_count as integer = countitem(thing_id) IF thing_kind = "item" ANDALSO already_count > 0 THEN append_menu_item buyst.info, readglobalstring(324, "You own", 20) & " " & already_count END IF DIM equipped_count as integer = count_equipped_item(thing_id) IF equipped_count > 0 THEN append_menu_item buyst.info, readglobalstring(326, "Equipped", 20) & " " & equipped_count END IF IF buy THEN DIM price as integer = buy."price".default(0).integer DIM price_str as string = "" IF price THEN price_str = price_string(price) END IF READNODE buy, ignoreall WITHNODE buy."trade" as trade IF LEN(price_str) THEN price_str &= ", " ELSE price_str &= trade_str & " " END IF price_str &= readitemname(GetInteger(trade)) DIM count as integer = trade."count".default(0).integer IF count > 1 THEN price_str &= CHR(1) & count DIM own as string = readglobalstring(328, " (have $N)", 20) replacestr own, "$N", STR(countitem(GetInteger(trade))) price_str &= own END WITHNODE END READNODE 'Set price text but don't wrap yet ChangeTextSlice buyst.price_sl, price_str, , , NO buyst.price_sl->Fill = NO IF LEN(price_str) THEN buyst.price_box->Visible = YES buyst.price_box->Width = small(buyst.price_sl->Width + 16, buyst.root_sl->Width) buyst.price_box->Height = 16 IF buyst.price_box->Width >= buyst.root_sl->Width THEN 'Fix the box at max width and wrap buyst.price_sl->Fill = YES buyst.price_sl->FillMode = sliceFillHoriz 'Update width of price_sl RefreshSliceScreenPos buyst.price_sl 'Enable wrapping, which calculates the price_sl height ChangeTextSlice buyst.price_sl, , , , YES buyst.price_box->Height = 8 + buyst.price_sl->Height END IF ELSE buyst.price_box->Visible = NO END IF END IF init_menu_state buyst.info_st, buyst.info buyst.info_st.active = NO END SUB SUB buystuff_refresh_empty (buyst as ShopBuyState) ChangeTextSlice buyst.money_sl, readglobalstring(145, "You have", 20) & " " & price_string(gold) buystuff_init_info_menu buyst buyst.hero_box->Visible = NO buyst.hire_box->Visible = NO ChangeTextSlice buyst.price_sl, readglobalstring(309, "The shop is empty", 30) buyst.price_box->Width = buyst.price_sl->Width + 16 'Move the box up so it's not hidden behind the 'Bought...' alert box: once the 'alert disappears, the menu will close. WITH *buyst.price_box .AnchorVert = alignCenter .AlignVert = alignCenter END WITH init_menu_state buyst.info_st, buyst.info buyst.info_st.active = NO END SUB SUB buystuff_init_info_menu(buyst as ShopBuyState) ClearMenuData buyst.info IF buyst.info_sl = 0 THEN debug "buyst.info_sl null ptr" ELSE WITH buyst.info .offset.x = buyst.info_sl->ScreenX - (vpages(vpage)->w / 2) + buyst.info_sl->Width / 2 .offset.y = buyst.info_sl->ScreenY - (vpages(vpage)->h / 2 + 2) .anchorvert = alignTop .bordersize = -8 .maxrows = buyst.info_sl->Height / 8 .itemspacing = -2 .min_chars = buyst.info_sl->Width / 8 .max_chars = buyst.info_sl->Width / 8 .no_box = YES 'comment this out if you want to change/debug the box positioning END WITH END IF END SUB 'Initialises or resets an element of gam.stock() as needed, and returns its value. 'Pass in data from the STF record: 'stockidx: This is the non-offset index in gam.stock(): subtract 1 from STF 'stockidx' value 'thingtype: 0 for items, 1 for heroes 'initial_stock: 'In Stock' field, which differs from gam.stock(): -1 for infinite, >= 0 for finite. FUNCTION initialize_stock OVERLOAD (shop_id as integer, stockidx as integer, thingtype as integer, thingid as integer, initial_stock as integer) as integer DIM byref stock as integer = gam.stock(shop_id, stockidx) DIM byref origstock as OriginalStock = gam.original_stock(shop_id, stockidx) 'If this thing record has been edited since 'stock' was initialised, reset it '(When loading old saved games, gam.stock() will be initialised but gam.original_stock() won't be) WITH origstock IF .thingtype >= 0 THEN 'Is initialised IF .thingtype <> thingtype ORELSE .thingid <> thingid ORELSE _ (.stock < 0) <> (initial_stock < 0) THEN 'The 'thing' changed, or the In Stock level was changed to/from Infinite stock = 0 'Reset gam.stock() .thingtype = -1 'Reset gam.original_stock() ELSEIF .stock <> initial_stock THEN 'If the initial stock was changed to some different finite value, adjust by the difference DIM diff as integer = initial_stock - .stock stock = large(1, stock + diff) 'Minimum of 0 in stock .thingtype = -1 'Reset gam.original_stock() END IF END IF IF .thingtype < 0 THEN 'Needs (re-)initialising .thingtype = thingtype .thingid = thingid .stock = initial_stock END IF END WITH 'Load stock data if uninitialised or reset IF stock = 0 THEN stock = IIF(initial_stock < 0, initial_stock, initial_stock + 1) RETURN stock END FUNCTION 'Initialises or resets an element of gam.stock() as needed, and returns its value. FUNCTION initialize_stock(shop_id as integer, slot as NodePtr) as integer DIM thing as NodePtr IF slot."hero".exists THEN thing = slot."hero".ptr ELSE thing = slot."item".ptr END IF 'There's no real need to provide a default for stockidx, it should always exist DIM stockidx as integer = thing."stockidx".default(GetInteger(slot)) DIM thingtype as integer = IIF(slot."item".exists, 0, 1) DIM thingid as integer = GetInteger(thing) DIM initial_stock as integer IF thing."stock".exists THEN initial_stock = thing."stock" ELSEIF thing."infinite".exists THEN initial_stock = -1 END IF RETURN initialize_stock(shop_id, stockidx, thingtype, thingid, initial_stock) END FUNCTION FUNCTION buystuff_can_show(byval slot as NodePtr, byval shop_id as integer) as bool 'Return YES if this thing should appear in the menu DIM thing as NodePtr DIM buy as NodePtr IF slot."hero".exists THEN thing = slot."hero".ptr buy = thing."hire".ptr ELSEIF slot."item".exists THEN thing = slot."item".ptr buy = thing."buy".ptr ELSE debug "buystuff_can_show: no hero or item node" RETURN NO END IF 'Initialise and retrieve gam.stock() value DIM stock as integer = initialize_stock(shop_id, slot) IF stock = 1 THEN RETURN NO 'This item is out of stock IF buy."require_tag".exists THEN IF NOT istag(tag(), buy."require_tag", YES) THEN RETURN NO END IF END IF RETURN YES END FUNCTION FUNCTION buystuff_can_buy(byval thing as NodePtr, buyst as ShopBuyState, byval sound_and_alert as bool=NO) as bool IF thing = 0 THEN debug "buystuff_can_buy: null thing ptr" : RETURN NO DIM thing_kind as string = NodeName(thing) DIM thing_id as integer = GetInteger(thing) DIM buy as Nodeptr DIM alert as string IF thing_kind = "hero" THEN buy = thing."hire".ptr 'debug "Hero=" & getheroname(thing_id) IF active_party_size() = active_party_slots() ORELSE free_slots_in_party() <= 0 THEN IF sound_and_alert THEN menusound gen(genCantBuySFX) buystuff_set_alert buyst, readglobalstring(100, "No room in party", 20) END IF RETURN NO END IF alert = readglobalstring(91, "Cannot Hire", 20) ELSEIF thing_kind = "item" THEN buy = thing."buy".ptr 'debug "item=" & readitemname(thing_id) IF NOT room_for_item(thing_id) THEN IF sound_and_alert THEN menusound gen(genCantBuySFX) buystuff_set_alert buyst, readglobalstring(305, "No room in inventory", 30) END IF RETURN NO END IF alert = readglobalstring(89, "Cannot Afford", 20) ELSE debug "buystuff_can_buy: unknown stuff kind """ & thing_kind & """" menusound gen(genCantBuySFX) RETURN NO END IF 'debug " price=" & buy."price".default(0).integer IF buy."price".default(0).integer > gold THEN IF sound_and_alert THEN menusound gen(genCantBuySFX) buystuff_set_alert buyst, alert END IF RETURN NO END IF READNODE buy, ignoreall WITHNODE buy."trade" as trade 'debug " trade=" & GetInteger(trade) & " " & readitemname(GetInteger(trade)) & " *" & trade."count".integer IF countitem(GetInteger(trade)) < trade."count".integer THEN IF sound_and_alert THEN menusound gen(genCantBuySFX) buystuff_set_alert buyst, alert END IF RETURN NO END IF END WITHNODE END READNODE RETURN YES END FUNCTION FUNCTION is_shop_empty(byval shop_id as integer, byval shoptype as integer) as bool 'Load the shop stuff state DIM stuff_doc as DocPtr stuff_doc = CreateDocument() DIM stuff_list as NodePtr stuff_list = CreateNode(stuff_doc, "stuff_list") SetRootNode(stuff_doc, stuff_list) load_shop_stuff shop_id, stuff_list DIM available_stuff_count as integer = 0 READNODE stuff_list WITHNODE stuff_list."stuff_slot" as stuff_slot IF buystuff_can_show(stuff_slot, shop_id) THEN IF shoptype = 0 THEN IF stuff_slot."item".exists THEN available_stuff_count += 1 END IF ELSEIF shoptype = 1 THEN IF stuff_slot."hero".exists THEN available_stuff_count += 1 END IF END IF END IF END WITHNODE END READNODE RETURN available_stuff_count = 0 END FUNCTION SUB buystuff_refresh_menu (buyst as ShopBuyState, menu as MenuDef, st as MenuState) DIM thing as NodePtr DIM id as integer ClearMenuData menu WITH menu .textalign = alignLeft .anchorhoriz = alignLeft .anchorvert = alignTop .offset.x = -155 .offset.y = -95 .bordersize = -2 .min_chars = 16 .max_chars = 16 .maxrows = 16 .highlight_selection = YES END WITH DIM stuff_list as NodePtr = buyst.stuff_list READNODE stuff_list WITHNODE stuff_list."stuff_slot" as stuff_slot IF buystuff_can_show(stuff_slot, buyst.shop_id) THEN IF buyst.shoptype = 0 THEN IF stuff_slot."item".exists THEN thing = stuff_slot."item".ptr id = stuff_slot."item".integer append_menu_item menu, thing."name".default(readitemname(id)).string menu.last->t = shopmtypeItem 'Not actually used menu.last->sub_t = GetInteger(stuff_slot) menu.last->dataptr = thing menu.last->disabled = NOT buystuff_can_buy(thing, buyst) END IF ELSEIF buyst.shoptype = 1 THEN IF stuff_slot."hero".exists THEN thing = stuff_slot."hero".ptr id = stuff_slot."hero".integer append_menu_item menu, thing."name".default(getheroname(id)).string menu.last->t = shopmtypeHero 'Not actually used menu.last->sub_t = GetInteger(stuff_slot) menu.last->dataptr = thing menu.last->disabled = NOT buystuff_can_buy(thing, buyst) END IF END IF END IF END WITHNODE END READNODE FOR i as integer = 0 TO 3 IF gam.hero(i).id >= 0 THEN ChangeSpriteSlice buyst.party_sl(i), , gam.hero(i).battle_pic, gam.hero(i).battle_pal ELSE buyst.party_sl(i)->Visible = NO END IF NEXT i buyst.price_box->Width = buyst.price_sl->Width + 16 init_menu_state st, menu IF menu.numitems = 0 THEN buystuff_refresh_empty buyst ELSE buystuff_refresh_selected buyst, menu.items[st.pt]->dataptr END IF END SUB SUB buystuff_make_slices(buyst as ShopBuyState) '--Create the slices buyst.root_sl = NewSliceOfType(slSpecial) WITH *(buyst.root_sl) .Fill = YES END WITH RefreshSliceScreenPos buyst.root_sl 'The container for the right side of the shop DIM right_panel as Slice Ptr right_panel = NewSliceOfType(slContainer, buyst.root_sl) WITH *right_panel .AnchorHoriz = alignRight .AlignHoriz = alignRight .Width = 168 .Height = 176 .PaddingTop = 25 .Lookup = SL_SHOP_BUY_INFO_PANEL END WITH DIM your_money_box as Slice Ptr your_money_box = NewSliceOfType(slRectangle, right_panel) WITH *your_money_box .AnchorHoriz = alignCenter .AlignHoriz = alignCenter .Width = .parent->Width .Height = INT(.parent->PaddingTop * 0.75) .Y = -(.parent->PaddingTop) + 4 END WITH ChangeRectangleSlice your_money_box, 3, , , , transOpaque buyst.money_sl = NewSliceOfType(slText, your_money_box) WITH *(buyst.money_sl) .AnchorHoriz = alignCenter .AnchorVert = alignCenter .AlignHoriz = alignCenter .AlignVert = alignCenter END WITH ChangeTextSlice buyst.money_sl, "You have ??? Gold" , uilook(uiGold), YES DIM info_box as Slice Ptr info_box = NewSliceOfType(slRectangle, right_panel) WITH *info_box .Fill = YES .PaddingTop = 4 .PaddingBottom = 4 .PaddingLeft = 4 .PaddingRight = 4 END WITH ChangeRectangleSlice info_box, 0, , , , transOpaque RefreshSliceScreenPos info_box buyst.hero_box = NewSliceOfType(slContainer, info_box) WITH *(buyst.hero_box) .AnchorVert = alignBottom .AlignVert = alignBottom .Width = .parent->Width - (.parent->PaddingLeft + .parent->PaddingRight) .Height = 44 END WITH DIM w_tmp as integer = buyst.hero_box->Width / 4 FOR i as integer = 0 TO 3 buyst.party_box(i) = NewSliceOfType(slRectangle, buyst.hero_box) WITH *(buyst.party_box(i)) .Width = 38 .Height = 44 .X = w_tmp * i + (w_tmp - 32) / 2 END WITH ChangeRectangleSlice buyst.party_box(i), 0, , , , transOpaque ChangeRectangleSlice buyst.party_box(i), , , , borderLine buyst.party_sl(i) = NewSliceOfType(slSprite, buyst.party_box(i)) WITH *(buyst.party_sl(i)) .AnchorVert = alignCenter .AlignVert = alignCenter .AnchorHoriz = alignCenter .AlignHoriz = alignCenter END WITH ChangeSpriteSlice buyst.party_sl(i), sprTypeHero NEXT i 'info_sl is just used later to set the position and size of the info menu buyst.info_sl = NewSliceOfType(slContainer, info_box) WITH *(buyst.info_sl) .Width = .parent->Width - (.parent->PaddingLeft + .parent->PaddingRight) .Height = .parent->Height - (.parent->PaddingTop + .parent->PaddingBottom) - buyst.hero_box->Height END WITH RefreshSliceScreenPos buyst.info_sl buyst.hire_box = NewSliceOfType(slRectangle, right_panel) WITH *(buyst.hire_box) .AnchorVert = alignBottom .AlignVert = alignBottom .X = 30 .Y = -20 .Width = 38 .Height = 44 .Visible = NO END WITH ChangeRectangleSlice buyst.hire_box, 3 buyst.hire_sl = NewSliceOfType(slSprite, buyst.hire_box) WITH *(buyst.hire_sl) .AnchorVert = alignCenter .AlignVert = alignCenter .AnchorHoriz = alignCenter .AlignHoriz = alignCenter END WITH ChangeSpriteSlice buyst.hire_sl, sprTypeHero buyst.portrait_box = NewSliceOfType(slRectangle, right_panel) WITH *(buyst.portrait_box) .AnchorVert = alignBottom .AlignVert = alignBottom .AnchorHoriz = alignRight .AlignHoriz = alignRight .X = -20 .Y = -20 .Width = 54 .Height = 54 .Visible = NO END WITH ChangeRectangleSlice buyst.portrait_box, 3 buyst.portrait_sl = NewSliceOfType(slSprite, buyst.portrait_box) WITH *(buyst.portrait_sl) .AnchorVert = alignCenter .AlignVert = alignCenter .AnchorHoriz = alignCenter .AlignHoriz = alignCenter END WITH ChangeSpriteSlice buyst.portrait_sl, sprTypePortrait buyst.price_box = NewSliceOfType(slRectangle, buyst.root_sl) WITH *(buyst.price_box) .AnchorHoriz = alignCenter .AlignHoriz = alignCenter .AnchorVert = alignBottom .AlignVert = alignBottom .Y = -3 .Height = 16 .PaddingLeft = 8 .PaddingRight = 8 END WITH ChangeRectangleSlice buyst.price_box, 0 buyst.price_sl = NewSliceOfType(slText, buyst.price_box) WITH *(buyst.price_sl) .AnchorVert = alignCenter .AlignVert = alignCenter .AnchorHoriz = alignCenter .AlignHoriz = alignCenter .Fill = NO .FillMode = sliceFillHoriz END WITH ChangeTextSlice buyst.price_sl, "Price goes here", uilook(uiText), YES buyst.alert_box = NewSliceOfType(slRectangle, buyst.root_sl) WITH *(buyst.alert_box) .AnchorHoriz = alignCenter .AlignHoriz = alignCenter .AnchorVert = alignBottom .AlignVert = alignBottom .Y = -3 .Height = 16 .Visible = NO END WITH ChangeRectangleSlice buyst.alert_box, 2 buyst.alert_sl = NewSliceOfType(slText, buyst.alert_box) WITH *(buyst.alert_sl) .AnchorVert = alignCenter .AlignVert = alignCenter .AnchorHoriz = alignCenter .AlignHoriz = alignCenter END WITH ChangeTextSlice buyst.alert_sl, "", uilook(uiText), YES END SUB SUB buystuff_set_alert(buyst as ShopBuyState, alert as string, byval ticks as integer = 18) ChangeTextSlice buyst.alert_sl, alert WITH *(buyst.alert_box) .Visible = YES .Width = buyst.alert_sl->Width + 16 END WITH buyst.alert_ticks = ticks END SUB FUNCTION chkOOBtarg (byval attacker as integer, byval target as integer, byval atk as integer) as bool 'Out-of-battle attack target class evaluation: Whether 'target' is a valid target. 'attacker is -1 when no specific attacker, e.g. using an item or not passed to "map cure". 'atk id can be -1 for when no attack is relevant (when we are learning an attack, not using it) IF target < 0 OR target > UBOUND(gam.hero) THEN RETURN NO IF gam.hero(target).id = -1 THEN RETURN NO IF atk < -1 OR atk > gen(genMaxAttack) THEN RETURN NO DIM hp as integer hp = gam.hero(target).stat.cur.hp IF atk >= 0 THEN DIM attack as AttackData loadattackdata attack, atk SELECT CASE attack.targ_class CASE 2 'Self IF attacker > -1 ANDALSO attacker <> target THEN RETURN NO CASE 5 'Ally-not-self IF attacker > -1 ANDALSO attacker = target THEN RETURN NO CASE 10, 15 'Dead Ally, Dead Foe RETURN (hp <= 0) CASE 16 'Foe-including-dead RETURN YES 'attack_can_hit_dead() would return NO because heroes can't hit dead enemies END SELECT 'Other target classes are treated equivalent to "Ally (not dead)", or 'to Ally-including-dead if they can hit dead. IF hp <= 0 THEN RETURN attack_can_hit_dead(attack) ELSE IF hp <= 0 THEN RETURN NO END IF RETURN YES END FUNCTION 'After a change in equipment or stat caps, update the current and max values 'according to slightly odd rule. SUB update_hero_max_and_cur_stats (byval who as integer) recompute_hero_max_stats who '--update current stats WITH gam.hero(who).stat FOR statnum as integer = 0 TO statLast 'max stats are already capped, so current stats will be too IF statnum <> statHP AND statnum <> statMP THEN .cur.sta(statnum) = .max.sta(statnum) ELSE .cur.sta(statnum) = small(.cur.sta(statnum), .max.sta(statnum)) END IF NEXT statnum END WITH END SUB 'Equip an item. Automatically unequips anything previously equipped. 'toequip: item ID (-1 is invalid) 'who: hero slot 'where: equip slot 'Return value YES for success, NO for failure FUNCTION doequip (toequip as integer, who as integer, where as integer) as bool 'First remove the item from inventory (unless it is the default weapon) IF toequip = gam.hero(who).def_wep - 1 AND where = 0 THEN ELSE 'delete the item from inventory delitem toequip END IF 'Now remove the old equipment (Removes the default weapon too) IF gam.hero(who).equip(where).id >= 0 THEN IF unequip(who, where, NO, NO) = NO THEN 'Unequipping failed. This is because there was no room to return the item back to inventory 'Instead, return the original item to inventory (which we can do because we just removed it, 'and its stack is guaranteed to have room even if the rest of the inventory is full) ' '(Also there is no danger of unequip recursively calling doequip again because that ' only happens if the resetdefwep arg is YES and it is NO here) getitem toequip debug "Failed attempt to unequip " & readitemname(gam.hero(who).equip(where).id) & " and equip " & readitemname(toequip) & " (No room in inventory)" RETURN NO END IF END IF 'set new equipment gam.hero(who).equip(where).id = toequip 'Update this hero's stat bonuses update_hero_max_and_cur_stats who evalitemtags evalherotags 'You could kill someone, right? (Yes, with a negative HP bonus) tag_updates RETURN YES END FUNCTION LOCAL FUNCTION getOOBtarg (byval search_direction as integer, byval attacker as integer, byref target as integer, byval atk as integer) as bool 'Move the hero selection cursor for the target picker for an OOB attack/learning attack. '--return true on success, false on failure '--atk id can be -1 for when no attack is relevant (when we are learning an attack, not using it) DIM safety as integer = 0 DO loopvar target, 0, 3, search_direction IF chkOOBtarg(attacker, target, atk) THEN RETURN YES safety += 1 IF safety >= 4 THEN EXIT DO LOOP 'Failure target = -1 RETURN NO END FUNCTION 'Returns the string such as "x 9" that appears in inventory slots FUNCTION inventory_slot_number_str(slot as InventSlot) as string IF slot.num = 1 THEN 'Optionally, don't display the x1 suffix IF gen(genInventSlotx1Display) = 1 ORELSE _ 'Never display x1 (gen(genInventSlotx1Display) = 2 ANDALSO get_item_stack_size(slot.id) <= 1) THEN 'Only if stacksize>1 RETURN " " END IF END IF RETURN CHR(1) & lpad(STR(slot.num), , 2) 'CHR(1) is 'X' END FUNCTION SUB update_inventory_caption (byval i as integer) 'WARNING: this is no longer used by the Items menu, but is still used by Equip and Battle items menus, 'which need re-writing. (I think the goal is to get rid of InventSlot.text?) WITH inventory(i) IF .used THEN .text = rpad(readitemname(.id), " ", 8) & inventory_slot_number_str(inventory(i)) ELSE .text = SPACE(11) END IF END WITH END SUB SUB oobcure (byval attacker as integer, byval target as integer, byval atk as integer, byval target_count as integer) '--outside-of-battle cure ' attacker and target are hero slots; attacker may be -1 WITH gam.hero(target).stat DIM as BattleSprite attacker_obj, target_obj '--populate attacker object IF attacker = -1 THEN '--average stats for item-triggered spells DIM partysize as integer = 0 FOR o as integer = 0 TO 3 IF gam.hero(o).id >= 0 THEN partysize += 1 FOR i as integer = 0 TO statLast attacker_obj.stat.cur.sta(i) += gam.hero(o).stat.cur.sta(i) attacker_obj.stat.max.sta(i) += gam.hero(o).stat.max.sta(i) NEXT i END IF NEXT o FOR i as integer = 0 TO statLast attacker_obj.stat.cur.sta(i) /= partysize attacker_obj.stat.max.sta(i) /= partysize NEXT i ELSE FOR i as integer = 0 TO statLast attacker_obj.stat.cur.sta(i) = gam.hero(attacker).stat.cur.sta(i) attacker_obj.stat.max.sta(i) = gam.hero(attacker).stat.max.sta(i) NEXT i END IF '--populate the target object FOR i as integer = 0 to statLast target_obj.stat.cur.sta(i) = .cur.sta(i) target_obj.stat.max.sta(i) = .max.sta(i) NEXT i calc_hero_elementals target_obj.elementaldmg(), target DIM attack as AttackData loadattackdata attack, atk '--out of battle attacks that target stats other than HP and MP '--always affect the max stat, so force exceed_maximum on IF attack.targ_stat > 1 THEN attack.allow_cure_to_exceed_maximum = YES END IF '--out of battle attacks aren't allowed to miss. attack.aim_math = 3 'Currently (bug 980) stat caps aren't enforced in-battle, which makes this easier. 'Once it's fixed, I think we'll want an argument (or attack bitset!) to disable capping. 'Also, we assume that inflict only modifies current values of stats. inflict( , , 0, 1, attacker_obj, target_obj, attack, target_count) '--copy back stats that need copying back DIM last_plain_stat as integer IF prefbit(31) = NO THEN '"Don't reset max stats after OOB attack" '--Then update just the max for the other stats '--this kinda sucks but it is consistent with the way outside of battle cure has always worked. '--It's needed so that items which permanently change stats out of battle can work, but it's '--also a bad thing if a script expects the max values to not change. '--Note that an item can't permanently change HP or MP! '--Note also that this has bad results if cur != max to begin with: the cur becomes '--the new max even if the attack does nothing! (We should probably change that) last_plain_stat = statMP FOR statnum as integer = statMP + 1 to statLast 'Indirectly update base DIM diff as integer = target_obj.stat.cur.sta(statnum) - .max.sta(statnum) .base.sta(statnum) += diff 'Previously we effectively did .max.sta(statnum) += diff NEXT statnum recompute_hero_max_stats target FOR statnum as integer = statMP + 1 to statLast .cur.sta(statnum) = .max.sta(statnum) NEXT ELSE last_plain_stat = statLast END IF '--Stats which are copied directly: usually just HP and MP FOR statnum as integer = 0 to last_plain_stat .cur.sta(statnum) = target_obj.stat.cur.sta(statnum) DIM cap as integer = gen(genStatCap + statnum) IF cap > 0 THEN .cur.sta(statnum) = small(.cur.sta(statnum), cap) END IF NEXT statnum MenuSound attack.sound_effect '--TODO: Must add the attack-tag conditional stuff. END WITH END SUB SUB patcharray (array() as integer, n as string) DIM num(2) as string DIM hexk(15) as integer hexk(0) = 11 FOR i as integer = 1 TO 9 hexk(i) = i + 1 NEXT i hexk(10) = 30 hexk(11) = 48 hexk(12) = 46 hexk(13) = 32 hexk(14) = 18 hexk(15) = 33 DIM pt as integer = 0 DIM tog as integer DIM csr as integer ensure_normal_palette setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 IF keyval(ccCancel) > 1 THEN EXIT DO IF keyval(ccUp) > 1 THEN csr = large(0, csr - 1) IF keyval(ccDown) > 1 THEN csr = small(2, csr + 1) IF csr = 0 THEN intgrabber pt, 0, UBOUND(array) IF csr = 1 THEN intgrabber array(pt), -32768, 32767 IF csr = 2 THEN FOR i as integer = 0 TO 15 IF keyval(hexk(i)) > 1 THEN setbit array(), pt, i, readbit(array(), pt, i) XOR 1 NEXT i END IF num(0) = n & "(" & ABS(pt) & ")" num(1) = "value = " & array(pt) num(2) = "" FOR i as integer = 0 TO 15 IF readbit(array(), pt, i) THEN num(2) = num(2) + "1" ELSE num(2) = num(2) + "0" END IF NEXT i clearpage dpage edgeprint "DEBUG MODE", pCentered, rCenter - 50, uilook(uiText), dpage centerbox , , 140, 60, 1, dpage DIM c as integer FOR i as integer = 0 TO 2 IF i = csr THEN c = uilook(uiSelectedItem + tog) ELSE c = uilook(uiMenuItem) END IF edgeprint num(i), pCentered, rCenter - 20 + i * 10, c, dpage NEXT i edgeprint "0123456789ABCDEF", pCentered, rCenter + 10, uilook(uiSelectedDisabled), dpage SWAP vpage, dpage setvispage vpage dowait LOOP restore_previous_palette END SUB FUNCTION pickload (newgame_opt as bool = YES, beep_if_no_saves as bool = NO) as integer ' Load game menu. The menu will be skipped if there are no saves (optionally plays Cancel sound) ' newgame_opt: whether the "New Game" button is shown. ' ' Return value: ' 0 or higher: save slot ' -1 New Game/no saves available (even if newgame_opt=NO) ' -2 cancelled/Exit RETURN picksaveload(YES, newgame_opt, beep_if_no_saves) END FUNCTION FUNCTION picksave () as integer ' Return value: ' 0 or higher: save slot ' -2: cancelled/Exit RETURN picksaveload(NO) END FUNCTION PRIVATE FUNCTION picksaveload (byval loading as bool, byval newgame_opt as bool = YES, byval beep_if_no_saves as bool = NO) as integer ' Save or load menu. ' loading: false is the save menu, true is the load menu. ' newgame_opt is ignored for the save menu. For the load menu it determines whether ' the "New Game" button is shown. ' beep_if_no_saves is for providing audible feedback if the load menu is skipped if ' there are no saves. ' Return value: ' 0 or higher: save slot ' -1 (loading only) no saves available/New Game ' -2 cancelled/Exit show_virtual_gamepad() DIM ret_slot as integer DIM max_slot as integer = maxSaveSlotCount - 1 DIM last_slot as integer last_slot = gen(genSaveSlotCount) - 1 IF last_slot < 0 THEN last_slot = 3 'Use the default! IF last_slot > max_slot THEN debug "Can't allow last save slot to be " & last_slot & ", " & max_slot & " is the max." last_slot = max_slot END IF IF prefbit(44) THEN '"Hide empty save slots at the bottom of the save/load menus" FOR i as integer = last_slot TO 4 STEP -1 IF save_slot_used(i) THEN 'Found a used save slot EXIT FOR ELSE 'Save slot is empty IF loading THEN last_slot = i - 1 ELSE last_slot = i END IF END IF NEXT i END IF DIM mapname(max_slot) as string DIM lev(max_slot) as string DIM menu(1) as string DIM sprites(max_slot, 3) as GraphicPair DIM st as MenuState 'newgame_opt controls whether there are one (left only) or two buttons (left and right) at the top 'menu() holds the names of the options at the top of the screen (only one appears when saving) 'st.pt = -1 and menu(0) is the left button, st.pt = -2 and menu(1) is the right button 'If "New game" is an option it is left (st.pt = -1) and cancel is the right one (st.pt = -2), 'Otherwise "Cancel" is the only button (st.pt = -1). 'But if exiting the game is disallowed there will instead be only one button on the left '(exit can't be disabled when newgame is disabled). 'Note st.pt is no longer equal to the return value. st.first = -1 'Note that st.pt can actually go down to -2 st.last = last_slot st.size = 3 DIM drag_start_top as integer = -10 DIM allow as bool IF loading THEN st.pt = 0 ELSE newgame_opt = NO st.pt = lastsaveslot - 1 END IF IF newgame_opt THEN menu(0) = readglobalstring(52, "NEW GAME", 10) menu(1) = readglobalstring(53, "EXIT", 10) ELSE menu(0) = readglobalstring(59, "CANCEL", 10) END IF DIM holdscreen as integer DIM page as integer page = compatpage holdscreen = allocatepage '--preserve background for display beneath the save/load picker copypage page, holdscreen DIM pv(last_slot) as SaveSlotPreview FOR i as integer = 0 TO last_slot get_save_slot_preview i, pv(i) IF pv(i).valid THEN mapname(i) = embed_text_codes(getmapname(pv(i).cur_map), i) '--leader level lev(i) = readglobalstring(43, "Level", 10) & " " & pv(i).leader_lev FOR o as integer = 0 TO 3 '--hero pic and palette IF pv(i).hero_id(o) > 0 THEN load_sprite_and_pal sprites(i, o), sprTypeHero, pv(i).hero(o).battle_pic, pv(i).hero(o).battle_pal END IF NEXT o END IF NEXT i DIM exit_load_disabled as bool = NO IF loading THEN 'Check for no saves DIM nosaves as bool = YES FOR i as integer = 0 TO last_slot IF pv(i).valid THEN nosaves = NO NEXT i IF nosaves THEN ret_slot = -1 IF beep_if_no_saves THEN MenuSound gen(genCancelSFX) GOTO freesprites END IF END IF IF loading ANDALSO newgame_opt ANDALSO NOT exit_from_game_is_allowed() THEN exit_load_disabled = YES 'FIXME: using need_fade_in to trigger the sound effect seems like a bad idea IF gam.need_fade_in = NO THEN MenuSound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys IF loading = NO THEN playtimer IF game_check_cancel_key() THEN MenuSound gen(genCancelSFX) IF exit_load_disabled THEN 'No nothing if exiting is not allowed from the initital loadgame screen ELSE 'Cancel and exit out of the load menu ret_slot = -2 EXIT DO END IF END IF 'Make menu position -2 appear as -1 to usemenu DIM temppt as integer = iif(st.pt = -2, -1, st.pt) usemenusounds IF usemenu(temppt, st.top, st.first, st.last, st.size) THEN st.pt = temppt END IF IF get_gen_bool("/mouse/mouse_menus") THEN picksave_update_mouse st, newgame_opt, drag_start_top END IF IF st.pt < 0 AND newgame_opt THEN IF exit_load_disabled THEN 'If exit is disabled, then new game is the only option (exit can't be disabled when newgame is disabled) st.pt = -1 ELSE IF carray(ccLeft) > 1 THEN st.pt = -1: MenuSound gen(genCursorSFX) IF carray(ccRight) > 1 THEN st.pt = -2: MenuSound gen(genCursorSFX) END IF END IF IF game_check_use_key() ORELSE (get_gen_bool("/mouse/mouse_menus") ANDALSO (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 ANDALSO (readmouse.buttons AND mouseRight) = 0 ) THEN IF newgame_opt AND st.pt = -1 THEN 'Selected "New Game" MenuSound gen(genAcceptSFX) ret_slot = -1 EXIT DO ELSEIF st.pt < 0 THEN 'Selected "Cancel" or "Exit" MenuSound gen(genCancelSFX) IF exit_load_disabled THEN 'Do nothing if exiting is not allowed from the initial loadgame screen ELSE 'Cancel and exit out of the load menu ret_slot = -2 EXIT DO END IF ELSE allow = YES IF loading THEN '--don't allow load of an empty save IF pv(st.pt).valid = 0 THEN allow = NO ELSE '--normal save in a slot IF pv(st.pt).valid THEN allow = picksave_confirm(menu(), loading, newgame_opt, exit_load_disabled, sprites(), pv(), mapname(), lev(), st, holdscreen, page) END IF IF allow = YES THEN MenuSound gen(genAcceptSFX) ret_slot = st.pt EXIT DO ELSE MenuSound gen(genCancelSFX) END IF END IF END IF copypage holdscreen, page picksave_draw menu(), loading, newgame_opt, exit_load_disabled, sprites(), pv(), mapname(), lev(), st, page setvispage vpage check_for_queued_fade_in dowait LOOP freesprites: freepage page freepage holdscreen clearkeys FOR i as integer = 0 TO last_slot FOR o as integer = 0 TO 3 unload_sprite_and_pal sprites(i, o) NEXT NEXT RETURN ret_slot END FUNCTION 'Returns YES if overwrite is allowed, NO if canceled FUNCTION picksave_confirm(menu() as string, byval loading as bool, byval newgame_opt as bool, exit_load_disabled as bool=NO, sprites() as GraphicPair, pv() as SaveSlotPreview, mapname() as string, lev() as string, byref st as MenuState, byval holdscreen as integer, byval page as integer) as bool DIM confirmboxY as integer = 14 + (44 * (st.pt - st.top)) DIM tog as integer DIM confirm(1) as string confirm(0) = readglobalstring(44, "Yes", 10) confirm(1) = readglobalstring(45, "No", 10) DIM menuwidth as integer menuwidth = 8 * large(LEN(confirm(0)), LEN(confirm(1))) DIM replacedat as string replacedat = readglobalstring(102, "Replace Old Data?", 20) DIM state as MenuState state.last = 1 state.size = 2 state.pt = 1 ' default to "No" MenuSound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer IF game_check_cancel_key() THEN MenuSound gen(genCancelSFX) RETURN NO END IF IF game_check_use_key() THEN RETURN state.pt = 0 usemenu state usemenusounds IF get_gen_bool("/mouse/mouse_menus") THEN state.hover = -1 DIM overbutton as bool = NO FOR i as integer = 0 to 1 'The button rect is slightly wider than the default "YES/NO" text 'but slightly smaller than the maximum possible length of the captions '(the part of the text that might overflow outside the decorative box is not in the button rect) 'FIXME: this will all be better when it is sliceified ;) DIM r as RectType = (208, confirmboxY - 9 + (i * 9), 40, 9) IF rect_collide_point(r, readmouse.pos) THEN state.hover = i overbutton = YES END IF NEXT i IF state.hover >= 0 ANDALSO (readmouse.buttons AND mouseLeft) THEN state.pt = state.hover END IF IF overbutton ANDALSO (readmouse.release AND mouseLeft) THEN RETURN state.pt = 0 END IF copypage holdscreen, page picksave_draw menu(), loading, newgame_opt, exit_load_disabled, sprites(), pv(), mapname(), lev(), st, page centerbox 160, confirmboxY, 40 + (LEN(replacedat) * 8) + menuwidth, 24, 3, page edgeprint replacedat, 200 - (LEN(replacedat) * 8), confirmboxY - 5, uilook(uiText), page FOR i as integer = 0 TO 1 DIM col as integer = uilook(uiMenuItem) IF state.hover = i THEN col = uilook(uiMouseHoverItem) IF state.pt = i THEN col = uilook(uiSelectedItem + tog) edgeprint confirm(i), 216, confirmboxY - 9 + (i * 9), col, page NEXT i setvispage vpage dowait LOOP END FUNCTION SUB picksave_draw(menu() as string, byval loading as bool, byval newgame_opt as bool, exit_load_disabled as bool=NO, sprites() as GraphicPair, pv() as SaveSlotPreview, mapname() as string, lev() as string, byref st as MenuState, byval page as integer) 'newgame_opt controls whether there are one (left only) or two buttons (left and right) at the top 'st.pt = -1 and menu(0) is the left button, st.pt = -2 and menu(1) is the right button DIM col as integer DIM boxstyle as integer 'load and save menus enjoy different colour schemes IF loading THEN boxstyle = 1 ELSE boxstyle = 0 STATIC tog as integer STATIC wtog as integer tog = tog XOR 1 loopvar wtog, 0, 2 * wtog_ticks() - 1 '' These are hero battle sprites. max_wtog() is for walkabouts DIM last_slot as integer = UBOUND(pv) centerbox 50, 11, 80, 14, 1 + 14, page IF newgame_opt ANDALSO NOT exit_load_disabled THEN centerbox 270, 11, 80, 14, 1 + 14, page DIM top as integer = large(st.top, 0) DIM yoff as integer FOR i as integer = top TO small(top + 3, last_slot) yoff = (i - top) * 44 centerbox 160, 44 + yoff, 310, 42, 1 + 14, page NEXT i SELECT CASE st.pt CASE -2 centerbox 270, 11, 82, 16, 1 + boxstyle, page CASE -1 centerbox 50, 11, 82, 16, 1 + boxstyle, page CASE ELSE centerbox 160, 44 + (st.pt - top) * 44, 312, 44, 1 + boxstyle, page END SELECT FOR i as integer = top TO small(top + 3, last_slot) col = uilook(uiMenuItem) IF st.hover = i THEN col = uilook(uiMouseHoverItem) IF st.pt = i THEN col = uilook(uiSelectedItem + tog) yoff = (i - top) * 44 IF pv(i).valid THEN 'If the sprites are much larger than the default 32x40 then they will overlap, and get clipped setclip 5, yoff + 22, 315, yoff + 63, vpages(page) FOR o as integer = 3 TO 0 STEP -1 IF sprites(i, o).sprite THEN DIM wframe as integer = 0 IF st.pt = i THEN wframe = wtog_to_frame(wtog) frame_draw sprites(i, o).sprite + wframe, sprites(i, o).pal, 140 + (o * 42), 24 + yoff, , page END IF NEXT o setclip , , , , vpages(page) edgeprint pv(i).leader_name, 14, 25 + yoff, col, page edgeprint lev(i), 14, 34 + yoff, col, page edgeprint pv(i).playtime, 14, 43 + yoff, col, page edgeprint mapname(i), 14, 52 + yoff, col, page END IF DIM slot_label as string = "" & (i + 1) edgeprint slot_label, 312 - len(slot_label) * 8, 25 + yoff, col, page NEXT i IF st.hover = -1 THEN col = uilook(uiMouseHoverItem) col = uilook(IIF(st.pt = -1, uiSelectedItem + tog, uiMenuItem)) edgeprint menu(0), ancCenter + 50, 6, col, page IF newgame_opt ANDALSO NOT exit_load_disabled THEN IF st.hover = -2 THEN col = uilook(uiMouseHoverItem) col = uilook(IIF(st.pt = -2, uiSelectedItem + tog, uiMenuItem)) edgeprint menu(1), ancCenter + 270, 6, col, page END IF DIM scroll_st as MenuState WITH scroll_st .pt = large(0, st.pt) .top = large(0, st.top) .last = st.last .size = st.size END WITH draw_fullscreen_scrollbar scroll_st, , page END SUB SUB picksave_update_mouse(st as MenuState, byval newgame_opt as bool, byref drag_start_top as integer) st.hover = -10 DIM top as integer = large(st.top, 0) DIM yoff as integer DIM r as RectType FOR i as integer = top TO small(top + 3, st.last) yoff = (i - top) * 44 r = Type(5, 23 + yoff, 310, 42) IF rect_collide_point(r, readmouse.pos) THEN st.hover = i NEXT i ' Check for load "Exit" button IF newgame_opt THEN r = Type(229, 3, 82, 16) IF rect_collide_point(r, readmouse.pos) THEN st.hover = -2 END IF ' Check for save "Exit" or load "New Game" r = Type(9, 3, 82, 16) IF rect_collide_point(r, readmouse.pos) THEN st.hover = -1 'Check for click to focus IF (readmouse.buttons AND mouseLeft) ANDALSO st.hover > -10 ANDALSO (readmouse.buttons AND mouseRight) = 0 THEN st.pt = st.hover END IF IF (readmouse.clicks AND mouseRight) ANDALSO st.hover > -10 THEN st.pt = st.hover END IF 'Now handle scrolling IF st.pt >= 0 THEN 'Check for left-drag scrolling IF (readmouse.dragging AND mouseLeft) THEN IF readmouse.drag_dist > 10 ANDALSO drag_start_top = -10 THEN drag_start_top = st.top END IF IF drag_start_top > -10 THEN st.top = drag_start_top + (readmouse.clickstart.y - readmouse.pos.y) \ 44 END IF ELSE drag_start_top = -10 END IF 'Check for wheel scrolling st.top = st.top + (4 * readmouse.wheel_delta) \ 120 ' Bound top DIM lasttop as integer = large(st.first, st.last - st.size) st.top = bound(st.top, st.first, lasttop) ' Make sure .pt is visible st.pt = bound(st.pt, st.top, st.top + st.size) END IF END SUB SUB sellstuff (byval id as integer, storebuf() as integer) DIM b((getbinsize(binSTF) \ 2) * 50 - 1) as integer DIM permask((inventoryMax + 1) \ 16 ) as integer DIM price(inventoryMax + 1) as integer DIM recordsize as integer = curbinsize(binSTF) \ 2 ' get size in INTs '--preserve background for display under sell menu DIM page as integer DIM holdscreen as integer page = compatpage holdscreen = allocatepage copypage page, holdscreen DIM sold as string = readglobalstring(84, "Sold", 10) loadshopstuf b(), id ' Initialise state of the shop stock (if we haven't visited the shop before, or if ' new items have been added to it since last time, or if the stock needs resetting FOR slot as integer = 0 TO storebuf(16) DIM stockidx as integer = b(slot * recordsize + 37) - 1 DIM thingtype as integer = b(slot * recordsize + 17) DIM thingid as integer = b(slot * recordsize + 18) DIM initial_stock as integer = b(slot * recordsize + 19) initialize_stock id, stockidx, thingtype, thingid, initial_stock NEXT slot DIM ic as integer = 0 DIM top as integer = 0 DIM tog as integer DIM alert as integer = 0 DIM alert_str as string = "" DIM info as string = "" DIM drag_top as integer = -1 CONST rowheight as integer = 8 'Pixel height of each inventory row DIM pagesize as integer 'Size of the menu: number of inventory slots visible at once pagesize = small(63, last_inv_slot() + 1) CONST rowsize as integer = 3 'Number of inventory slots on each row DIM box as RectType = (8, 5, 304, 10 + (pagesize \ rowsize) * rowheight) menusound gen(genAcceptSFX) sellstuff_refresh storebuf(16), recordsize, permask(), price(), b() DIM scroll_state as MenuState 'Only used for the scrollbar WITH scroll_state .first = 0 .last = last_inv_slot() \ rowsize .size = pagesize \ rowsize - 1 END WITH sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize show_virtual_gamepad() setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer DIM do_quit as bool = NO DIM do_pick as bool = NO DIM old_ic as integer = ic 'Keyboard controls IF carray(ccUp) > 1 ANDALSO ic >= rowsize THEN ic -= rowsize IF ic < top THEN top -= rowsize END IF IF carray(ccDown) > 1 ANDALSO ic <= last_inv_slot() - rowsize THEN ic += rowsize IF ic >= top + pagesize THEN top += rowsize END IF IF carray(ccLeft) > 1 THEN IF ic MOD rowsize = 0 THEN ic += rowsize ic -= 1 END IF IF carray(ccRight) > 1 THEN ic += 1 IF ic MOD rowsize = 0 THEN ic -= rowsize END IF IF keyval(scPageUp) > 1 THEN ic -= pagesize - rowsize top -= pagesize - rowsize END IF IF keyval(scPageDown) > 1 THEN ic += pagesize - rowsize top += pagesize - rowsize END IF IF keyval(scHome) > 1 THEN ic = 0 top = 0 END IF IF keyval(scEnd) > 1 THEN ic = last_inv_slot() top = ic - (pagesize - 1) END IF IF game_check_cancel_key() THEN do_quit = YES IF game_check_use_key() THEN do_pick = YES IF get_gen_bool("/mouse/mouse_menus") THEN IF rect_collide_point(box, readmouse.pos) THEN 'Inside the inventory box DIM hover as integer = -1 FOR i as integer = top TO top + pagesize - 1 IF rect_collide_point(XYWH(20 + 96 * (i MOD rowsize), 12 + rowheight * ((i - top) \ rowsize), 96, rowheight), readmouse.pos) THEN hover = i NEXT i IF hover >= 0 THEN IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 THEN IF ic = hover THEN do_pick = YES ELSE ic = hover END IF END IF END IF IF (readmouse.dragging AND mouseLeft) THEN IF drag_top = -1 THEN drag_top = top IF readmouse.drag_dist > 10 THEN DIM dist as integer = (readmouse.clickstart.y - readmouse.pos.y) / rowheight top = drag_top + dist * rowsize END IF END IF top += readmouse.wheel_clicks * rowsize * 3 ELSE 'Outside the box 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 IF (readmouse.dragging AND mouseLeft) = 0 THEN drag_top = -1 END IF 'Clamp the top of the scrollable area into the safe range top = bound(top, 0, large(0, last_inv_slot() - (pagesize - 1))) 'Make sure the cursor stays onscreen DO WHILE ic < top : ic += rowsize: LOOP DO WHILE ic > top + pagesize - 1 : ic -= rowsize: LOOP 'Update scrollbar scroll_state.top = top \ rowsize IF old_ic <> ic THEN menusound gen(genCursorSFX) sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize END IF IF do_pick ANDALSO inventory(ic).used THEN IF readbit(permask(), 0, ic) = 0 THEN menusound gen(genSellSFX) alert = 10 alert_str = sold & " " & readitemname(inventory(ic).id) 'INCREMENT GOLD----------- gold = gold + price(ic) IF gold > 2000000000 THEN gold = 2000000000 IF gold < 0 THEN gold = 0 'CHECK FOR SPECIAL CASES--------- FOR i as integer = 0 TO storebuf(16) IF b(i * recordsize + 17) = 0 AND b(i * recordsize + 18) = inventory(ic).id THEN 'SET SELL BIT--- settag b(i * recordsize + 23) 'ADD TRADED ITEM----------- IF b(i * recordsize + 28) > 0 THEN getitem b(i * recordsize + 28) - 1, b(i * recordsize + 29) + 1 'INCREMENT STOCK------- IF b(i * recordsize + 26) > 0 THEN DIM stockidx as integer = b(i * recordsize + 37) - 1 IF b(i * recordsize + 26) = 1 THEN gam.stock(id, stockidx) = -1 'Acquire infinite stock IF b(i * recordsize + 26) = 2 AND gam.stock(id, stockidx) > 0 THEN gam.stock(id, stockidx) += 1 'Increment stock END IF END IF NEXT i 'DECREMENT ITEM----------- consumeitem ic 'UPDATE ITEM POSESSION TAGS-------- evalitemtags tag_updates 'REFRESH DISPLAY-------- sellstuff_refresh storebuf(16), recordsize, permask(), price(), b() sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize ELSE menusound gen(genCantSellSFX) END IF END IF IF do_quit THEN EXIT DO '--Draw the sell screen copypage holdscreen, page edgeboxstyle box, 0, page draw_scrollbar scroll_state, box, , page FOR i as integer = top TO top + pagesize - 1 textcolor uilook(uiMenuItem), 0 IF readbit(permask(), 0, i) THEN textcolor uilook(uiDisabledItem), 0 IF ic = i THEN textcolor uilook(uiSelectedItem + tog), uilook(uiHighlight2) IF readbit(permask(), 0, i) THEN textcolor uilook(uiGold), uilook(uiHighlight2) END IF DIM item_name as string IF i > last_inv_slot() THEN item_name = "OUTOFRANGE!" ELSE item_name = inventory(i).text END IF printstr item_name, 20 + 96 * (i MOD rowsize), 12 + rowheight * ((i - top) \ rowsize), page NEXT i centerbox 160, 192, 312, 14, 4, page edgeprint info, pCenteredLeft, 187, uilook(uiText), page edgeprint price_string(gold), pRight - 10, 1, uilook(uiGold), page IF alert THEN alert -= 1 centerbox 160, 178, textwidth(alert_str) + 8, 14, 4, page edgeprint alert_str, pCentered, 173, uilook(uiSelectedItem + tog), page END IF setvispage vpage dowait LOOP freepage page freepage holdscreen menusound gen(genCancelSFX) evalitemtags tag_updates END SUB SUB sellstuff_infostr(byval ic as integer, info as string, b() as integer, permask() as integer, price() as integer, byval stuff_count as integer, byval recordsize as integer) DIM cannotsell as string = readglobalstring(75, "CANNOT SELL", 20) DIM worth as string = readglobalstring(77, "Worth", 20) DIM tradefor as string = readglobalstring(79, "Trade for", 20) DIM worthnothing as string = readglobalstring(82, "Worth Nothing", 20) info = "" IF inventory(ic).used = 0 THEN RETURN IF readbit(permask(), 0, ic) = 1 THEN info = cannotsell: RETURN IF price(ic) > 0 THEN info = worth & " " & price_string(price(ic)) FOR i as integer = 0 TO stuff_count IF b(i * recordsize + 17) = 0 AND b(i * recordsize + 18) = inventory(ic).id THEN IF b(i * recordsize + 28) > 0 THEN IF info = "" THEN info = tradefor & " " ELSE IF b(i * recordsize + 29) > 0 THEN info &= " " & readglobalstring(153, "and", 10) & " " ELSE info &= " " & readglobalstring(81, "and a", 10) & " " END IF END IF IF b(i * recordsize + 29) > 0 THEN info &= (b(i * recordsize + 29) + 1) & " " info &= readitemname(b(i * recordsize + 28) - 1) END IF END IF NEXT i IF info = "" THEN info = worthnothing END SUB SUB sellstuff_refresh(byval last_stuff as integer, byval recordsize as integer, permask() as integer, price() as integer, stuffdata() as integer) DIM itemdata(dimbinsize(binITM)) as integer DIM thing_type as integer DIM num as integer DIM sell_req_tag as integer DIM sell_price as integer DIM sell_mode as integer FOR i as integer = 0 TO last_inv_slot() IF inventory(i).used THEN loaditemdata itemdata(), inventory(i).id IF itemdata(73) = 2 THEN setbit permask(), 0, i, 1 price(i) = INT(itemdata(46) * .5) FOR o as integer = 0 TO last_stuff thing_type = stuffdata(o * recordsize + 17) num = stuffdata(o * recordsize + 18) sell_req_tag = stuffdata(o * recordsize + 21) sell_mode = stuffdata(o * recordsize + 26) sell_price = stuffdata(o * recordsize + 27) IF num = inventory(i).id THEN IF ABS(sell_req_tag) > 0 THEN IF NOT istag(sell_req_tag, 0) THEN setbit permask(), 0, i, 1 END IF IF thing_type = 0 THEN price(i) = sell_price IF sell_mode = 3 THEN setbit permask(), 0, i, 1 END IF END IF NEXT o END IF NEXT i END SUB 'Format one of the strings on the second Status menu screen FUNCTION hero_elemental_resist_msg (element as string, damage as single) as string DIM raw as string IF ABS(damage) < 0.000005 THEN raw = readglobalstring(168, "Immune to $E", 30) ELSEIF damage < 0.0 THEN raw = readglobalstring(171, "Absorb $E", 30) ELSEIF damage < 1.0 THEN raw = readglobalstring(165, "Strong to $E", 30) ELSEIF damage > 1.0 THEN raw = readglobalstring(162, "Weak to $E", 30) END IF 'No message for 100% damage replacestr raw, "$E", element replacestr raw, "$D", format_percent(damage, 3) replacestr raw, "$X", format_percent(damage - 1.0, 3) replacestr raw, "$R", format_percent(1.0 - damage, 3) replacestr raw, "$A", format_percent(-damage, 3) RETURN raw END FUNCTION FUNCTION get_elemental_info_text(byval hero_slot as integer) as string '--get the list of elemental names DIM elementnames() as string getelementnames elementnames() '--get this hero's elemental resists, with worn equipment DIM elementaldmg(maxElements - 1) as single calc_hero_elementals elementaldmg(), hero_slot '--build elemental strings DIM eleminfo as string DIM msg as string DIM found_elem as bool = NO msg = readglobalstring(302, "Elemental Effects:", 30) IF LEN(msg) THEN eleminfo &= msg & CHR(10) FOR i as integer = 0 TO gen(genNumElements) - 1 msg = hero_elemental_resist_msg(elementnames(i), elementaldmg(i)) IF LEN(msg) THEN eleminfo &= msg & CHR(10) found_elem = YES END IF NEXT i IF NOT found_elem THEN eleminfo = readglobalstring(130, "No Elemental Effects", 30) END IF RETURN eleminfo END FUNCTION FUNCTION hero_has_mp (byval hero_slot as integer) as bool RETURN gam.hero(hero_slot).stat.max.mp > 0 END FUNCTION FUNCTION hero_uses_lmp (byval hero_slot as integer) as bool DIM hero_id as integer = gam.hero(hero_slot).id IF hero_id < 0 THEN debug "hero_uses_lmp: empty hero slot " & hero_slot RETURN NO END IF DIM her as HeroDef loadherodata her, hero_id DIM hero_node as NodePtr = her.reld READNODE hero_node."battle_menus" as batmenu WITHNODE batmenu."menu" as m DIM k as NodePtr k = m."kind".ptr IF k."spells".exists THEN DIM listnum as integer = k."spells".integer IF LEN(her.list_name(listnum)) > 0 THEN IF her.list_type(listnum) = 1 THEN 'This hero has a LMP spell list, and it is in their battle menu 'And it has a non-blank name RETURN YES END IF END IF END IF END WITHNODE END READNODE RETURN NO END FUNCTION FUNCTION StatCodeName (byval statnum as integer) as string 'Don't confuse this with statnames() 'These are internal codes that never change, and statnames() 'is user-defined names for display SELECT CASE statnum CASE statHP: RETURN "HP" CASE statMP: RETURN "MP" CASE statAtk: RETURN "ATK" CASE statAim: RETURN "AIM" CASE statDef: RETURN "DEF" CASE statDodge: RETURN "DODGE" CASE statMagic: RETURN "MAGIC" CASE statWill: RETURN "WILL" CASE statSpeed: RETURN "SPEED" CASE statCtr: RETURN "CTR" CASE statFocus: RETURN "FOCUS" CASE statHitX: RETURN "HITX" END SELECT debug "StatCodeName: invalid stat number " & statnum RETURN "???" END FUNCTION FUNCTION StatByCodeName (statcode as string) as integer SELECT CASE statcode CASE "HP": RETURN statHP CASE "MP": RETURN statMP CASE "ATK": RETURN statAtk CASE "AIM": RETURN statAim CASE "DEF": RETURN statDef CASE "DODGE": RETURN statDodge CASE "MAGIC": RETURN statMagic CASE "WILL": RETURN statWill CASE "SPEED": RETURN statSpeed CASE "CTR": RETURN statCtr CASE "FOCUS": RETURN statFocus CASE "HITX": RETURN statHitX END SELECT debug "StatByCodeName: invalid code " & statcode RETURN -1 END FUNCTION SUB ExpandTextHeroSlot (code as string, result as string, byval arg0 as ANY ptr=0, byval arg1 as ANY ptr=0, byval arg2 as ANY ptr=0) DIM hero_slot as integer = *CAST(integer ptr, arg0) DIM hero_id as integer = gam.hero(hero_slot).id IF hero_id < 0 THEN 'Doesn't work on empty slots EXIT SUB END IF WITH gam.hero(hero_slot) 'Simple stuff SELECT CASE UCASE(code) CASE "HERONAME": result = gam.hero(hero_slot).name CASE "LEVLABEL": result = readglobalstring(43, "Level", 10) CASE "LEV": result = STR(.lev) CASE "EXPLABEL": result = readglobalstring(33, "Experience", 10) CASE "EXPCUR": result = STR(.exp_cur) CASE "EXPNEXT": result = STR(.exp_next) CASE "EXPNEED": result = STR(.exp_next - .exp_cur) CASE "FORNEXT": result = readglobalstring(47, "for next", 10) CASE "LEVMPLABEL": result = readglobalstring(160, "Level MP", 20) CASE "ELEMENTS": result = get_elemental_info_text(hero_slot) CASE "MONEY": result = price_string(gold) END SELECT DIM her as HeroDef loadherodata her, hero_id DIM codename as string FOR statnum as integer = 0 TO statLast IF NOT should_hide_hero_stat(her, statnum) THEN codename = StatCodeName(statnum) SELECT CASE UCASE(code) CASE codename & "LABEL": result = statnames(statnum) CASE codename & "CUR": result = STR(.stat.cur.sta(statnum)) CASE codename & "MAX": result = STR(.stat.max.sta(statnum)) CASE codename & "BASE": result = STR(.stat.base.sta(statnum)) END SELECT END IF NEXT statnum IF LEFT(code, 3) = "LMP" THEN DIM lmplev as integer = str2int(MID(code, 4)) - 1 IF lmplev >= 0 ANDALSO lmplev <= 8 THEN result = STR(gam.hero(hero_slot).levelmp(lmplev)) END IF END IF END WITH END SUB SUB ExpandTextHeroSlotStat (code as string, result as string, byval arg0 as ANY ptr=0, byval arg1 as ANY ptr=0, byval arg2 as ANY ptr=0) DIM hero_slot as integer = *CAST(integer ptr, arg0) DIM statnum as integer = *CAST(integer ptr, arg1) DIM hero_id as integer = gam.hero(hero_slot).id IF hero_id < 0 THEN 'Doesn't work on empty slots EXIT SUB END IF WITH gam.hero(hero_slot) SELECT CASE UCASE(code) CASE "LABEL": result = statnames(statnum) CASE "CUR": result = STR(.stat.cur.sta(statnum)) CASE "MAX": result = STR(.stat.max.sta(statnum)) CASE "BASE": result = STR(.stat.base.sta(statnum)) END SELECT END WITH END SUB SUB ExpandTextItemScreen (code as string, result as string, byval arg0 as ANY ptr=0, byval arg1 as ANY ptr=0, byval arg2 as ANY ptr=0) DIM cur as Slice Ptr = arg0 DIM swapcur as Slice Ptr = arg1 DIM slot as integer = -1 IF cur THEN slot = cur->Extra(0) SELECT CASE UCASE(code) CASE "EXIT": result = readglobalstring(35, "DONE", 10) CASE "SORT": result = readglobalstring(36, "AUTOSORT", 10) CASE "TRASH": result = readglobalstring(37, "TRASH", 10) CASE "ITEM": result = "" IF slot >= 0 ANDALSO inventory(slot).used THEN result = readitemname(inventory(slot).id) END IF CASE "DESC": result = "" IF slot >= 0 ANDALSO inventory(slot).used THEN result = readitemdescription(inventory(slot).id) ELSEIF cur ANDALSO cur->Lookup = SL_ITEM_TRASHBUTTON THEN IF swapcur THEN DIM swapslot as integer = swapcur->Extra(0) IF swapslot >= 0 ANDALSO inventory(swapslot).used THEN DIM id as integer = inventory(swapslot).id 'info is the same as inventory(swapslot).text but with less whitespace DIM info as string = RTRIM(readitemname(id) & inventory_slot_number_str(inventory(swapslot))) IF item_can_be_discarded(id) THEN result = readglobalstring(41, "Discard", 10) & " " & info ELSE result = readglobalstring(42, "Cannot", 10) & " " & readglobalstring(41, "Discard", 10) & " " & info & "!" END IF END IF END IF END IF END SELECT END SUB SUB ExpandTextItemSlot (code as string, result as string, byval arg0 as ANY ptr=0, byval arg1 as ANY ptr=0, byval arg2 as ANY ptr=0) DIM slot as integer = *CAST(integer ptr, arg0) '.used .id .num .text IF inventory(slot).used THEN SELECT CASE UCASE(code) CASE "ITEM": result = readitemname(inventory(slot).id) CASE "NUM": result = inventory_slot_number_str(inventory(slot)) END SELECT ELSE 'An empty slot SELECT CASE UCASE(code) CASE "ITEM": result = "" CASE "NUM": result = "" END SELECT END IF END SUB SUB ExpandTextVirtualKeyboard (code as string, result as string, byval arg0 as ANY ptr=0, byval arg1 as ANY ptr=0, byval arg2 as ANY ptr=0) DIM st as VirtualKeyboardScreenState Ptr = CAST(VirtualKeyboardScreenState ptr, arg0) SELECT CASE UCASE(code) CASE "PROMPT": result = st->prompt END SELECT END SUB '----------------------------------------------------------------------- TYPE StatusScreenState sl as slice ptr slot as integer need_update as bool END TYPE SUB status_screen_refresh(byref st as StatusScreenState) plank_menu_clear st.sl, SL_STATUS_STATLIST expand_slice_text_insert_codes st.sl, @ExpandTextHeroSlot, @(st.slot) DIM hero_id as integer = gam.hero(st.slot).id IF hero_id >= 0 THEN DIM her as HeroDef loadherodata her, hero_id hide_slices_by_lookup_code st.sl, SL_STATUS_HIDE_IF_NO_HP, should_hide_hero_stat(her, statHP) hide_slices_by_lookup_code st.sl, SL_STATUS_HIDE_IF_NO_MP, (NOT hero_has_mp(st.slot) ORELSE should_hide_hero_stat(her, statMP)) hide_slices_by_lookup_code st.sl, SL_STATUS_HIDE_IF_NO_LMP, NOT hero_uses_lmp(st.slot) hide_slices_by_lookup_code st.sl, SL_STATUS_HIDE_IF_MAX_LEV, gam.hero(st.slot).lev >= current_max_level hide_slices_by_lookup_code st.sl, SL_STATUS_HIDE_IF_NO_PORTRAIT, gam.hero(st.slot).portrait_pic < 0 set_sprites_by_lookup_code st.sl, SL_STATUS_PORTRAIT, sprTypePortrait, gam.hero(st.slot).portrait_pic, gam.hero(st.slot).portrait_pal set_sprites_by_lookup_code st.sl, SL_STATUS_WALKABOUT, sprTypeWalkabout, gam.hero(st.slot).pic, gam.hero(st.slot).pal set_sprites_by_lookup_code st.sl, SL_STATUS_BATTLESPRITE, sprTypeHero, gam.hero(st.slot).battle_pic, gam.hero(st.slot).battle_pal FOR statnum as integer = 2 TO statLast IF NOT should_hide_hero_stat(her, statnum) THEN plank_menu_append st.sl, SL_STATUS_STATLIST, , @ExpandTextHeroSlotStat, @(st.slot), @statnum END IF NEXT statnum END IF END SUB SUB status_screen (byval slot as integer) DIM st as StatusScreenState st.slot = slot DIM holdscreen as integer = duplicatepage(vpage) st.sl = LoadSliceCollection(SL_COLLECT_STATUSSCREEN) ERROR_IF(st.sl = NULL, "Couldn't load slices!") status_screen_refresh st show_virtual_gamepad() menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys playtimer IF game_check_cancel_key() THEN EXIT DO DIM do_next as bool = NO IF game_check_use_key() THEN do_next = YES IF get_gen_bool("/mouse/mouse_menus") THEN IF (readmouse.release AND mouseLeft) THEN do_next = YES ELSEIF (readmouse.release AND mouseRight) THEN EXIT DO END IF END IF IF do_next THEN DIM try_rename as bool = NO DIM sel as Slice Ptr = LookupSlice(SL_STATUS_PAGE_SELECT, st.sl) IF sel THEN SelectSliceNext sel menusound gen(genCursorSFX) DIM seldat as SelectSliceData ptr seldat = sel->SliceData if seldat->index = 0 then try_rename = YES ELSE try_rename = YES END IF IF try_rename ANDALSO gam.hero(st.slot).rename_on_status THEN '--status-screen rename is allowed renamehero st.slot, YES st.need_update = YES IF game_check_cancel_key() THEN EXIT DO END IF END IF IF is_active_party_slot(slot) THEN IF carray(ccLeft) > 1 THEN st.slot = loop_active_party_slot(st.slot, -1) st.need_update = YES menusound gen(genCursorSFX) END IF IF carray(ccRight) > 1 THEN st.slot = loop_active_party_slot(st.slot, 1) st.need_update = YES menusound gen(genCursorSFX) END IF END IF IF keyval(scF8) > 1 THEN slice_editor st.sl st.need_update = YES END IF IF UpdateScreenSlice() THEN st.need_update = YES IF st.need_update THEN status_screen_refresh st st.need_update = NO END IF 'Draw the screen copypage holdscreen, vpage DrawSlice st.sl, vpage setvispage vpage dowait LOOP menusound gen(genCancelSFX) freepage holdscreen DeleteSlice @(st.sl) setkeys END SUB '----------------------------------------------------------------------- TYPE ItemScreenState ps as PlankState need_update as bool textbox as integer swapcur as Slice Ptr lastcur as Slice Ptr quit as bool refocus as bool re_use as bool END TYPE SUB set_item_plank_state (byval sl as Slice Ptr, byval state as PlankItemState) SELECT CASE sl->SliceType CASE slText: SELECT CASE state CASE plankNORMAL: ChangeTextSlice sl, , uiItemScreenItem * -1 - 1 CASE plankSEL: ChangeTextSlice sl, , uiItemScreenSelected2 * -1 - 1 CASE plankDISABLE: ChangeTextSlice sl, , uiItemScreenDisabled * -1 - 1 CASE plankSELDISABLE: ChangeTextSlice sl, , uiItemScreenSelectedDisabled2 * -1 - 1 CASE plankSPECIAL: ChangeTextSlice sl, , uiItemScreenSpecial * -1 - 1 CASE plankSELSPECIAL: ChangeTextSlice sl, , uiItemScreenSelectedSpecial2 * -1 - 1 CASE plankITEMSWAP: ChangeTextSlice sl, , uiItemScreenSwap * -1 - 1 CASE plankITEMSWAPDISABLE: ChangeTextSlice sl, , uiItemScreenSwapDisabled * -1 - 1 CASE plankITEMSWAPSPECIAL: ChangeTextSlice sl, , uiItemScreenSwapSpecial * -1 - 1 END SELECT CASE slRectangle: sl->Visible = YES SELECT CASE state CASE plankNORMAL: sl->Visible = NO CASE plankSEL: ChangeRectangleSlice sl, , uiItemScreenHighlight2 * -1 - 1 CASE plankDISABLE: sl->Visible = NO CASE plankSELDISABLE: ChangeRectangleSlice sl, , uiItemScreenHighlight2 * -1 - 1 CASE plankSPECIAL: sl->Visible = NO CASE plankSELSPECIAL: ChangeRectangleSlice sl, , uiItemScreenHighlight2 * -1 - 1 CASE plankITEMSWAP: ChangeRectangleSlice sl, , uiItemScreenSwapHighlight2 * -1 - 1 CASE plankITEMSWAPDISABLE: ChangeRectangleSlice sl, , uiItemScreenSwapHighlight2 * -1 - 1 CASE plankITEMSWAPSPECIAL: ChangeRectangleSlice sl, , uiItemScreenSwapHighlight2 * -1 - 1 END SELECT END SELECT END SUB SUB update_item_plank (byval pl as Slice Ptr, byref st as ItemScreenState) IF pl = 0 THEN debug "update_item_plank: null ptr" : EXIT SUB DIM slot as integer = pl->Extra(0) DIM sel as bool = (st.ps.cur = pl) DIM special as bool = NO DIM usable as bool = NO DIM swapsel as bool = (st.swapcur = pl) IF slot < 0 THEN usable = YES ELSEIF slot <= UBOUND(inventory) THEN IF inventory(slot).used THEN DIM itemtemp(dimbinsize(binITM)) as integer loaditemdata itemtemp(), inventory(slot).id IF itemtemp(73) = 2 THEN special = YES 'Cannot be sold/dropped 'TODO: call item_can_be_used_bits instead IF itemtemp(50) > 0 THEN usable = YES 'Teaches a spell IF itemtemp(51) > 0 THEN usable = YES 'Triggers an attack (cure) IF itemtemp(51) < 0 THEN usable = YES 'Triggers a text box END IF ELSE debug "update_item_plank: out of bounds slot " & slot END IF DIM state as PlankItemState = plankNORMAL IF sel = NO ANDALSO usable = YES ANDALSO special = NO THEN state = plankNORMAL IF sel = YES ANDALSO usable = YES ANDALSO special = NO THEN state = plankSEL IF sel = NO ANDALSO usable = NO ANDALSO special = NO THEN state = plankDISABLE IF sel = YES ANDALSO usable = NO ANDALSO special = NO THEN state = plankSELDISABLE IF sel = NO ANDALSO special = YES THEN state = plankSPECIAL IF sel = YES ANDALSO special = YES THEN state = plankSELSPECIAL IF swapsel = YES THEN state = plankITEMSWAP IF usable = NO THEN state = plankITEMSWAPDISABLE IF special = YES THEN state = plankITEMSWAPSPECIAL END IF set_plank_state st.ps, pl, state END SUB SUB item_screen_refresh(byref st as ItemScreenState) IF st.refocus THEN save_plank_selection st.ps st.ps.cur = 0 st.ps.hover = 0 plank_menu_clear st.ps.m, SL_ITEM_ITEMLIST DIM pl as Slice Ptr FOR i as integer = 0 TO last_inv_slot() pl = plank_menu_append(st.ps.m, SL_ITEM_ITEMLIST, , @ExpandTextItemSlot, @i) IF pl THEN pl->Extra(0) = i update_item_plank pl, st ELSE debug "Failed to load item plank for slot " & i END IF NEXT i pl = LookupSlice(SL_ITEM_EXITBUTTON, st.ps.m) IF pl THEN pl->Extra(0) = -1 : update_item_plank pl, st pl = LookupSlice(SL_ITEM_SORTBUTTON, st.ps.m) IF pl THEN pl->Extra(0) = -1 : update_item_plank pl, st pl = LookupSlice(SL_ITEM_TRASHBUTTON, st.ps.m) IF pl THEN pl->Extra(0) = -1 :update_item_plank pl, st 'Early draw to force update of slice positions DrawSlice st.ps.m, vpage IF st.refocus THEN restore_plank_selection st.ps st.refocus = NO ELSE st.ps.cur = top_left_plank(st.ps) END IF update_item_plank st.ps.cur, st st.swapcur = 0 expand_slice_text_insert_codes st.ps.m, @ExpandTextItemScreen, st.ps.cur, st.swapcur update_plank_scrolling st.ps END SUB FUNCTION is_item_plank(byval sl as Slice Ptr) as bool IF sl = 0 THEN debug "is_item_plank: null slice ptr" : RETURN NO IF sl->Lookup = SL_PLANK_HOLDER THEN RETURN YES IF sl->Lookup = SL_ITEM_EXITBUTTON THEN RETURN YES IF sl->Lookup = SL_ITEM_SORTBUTTON THEN RETURN YES IF sl->Lookup = SL_ITEM_TRASHBUTTON THEN RETURN YES RETURN NO END FUNCTION FUNCTION item_can_be_discarded (byval item_id as integer) as bool DIM itemtemp(dimbinsize(binITM)) as integer loaditemdata itemtemp(), item_id IF itemtemp(73) = 2 THEN RETURN NO 'Cannot be sold/dropped RETURN YES END FUNCTION 'Returns a length two bitvector (values 0-3), a combination of: '1: can be used in-battle '2: can be used out of battle FUNCTION item_can_be_used_bits (byval item_id as integer) as integer DIM itemtemp(dimbinsize(binITM)) as integer loaditemdata itemtemp(), item_id DIM ret as integer IF itemtemp(50) > 0 THEN ret OR= 2 'Teaches a spell IF itemtemp(51) > 0 THEN ret OR= 2 'Triggers an attack (cure) out-of-battle IF itemtemp(51) < 0 THEN ret OR= 2 'Triggers a text box out-of-battle IF itemtemp(47) > 0 THEN ret OR= 1 'Triggers an attack in battle RETURN ret END FUNCTION SUB item_screen_cancel_action (byref st as ItemScreenState) menusound gen(genCancelSFX) IF st.swapcur THEN '--De-select the currently selected item DIM last as slice ptr = st.swapcur st.swapcur = 0 update_item_plank last, st ELSE st.quit = YES END IF END SUB 'Updates autoset tags SUB item_screen_usable_item_action (byref st as ItemScreenState) IF st.ps.cur = 0 THEN debug "item_screen_usable_item_action: no cursor ptr" : EXIT SUB DIM slot as integer = st.ps.cur->Extra(0) IF inventory(slot).used ANDALSO inventory(slot).num > 0 THEN DIM item_id as integer = inventory(slot).id IF item_can_be_used_bits(item_id) AND 2 THEN DIM consumed as bool = NO ' FIXME: can probably get rid of this later IF use_item_in_slot(slot, st.textbox, consumed) THEN st.re_use = YES evalitemtags evalherotags tag_updates END IF END IF st.need_update = YES st.refocus = YES st.swapcur = 0 END IF END SUB 'Updates autoset tags SUB item_screen_use_action (byref st as ItemScreenState) IF st.ps.cur = 0 THEN debug "item_screen_use_action: no cursor ptr" : EXIT SUB DIM slot as integer = st.ps.cur->Extra(0) DIM swapslot as integer = -1 IF st.swapcur THEN swapslot = st.swapcur->Extra(0) DIM lastcur as slice ptr = st.ps.cur DIM lastswap as slice ptr = st.swapcur SELECT CASE st.ps.cur->Lookup CASE SL_ITEM_EXITBUTTON: st.quit = YES EXIT SUB CASE SL_ITEM_SORTBUTTON: IF st.swapcur THEN st.swapcur = 0 update_item_plank lastswap, st ELSE inventory_autosort() st.need_update = YES st.refocus = YES END IF EXIT SUB CASE SL_ITEM_TRASHBUTTON: IF st.swapcur THEN IF inventory(swapslot).used ANDALSO item_can_be_discarded(inventory(swapslot).id) THEN MenuSound gen(genAcceptSFX) WITH inventory(swapslot) .used = 0 .id = 0 .num = 0 END WITH evalitemtags tag_updates END IF update_inventory_caption swapslot st.need_update = YES st.refocus = YES END IF EXIT SUB END SELECT IF st.re_use THEN st.re_use = NO item_screen_usable_item_action st EXIT SUB END IF IF st.swapcur THEN IF st.swapcur = st.ps.cur THEN item_screen_usable_item_action st EXIT SUB ELSE SWAP inventory(slot), inventory(swapslot) st.need_update = YES st.refocus = YES END IF st.swapcur = 0 update_item_plank lastswap, st ELSE st.swapcur = st.ps.cur update_item_plank st.swapcur, st END IF END SUB SUB item_screen_mouse_handler(byref st as ItemScreenState) DIM oldhover as Slice Ptr = st.ps.hover IF plank_menu_update_hover(st.ps) THEN IF oldhover THEN update_item_plank oldhover, st IF st.ps.hover THEN update_item_plank st.ps.hover, st END IF DIM oldswap as Slice Ptr = st.swapcur IF st.swapcur ANDALSO (readmouse.buttons AND mouseRight) THEN 'Cancel swap cursor selection if you right-click st.swapcur = 0 IF oldswap THEN update_item_plank oldswap, st END IF DIM oldcur as Slice Ptr = st.ps.cur IF st.ps.hover THEN IF ((readmouse.release AND mouseLeft) ORELSE (readmouse.clicks AND mouseRight)) ANDALSO readmouse.drag_dist < 10 THEN 'Selecting the hovered plank 'and deselect the old selection st.ps.cur = st.ps.hover IF oldcur THEN update_item_plank oldcur, st IF st.ps.cur THEN update_item_plank st.ps.cur, st END IF END IF IF st.ps.cur THEN IF st.ps.cur <> oldcur THEN 'Cursor just changed menusound gen(genCursorSFX) update_plank_scrolling st.ps expand_slice_text_insert_codes st.ps.m, @ExpandTextItemScreen, st.ps.cur, st.swapcur IF st.ps.cur ANDALSO st.swapcur ANDALSO st.ps.cur <> st.swapcur THEN 'Confirming a swap, so it is okay to to activate now IF (readmouse.release AND mouseLeft) THEN item_screen_use_action st END IF END IF IF st.ps.cur ANDALSO (st.ps.cur->Lookup = SL_ITEM_EXITBUTTON ORELSE st.ps.cur->Lookup = SL_ITEM_SORTBUTTON) THEN 'the exit button and the sort buttons don't need to be pre-focused, we can activate them now IF (readmouse.release AND mouseLeft) THEN item_screen_use_action st END IF END IF ELSEIF st.ps.hover = st.ps.cur THEN 'Cursor did not change, so a second click can activate it IF (readmouse.release AND mouseLeft) THEN menusound gen(genCursorSFX) item_screen_use_action st END IF END IF END IF plank_menu_drag_scroll st.ps plank_menu_mouse_wheel st.ps END SUB FUNCTION item_screen () as integer 'Returns 0, or ID of textbox to open (box 0 can't be opened) DIM st as ItemScreenState st.ps.is_plank_callback = @is_item_plank st.ps.state_callback = @set_item_plank_state DIM holdscreen as integer = duplicatepage(vpage) st.ps.m = LoadSliceCollection(SL_COLLECT_ITEMSCREEN) ERROR_IF(st.ps.m = NULL, "Couldn't load slices!", 0) item_screen_refresh st show_virtual_gamepad() menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys playtimer IF game_check_cancel_key() THEN item_screen_cancel_action st END IF IF game_check_use_key() ORELSE st.re_use THEN item_screen_use_action st END IF IF get_gen_bool("/mouse/mouse_menus") ANDALSO NOT st.re_use THEN item_screen_mouse_handler(st) END IF st.lastcur = st.ps.cur IF plank_menu_arrows(st.ps) THEN update_item_plank st.lastcur, st update_item_plank st.ps.cur, st menusound gen(genCursorSFX) update_plank_scrolling st.ps expand_slice_text_insert_codes st.ps.m, @ExpandTextItemScreen, st.ps.cur, st.swapcur END IF IF keyval(scF8) > 1 THEN slice_editor st.ps.m st.need_update = YES END IF IF st.quit THEN menusound gen(genCancelSFX) EXIT DO END IF IF st.textbox > 0 THEN EXIT DO END IF IF UpdateScreenSlice() THEN st.need_update = YES IF st.need_update THEN item_screen_refresh st st.need_update = NO END IF 'Draw the screen copypage holdscreen, vpage DrawSlice st.ps.m, vpage setvispage vpage dowait LOOP freepage holdscreen DeleteSlice @(st.ps.m) setkeys RETURN st.textbox END FUNCTION SUB inventory_autosort() DIM autosort_changed as bool = NO 'First sort all items to the top DIM numitems as integer = 0 FOR i as integer = 0 TO last_inv_slot() - 1 IF inventory(i).used THEN numitems += 1 : CONTINUE FOR FOR o as integer = i + 1 TO last_inv_slot() IF inventory(o).used THEN SWAP inventory(i), inventory(o) numitems += 1 autosort_changed = YES EXIT FOR END IF NEXT o NEXT i IF gen(genAutosortScheme) = 0 THEN 'Sorting by type: cache the sort order of the items, or the cost of repeatedly loading 'item data might go out of control DIM itemdata(dimbinsize(binITM)) as integer FOR slot as integer = 0 TO numitems - 1 loaditemdata itemdata(), inventory(slot).id IF itemdata(47) THEN 'Usable in-battle inventory(slot).sortorder = 0 ELSEIF itemdata(51) > 0 THEN 'Usable out-of-battle (attack) inventory(slot).sortorder = 10 ELSEIF itemdata(51) < 0 THEN 'Usable out-of-battle (textbox) inventory(slot).sortorder = 20 ELSEIF itemdata(50) THEN 'Teach spell inventory(slot).sortorder = 30 ELSEIF item_is_equippable(itemdata()) THEN 'Equippable FOR i as integer = 0 to 4 IF item_is_equippable_in_slot(itemdata(), i) THEN inventory(slot).sortorder = 40 + i EXIT FOR END IF NEXT i ELSE inventory(slot).sortorder = 50 END IF IF itemdata(73) = 2 THEN inventory(slot).sortorder += 0 'Can not be sold/dropped ELSEIF itemdata(73) = 1 THEN inventory(slot).sortorder += 1 'Unlimited use ELSE inventory(slot).sortorder += 2 'Consumed by use END IF NEXT ELSEIF gen(genAutosortScheme) = 1 THEN 'Sorting by usable (into four groups, depending on whether usable in battle and out of battle): 'Usable in-and-out of battle, usable out of battle, usable in battle, not usable FOR slot as integer = 0 TO numitems - 1 inventory(slot).sortorder = 3 - item_can_be_used_bits(inventory(slot).id) NEXT END IF 'Then sort by the autosort criterion (use bubble sort because it's stable) FOR i as integer = 0 TO numitems - 1 FOR slot as integer = numitems - 2 TO i STEP -1 DIM disordered as bool = NO SELECT CASE gen(genAutosortScheme) CASE 0 'type IF inventory(slot).sortorder > inventory(slot + 1).sortorder THEN disordered = YES CASE 1 'use IF inventory(slot).sortorder > inventory(slot + 1).sortorder THEN disordered = YES CASE 2 'alphabetical IF string_compare(inventory(slot).text, inventory(slot + 1).text) > 0 THEN disordered = YES CASE 3 'id IF inventory(slot).id > inventory(slot + 1).id THEN disordered = YES CASE 4 'nothing END SELECT IF disordered THEN SWAP inventory(slot), inventory(slot + 1) autosort_changed = YES END IF NEXT slot NEXT i IF autosort_changed THEN menusound gen(genAcceptSFX) ELSE menusound gen(genCancelSFX) END IF END SUB '----------------------------------------------------------------------- TYPE SpellScreenState ps as PlankState slot as integer need_update as bool in_list as bool END TYPE SUB spell_screen_refresh(byref st as SpellScreenState) plank_menu_clear st.ps.m, SL_SPELL_LISTLIST plank_menu_clear st.ps.m, SL_SPELL_SPELLLIST 'expand_slice_text_insert_codes st.ps.m, @ExpandTextSpellScreen, st.ps.cur ' DIM pl as Slice Ptr ' FOR i as integer = 0 TO last_inv_slot() ' pl = plank_menu_append(st.ps.m, SL_ITEM_ITEMLIST, , @ExpandTextItemSlot, @i) ' IF pl THEN ' pl->Extra(0) = i ' update_item_plank pl, st ' ELSE ' debug "Failed to load item plank for slot " & i ' END IF ' NEXT i ' ' pl = LookupSlice(SL_ITEM_EXITBUTTON, st.ps.m) ' IF pl THEN pl->Extra(0) = -1 : update_item_plank pl, st ' pl = LookupSlice(SL_ITEM_SORTBUTTON, st.ps.m) ' IF pl THEN pl->Extra(0) = -1 : update_item_plank pl, st ' pl = LookupSlice(SL_ITEM_TRASHBUTTON, st.ps.m) ' IF pl THEN pl->Extra(0) = -1 :update_item_plank pl, st ' ' 'Early draw to force update of slice positions ' DrawSlice st.ps.m, vpage ' ' IF st.refocus THEN ' restore_plank_selection st.ps ' st.refocus = NO ' ELSE ' st.ps.cur = top_left_plank(st.ps) ' END IF ' update_item_plank st.ps.cur, st ' ' st.swapcur = 0 ' update_plank_scrolling st.ps END SUB SUB spell_screen (byval slot as integer) DIM st as SpellScreenState DIM holdscreen as integer = duplicatepage(vpage) st.ps.m = LoadSliceCollection(SL_COLLECT_SPELLSCREEN) ERROR_IF(st.ps.m = NULL, "Couldn't load slices!") spell_screen_refresh st show_virtual_gamepad() menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys playtimer IF game_check_cancel_key() THEN EXIT DO IF game_check_use_key() THEN END IF IF is_active_party_slot(slot) THEN IF NOT st.in_list THEN IF carray(ccLeft) > 1 THEN st.slot = loop_active_party_slot(st.slot, -1) st.need_update = YES menusound gen(genCursorSFX) END IF IF carray(ccRight) > 1 THEN st.slot = loop_active_party_slot(st.slot, 1) st.need_update = YES menusound gen(genCursorSFX) END IF END IF END IF IF keyval(scF8) > 1 THEN slice_editor st.ps.m st.need_update = YES END IF IF UpdateScreenSlice() THEN st.need_update = YES IF st.need_update THEN spell_screen_refresh st st.need_update = NO END IF 'Draw the screen copypage holdscreen, vpage DrawSlice st.ps.m, vpage setvispage vpage dowait LOOP menusound gen(genCancelSFX) freepage holdscreen DeleteSlice @(st.ps.m) END SUB '----------------------------------------------------------------------- FUNCTION trylearn (byval who as integer, byval atk as integer) as bool 'Try to add a spell to a hero's spelllists whereever it is set to be learnable from an item. 'who is hero position in the party, atk is attack ID + 1 '--returns whether the spell was learned at least once IF gam.hero(who).id = -1 THEN debug "trylearn fail on empty party slot " & who RETURN NO END IF DIM result as bool = NO dim her as HeroDef '--load the hero's data. loadherodata her, gam.hero(who).id '--for each spell list FOR j as integer = 0 TO 3 '--for each spell slot FOR o as integer = 0 TO 23 '--if this slot is empty and accepts this spell '--and is learnable by an item IF gam.hero(who).spells(j, o) = 0 AND her.spell_lists(j,o).attack = atk AND her.spell_lists(j,o).learned = 0 THEN gam.hero(who).spells(j, o) = atk result = YES END IF NEXT o NEXT j RETURN result END FUNCTION 'Remove an equipped item. Returns false if couldn't empty the slot, otherwise true. 'who: hero slot 'where: equip slot 'resetdefwep: if unequipping weapon, reequip the default weapon 'force: unequip even if there's no room in the inventory (item will be destroyed!) FUNCTION unequip (who as integer, where as integer, resetdefwep as bool = YES, force as bool = YES) as bool WITH gam.hero(who) '--exit if nothing is equiped IF .equip(where).id < 0 THEN RETURN YES '--return item to inventory (if not the default weapon) IF where = 0 AND .equip(where).id = .def_wep - 1 THEN ELSE IF getitem(.equip(where).id, , YES) = NO THEN 'ok_to_fail=YES debug "Returning " & readitemname(.equip(where).id) & " to inventory failed (force=" & force & ")" IF force = NO THEN RETURN NO END IF END IF '--blank out equipment .equip(where).id = -1 update_hero_max_and_cur_stats who IF where = 0 AND resetdefwep THEN '--restore default weapon IF doequip(.def_wep - 1, who, where) = NO THEN 'This would be a really weird edge case for this to fail. I don't think it could happen debug "Failed attempt to restore default weapon """ & readitemname(.def_wep - 1) & """ for hero """ & .name & """ after an unequip (That's weird)" END IF END IF END WITH evalitemtags evalherotags 'You could kill someone, right? tag_updates RETURN YES END FUNCTION SUB loadshopstuf (array() as integer, byval id as integer) DIM ol as integer = getbinsize(binSTF) \ 2 'old size on disk DIM nw as integer = curbinsize(binSTF) \ 2 'new size in memory flusharray array(), nw * 50 - 1, 0 'load shop data from STF lump loadrecord buffer(), game + ".stf", ol * 50, id 'in case shop data has been resized, scale records to new size FOR thingidx as integer = 0 to 49 'shop item FOR i as integer = 0 TO ol - 1 'field in shop item record array(thingidx * nw + i) = buffer(thingidx * ol + i) NEXT i ' Format upgrade: initialise stockidx IF array(thingidx * nw + 37) = 0 THEN array(thingidx * nw + 37) = 1 + thingidx NEXT thingidx END SUB FUNCTION count_available_spells(byval who as integer, byval list as integer) as integer DIM i as integer DIM n as integer = 0 FOR i = 0 to 23 IF gam.hero(who).spells(list, i) > 0 THEN n + = 1 NEXT i RETURN n END FUNCTION FUNCTION outside_battle_cure (byval atk as integer, byval target as integer, byval attacker as integer, byval spread as bool) as bool DIM i as integer DIM didcure as bool = NO IF spread = NO THEN IF chkOOBtarg(attacker, target, atk) THEN oobcure attacker, target, atk, 1 didcure = YES END IF ELSE DIM target_count as integer = 0 FOR i = 0 TO 3 IF chkOOBtarg(attacker, i, atk) THEN target_count += 1 NEXT i FOR i = 0 TO 3 IF chkOOBtarg(attacker, i, atk) THEN oobcure attacker, i, atk, target_count didcure = YES END IF NEXT i END IF checkfatal = YES evalherotags evalitemtags tag_updates RETURN didcure END FUNCTION '===Equip menu=== SUB equip_menu_controls_slot_picker (st as EquipMenuState) '--primary menu DIM do_pick as bool = NO IF game_check_cancel_key() THEN st.quit = YES EXIT SUB END IF IF st.allow_switch THEN IF carray(ccLeft) > 1 THEN 'Left: previous hero st.who = loop_active_party_slot(st.who, -1) equip_menu_setup st MenuSound gen(genCursorSFX) st.item_info = equip_menu_equipped_item_info(st) END IF IF carray(ccRight) > 1 THEN 'Right: next hero st.who = loop_active_party_slot(st.who, 1) equip_menu_setup st MenuSound gen(genCursorSFX) st.item_info = equip_menu_equipped_item_info(st) END IF END IF usemenusounds IF usemenu(st.slot, 0, 0, 6, 6) THEN st.item_info = equip_menu_equipped_item_info(st) END IF IF game_check_use_key() THEN do_pick = YES END IF IF get_gen_bool("/mouse/mouse_menus") THEN DIM hover as integer = -1 DIM old_slot as integer = st.slot IF rect_collide_point(Type(14, 10, 140, 16), readmouse.pos) THEN 'hero name box IF st.allow_switch ANDALSO (readmouse.release AND mouseLeft) THEN st.who = loop_active_party_slot(st.who, 1) equip_menu_setup st MenuSound gen(genCursorSFX) st.item_info = equip_menu_equipped_item_info(st) END IF ELSEIF rect_collide_point(Type(196, 36, 80, 78), readmouse.pos) THEN 'equipment box FOR i as integer = 0 TO 6 IF rect_collide_point(Type(204, 45 + i * 9, 8 * 8, 9), readmouse.pos) THEN hover = i NEXT i IF hover >= 0 THEN IF (readmouse.release AND mouseLeft) ANDALSO (hover = st.slot ORELSE hover = 6) THEN do_pick = YES IF (readmouse.release AND mouseLeft) THEN st.slot = hover END IF ELSE 'Anywhere else IF (readmouse.release AND mouseLeft) THEN st.quit = YES EXIT SUB END IF END IF IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN IF hover >= 0 THEN st.slot = hover ELSE st.quit = YES EXIT SUB END IF END IF IF st.slot <> old_slot THEN MenuSound gen(genCursorSFX) st.item_info = equip_menu_equipped_item_info(st) END IF END IF IF do_pick THEN IF st.slot < 5 THEN '--change equipment IF st.eq(st.slot).count > 0 OR gam.hero(st.who).equip(st.slot).id >= 0 THEN '--switch to change equipment mode st.mode = 1 st.eq_cursor.pt = 0 st.eq_cursor.top = 0 'Number of options = num equippable things + nothing/unequip option st.eq_cursor.last = (st.eq(st.slot).count + 1) - 1 equip_menu_stat_bonus st MenuSound gen(genAcceptSFX) st.item_info = equip_menu_available_item_info(st) END IF 'UPDATE ITEM POSSESSION BITSETS evalitemtags END IF IF st.slot = 5 THEN MenuSound gen(genCancelSFX) '--unequip all FOR i as integer = 0 TO 4 IF unequip(st.who, i, , NO) = NO THEN 'force=NO st.item_info = readglobalstring(305, "No room in inventory", 30) END IF NEXT i equip_menu_setup st 'UPDATE ITEM POSSESSION BITSETS evalitemtags END IF IF st.slot = 6 THEN st.quit = YES END IF END SUB SUB equip_menu_controls_equip_change (st as EquipMenuState) '--change equip menu DIM do_pick as bool = NO DIM do_cancel as bool = NO IF game_check_cancel_key() THEN do_cancel = YES usemenusounds IF usemenu(st.eq_cursor) THEN equip_menu_stat_bonus st st.item_info = equip_menu_available_item_info(st) END IF IF game_check_use_key() THEN do_pick = YES IF get_gen_bool("/mouse/mouse_menus") THEN DIM hover as integer = -1 DIM old_cursor as integer = st.eq_cursor.pt IF rect_collide_point(Type(188, 24, 96, 152), readmouse.pos) THEN 'equipment box DIM list_bottom as integer = st.eq_cursor.top + st.eq_cursor.size DIM list_last as integer = st.eq(st.slot).count FOR i as integer = st.eq_cursor.top TO small(list_bottom, list_last) IF rect_collide_point(Type(192, 28 + (i - st.eq_cursor.top) * 8, 11 * 8, 8), readmouse.pos) THEN hover = i NEXT i IF hover >= 0 ANDALSO readmouse.drag_dist < 10 THEN IF (readmouse.release AND mouseLeft) ANDALSO hover = st.eq_cursor.pt THEN do_pick = YES IF (readmouse.release AND mouseLeft) THEN st.eq_cursor.pt = hover END IF ELSE 'Anywhere else IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist < 10 THEN do_cancel = YES END IF END IF IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN IF hover >= 0 THEN st.eq_cursor.pt = hover ELSE do_cancel = YES END IF END IF 'Scrolling support st.eq_cursor.hover = hover st.eq_cursor.spacing = 8 mouse_scroll_menu(st.eq_cursor) mouse_drag_menu(st.eq_cursor) IF st.eq_cursor.pt <> old_cursor THEN MenuSound gen(genCursorSFX) equip_menu_stat_bonus st st.item_info = equip_menu_available_item_info(st) END IF END IF IF do_cancel THEN st.mode = 0 MenuSound gen(genCancelSFX) st.item_info = equip_menu_equipped_item_info(st) EXIT SUB END IF IF do_pick THEN IF st.eq_cursor.pt = st.eq(st.slot).count THEN '--unequip IF unequip(st.who, st.slot, , NO) = NO THEN 'force=NO st.item_info = readglobalstring(305, "No room in inventory", 30) END IF equip_menu_back_to_menu st MenuSound gen(genCancelSFX) ELSE '--normal equip DIM item_id as integer = inventory(st.eq(st.slot).offset(st.eq_cursor.pt)).id equip_menu_do_equip item_id, st MenuSound gen(genAcceptSFX) END IF END IF END SUB SUB equip_menu (who as integer, allow_switch as bool = YES) 'who is party slot. 'allow_switch is whether to allow switching left/right to other heroes DIM m(4) as string DIM page as integer = compatpage DIM holdscreen as integer = allocatepage DIM st as EquipMenuState st.allow_switch = allow_switch DIM i as integer DIM tog as integer = 0 '--get names m(0) = readglobalstring(38, "Weapon", 10) FOR i = 0 TO 3 m(i + 1) = readglobalstring(25 + i, "Armor" & i+1) NEXT i st.menu(5) = rpad(readglobalstring(39, "-REMOVE-", 8), " ", 8) st.menu(6) = rpad(readglobalstring(40, "-EXIT-", 8), " ", 8) '--initialize WITH st .mode = 0 .who = who .eq_cursor.size = 17 .default_weapon_name = "" .unequip_caption = rpad(readglobalstring(110, "Nothing", 10), " ", 11) END WITH equip_menu_setup st st.item_info = equip_menu_equipped_item_info(st) '--prepare the backdrop 'preserve the background behind the equip menu copypage page, holdscreen show_virtual_gamepad() '--main loop MenuSound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer IF st.mode = 0 THEN equip_menu_controls_slot_picker st ELSE equip_menu_controls_equip_change st END IF IF st.quit THEN EXIT DO '--display copypage holdscreen, page centerfuz 160, 100, 304, 184, 1, page 'backdrop box centerbox 84, 18, 140, 16, 4, page 'hero name centerbox 84, 102, 140, 130, 4, page 'stats centerbox 236, 75, 80, 78, 4, page 'equipment edgeprint gam.hero(st.who).name, 84 + ancCenter, 13, uilook(uiText), page DIM stat_v_offset as integer = 0 FOR i = 0 TO statLast 'Draw stats and bonuses IF should_hide_hero_stat(st.hero, i) THEN CONTINUE FOR DIM as string stat_caption, stat_caption2 DIM statcol2 as integer DIM resultant_stat as integer statcol2 = uilook(uiMenuItem) IF st.mode = 0 THEN '--Selecting an equip slot resultant_stat = gam.hero(st.who).stat.max.sta(i) ELSE '--Selecting an item IF st.stat_bonus(i) < 0 THEN stat_caption = STR(st.stat_bonus(i)) statcol2 = uilook(uiDisabledItem) ELSEIF st.stat_bonus(i) > 0 THEN stat_caption = "+" & st.stat_bonus(i) statcol2 = uilook(uiSelectedItem + tog) END IF 'Calculate new max stat resultant_stat = gam.hero(st.who).stat.base.sta(i) + st.stat_total_bonus(i) IF prefbit(43) THEN 'Cap minimum stats to zero resultant_stat = large(resultant_stat, 0) END IF DIM cap as integer = gen(genStatCap + i) IF cap > 0 AND resultant_stat > cap THEN resultant_stat = cap 'Don't flash anything unless the change in equipment actually modifies the stat IF st.stat_bonus(i) <> 0 THEN stat_caption = fgcol_text(stat_caption, uilook(uiSelectedDisabled)) statcol2 = uilook(uiSelectedDisabled + tog) END IF END IF END IF stat_caption2 = STR(resultant_stat) edgeprint statnames(i) & stat_caption, 20, 42 + stat_v_offset * 10, uilook(uiMenuItem), page, YES edgeprint stat_caption2, 148 - LEN(stat_caption2) * 8, 42 + stat_v_offset * 10, statcol2, page stat_v_offset += 1 NEXT i IF st.mode = 0 THEN '--main menu display FOR i = 0 TO 6 textcolor uilook(uiMenuItem), uilook(uiHighlight) IF i < 5 THEN IF gam.hero(st.who).equip(i).id = -1 AND st.eq(i).count = 0 THEN textcolor uilook(uiMenuItem), boxlook(0).bgcol END IF IF st.slot = i THEN textcolor uilook(uiSelectedItem + tog), uilook(uiHighlight + tog) IF i < 5 THEN IF st.eq(i).count = 0 THEN textcolor uilook(uiSelectedItem), uilook(uiHighlight2) END IF END IF printstr st.menu(i), 204, 45 + i * 9, page NEXT i IF st.slot < 5 THEN centerbox 236, 22, (LEN(m(st.slot)) + 2) * 8, 16, 4, page edgeprint m(st.slot), 236 - (LEN(m(st.slot)) * 4), 17, uilook(uiText), page END IF END IF IF st.mode = 1 THEN '--change equipment menu centerbox 236, 100, 96, 152, 4, page FOR i = st.eq_cursor.top TO st.eq_cursor.top + st.eq_cursor.size textcolor uilook(uiMenuItem), 0 IF i = st.eq_cursor.pt THEN textcolor uilook(uiSelectedItem + tog), uilook(uiHighlight2) IF i > st.eq(st.slot).count THEN '--all done! EXIT FOR END IF printstr equip_menu_available_item_caption(st, i), 192, 28 + (i - st.eq_cursor.top) * 8, page NEXT i END IF centerbox 160, 192, 312, 16, 4, page edgeprint st.item_info, pCenteredLeft, 187, uilook(uiText), page setvispage vpage dowait LOOP setkeys freepage page freepage holdscreen MenuSound gen(genCancelSFX) 'tags handled in unequip, doequip END SUB 'Returns the item ID corresponding to the selected menu item of the equippable item menu, 'or -1 if Unequip is selected and it's not the default weapon FUNCTION equip_menu_available_item_id(st as EquipMenuState) as integer IF st.eq_cursor.pt = st.eq(st.slot).count THEN '--last option: unequip IF st.slot = 0 THEN '--special handling for weapon RETURN gam.hero(st.who).def_wep - 1 ELSE RETURN -1 END IF ELSE '--equippable item RETURN inventory(st.eq(st.slot).offset(st.eq_cursor.pt)).id END IF END FUNCTION 'Returns the caption for a menu item in the equippable item menu FUNCTION equip_menu_available_item_caption(st as EquipMenuState, byval menuslot as integer) as string IF menuslot = st.eq(st.slot).count THEN '--last option: unequip IF st.slot = 0 THEN RETURN st.default_weapon_name ELSE RETURN st.unequip_caption END IF ELSE '--equippable item RETURN inventory(st.eq(st.slot).offset(menuslot)).text END IF END FUNCTION FUNCTION equip_menu_equipped_item_info(st as EquipMenuState) as string WITH gam.hero(st.who) IF st.slot <= UBOUND(.equip) ANDALSO .equip(st.slot).id >= 0 THEN RETURN readitemdescription(.equip(st.slot).id) END IF END WITH RETURN "" END FUNCTION FUNCTION equip_menu_available_item_info(st as EquipMenuState) as string DIM item as integer = equip_menu_available_item_id(st) IF item >= 0 THEN RETURN readitemdescription(item) END IF RETURN "" END FUNCTION SUB equip_menu_setup (byref st as EquipMenuState) DIM byref herost as HeroState = gam.hero(st.who) loadherodata st.hero, herost.id st.default_weapon_name = rpad(readitemname(herost.def_wep - 1), " ", 11) IF LEN(TRIM(st.default_weapon_name)) = 0 THEN st.default_weapon_name = st.unequip_caption END IF FOR i as integer = 0 TO 4 st.menu(i) = " " IF herost.equip(i).id >= 0 THEN st.menu(i) = rpad(readitemname(herost.equip(i).id), " ", 8) END IF NEXT i 'erase the tables of equippables FOR i as integer = 0 TO 4 FOR j as integer = 0 TO last_inv_slot() st.eq(i).offset(j) = -1 NEXT j st.eq(i).count = 0 NEXT i DIM itembuf(dimbinsize(binITM)) as integer FOR i as integer = 0 TO last_inv_slot() IF inventory(i).used THEN '--load item data loaditemdata itembuf(), inventory(i).id FOR eq_slot as integer = 0 to 4 IF item_is_equippable_in_slot(itembuf(), eq_slot) THEN '--if this item is equipable in this slot IF item_read_equipbit(itembuf(), herost.id) THEN '--if this item is equipable by this hero WITH st.eq(eq_slot) .offset(.count) = i .count += 1 END WITH END IF END IF NEXT eq_slot END IF NEXT i END SUB SUB equip_menu_do_equip(byval item as integer, byref st as EquipMenuState) IF doequip(item, st.who, st.slot) = NO THEN st.item_info = readglobalstring(305, "No room in inventory", 30) MenuSound gen(genCancelSFX) END IF equip_menu_back_to_menu st END SUB SUB equip_menu_back_to_menu(byref st as EquipMenuState) st.mode = 0 equip_menu_setup st END SUB SUB equip_menu_stat_bonus(byref st as EquipMenuState) 'Load change in stat bonuses of currently hovered item for display '(that is, bonuses of hovered item minus bonuses of equipped item) 'as well as the resultant total stat bonuses DIM item as integer = equip_menu_available_item_id(st) 'Add new item bonuses DIM itembuf(dimbinsize(binITM)) as integer IF item = -1 THEN '--nothing to load! flusharray st.stat_bonus() ELSE loaditemdata itembuf(), item FOR i as integer = 0 TO statLast st.stat_bonus(i) = itembuf(54 + i) NEXT i END IF 'Subtract old item bonuses DIM equipped as integer = gam.hero(st.who).equip(st.slot).id IF equipped >= 0 THEN loaditemdata itembuf(), equipped FOR i as integer = 0 TO statLast st.stat_bonus(i) -= itembuf(54 + i) NEXT i END IF 'Calculate new total bonuses hero_total_equipment_bonuses st.who, st.stat_total_bonus() FOR i as integer = 0 TO statLast st.stat_total_bonus(i) += st.stat_bonus(i) NEXT i END SUB FUNCTION use_item_in_slot(byval slot as integer, byref trigger_box as integer, byref consumed as bool = NO) as bool '--slot is the index in your inventory '--trigger_box is used to communicate when an item has triggered a text box (note, box 0 can't be triggered) '--consumed optionally communicates whether the item was actually consumed '--return value is YES when an item is used, and NO when it is not used. '--Does not update autoset tags! consumed = NO IF inventory(slot).used = NO THEN RETURN NO DIM itemdata(dimbinsize(binITM)) as integer loaditemdata itemdata(), inventory(slot).id DIM attack_name as string = readbadbinstring(itemdata(), 0, 8, 0) DIM should_consume as integer = (itemdata(73) = 1) DIM attack_id as integer = itemdata(51) - 1 DIM is_attack_item as integer = ( itemdata(51) > 0 ANDALSO NOT itemdata(50) > 0 ) IF use_item_by_id(inventory(slot).id, trigger_box, inventory(slot).text) THEN IF should_consume THEN IF consumeitem(slot) THEN IF is_attack_item ANDALSO inventory(slot).used = NO THEN '--used the last attack item (potion) in a consumable stack menu_attack_targ_picker attack_id, -1, -1, rpad(attack_name, " ", 8) & CHR(1) & " 0", , NO menusound gen(genCancelSFX) END IF consumed = YES END IF END IF RETURN YES END IF RETURN NO END FUNCTION FUNCTION use_item_by_id(byval item_id as integer, byref trigger_box as integer, name_override as string="") as bool '--item_id is the actual ID number, not offset. '--trigger_box communicates the box id if this item triggerd a text box. ' this will remain unchanged (zero) if there is no box. The caller is ' responsible for loading the box. (FIXME: if it makes sense to ' actually load the box from here later, that would be awesome!) '--name_override is used so the inventory slot text can be used to replace ' the item name (which we would want because inventory slot text shows ' how many items are in the slot you are currently using) ' if name_override is left blank, you will just see the item name '--return value is YES if the item use was confirmed by the user ' or NO if it was cancelled or otherwise failed. '--This sub does not care if you actually own the item in question, ' nor will it consume items from your inventory even if the item is a consuming item. '--Does not update autoset tags! DIM itemdata(dimbinsize(binITM)) as integer loaditemdata itemdata(), item_id DIM caption as string IF name_override <> "" THEN caption = name_override ELSE caption = readbadbinstring(itemdata(), 0, 8, 0) END IF IF itemdata(50) > 0 THEN '--learn a spell MenuSound gen(genAcceptSFX) IF menu_attack_targ_picker(-1, itemdata(50)-1, -1, caption) THEN '--successfully learned RETURN YES END IF RETURN NO END IF IF itemdata(51) > 0 THEN '--attack/oobcure MenuSound gen(genAcceptSFX) IF menu_attack_targ_picker(itemdata(51)-1, -1, -1, caption) THEN RETURN YES END IF RETURN NO END IF IF itemdata(51) < 0 THEN '--trigger a text box trigger_box = itemdata(51) * -1 RETURN YES END IF RETURN NO END FUNCTION FUNCTION menu_attack_targ_picker(byval attack_id as integer, byval learn_id as integer, byval attacker as integer, use_caption as string, byval x_offset as integer=0, byval really_use_attack as bool=YES) as bool 'Lets the player pick a target, and then performs an attack or teaches a spell 'attack_id: attack to be used, -1 otherwise 'learn_id: attack to be learnt, -1 otherwise '(FIXME: should move to separate function! Why not just use pickhero for learning spells??) 'attacker == -1 when not using an attack from a spell list. In that case, use party's avg stats 'Returns true if the attack/spell was actually used/learned 'Doesn't update tags, caller must. 'FIXME: x_offset should probably go away in favor of a slice template at some point in the future menu_attack_targ_picker = NO STATIC targ as integer '0-3 or not used if spread=YES. Can be -1 but that doesn't indicate spread. STATIC spread as bool '--Preserve background for display beneath the targ picker menu DIM page as integer page = compatpage DIM holdscreen as integer holdscreen = allocatepage copypage page, holdscreen DIM wtog(3) as integer DIM tog as integer DIM col as integer DIM atk as AttackData DIM learn_attack as AttackData DIM caption as string DIM allow_spread as bool = NO DIM must_spread as bool = NO '--Build the menu from slices DIM ps as PlankState 'This menu is not fully plankified, but I did want to be able to call set_plank_state ps.m = menu_attack_targ_picker_make_slices(x_offset) ps.state_callback = @menu_targpick_plank_state_callback DIM holder as Slice Ptr = LookupSlice(SL_PLANK_HOLDER, ps.m) IF attack_id >= 0 THEN loadattackdata atk, attack_id allow_spread = (atk.targ_set = 2) must_spread = (atk.targ_set = 1) END IF IF learn_id >= 0 THEN loadattackdata learn_attack, learn_id END IF '--make sure the default target is okay IF chkOOBtarg(attacker, targ, attack_id) = NO THEN targ = -1 FOR i as integer = 0 TO 3 IF chkOOBtarg(attacker, i, attack_id) THEN targ = i EXIT FOR END IF NEXT i END IF IF allow_spread = NO THEN spread = NO IF must_spread THEN spread = YES DIM do_effect as bool = NO setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer do_effect = NO '--handle keys IF game_check_cancel_key() THEN menusound gen(genCancelSFX) EXIT DO END IF IF spread = NO THEN IF carray(ccUp) > 1 THEN getOOBtarg -1, attacker, targ, attack_id MenuSound gen(genCursorSFX) END IF IF carray(ccDown) > 1 THEN getOOBtarg 1, attacker, targ, attack_id MenuSound gen(genCursorSFX) END IF 'FIXME: should use usemenu() with unselectable menu items, so pgup/pgdn/home/end work... and mouse too END IF IF allow_spread THEN IF carray(ccLeft) > 1 OR carray(ccRight) > 1 THEN MenuSound gen(genCursorSFX) spread = NOT spread END IF END IF IF game_check_use_key() THEN do_effect = YES END IF IF keyval(scF8) > 1 THEN slice_editor ps.m END IF '--handle mouse DIM old_targ as integer = targ DIM old_spread as bool = spread IF get_gen_bool("/mouse/mouse_menus") THEN DIM hover as integer = -1 FOR i as integer = 0 to 3 DIM hover_sl as Slice Ptr = SliceChildByIndex(holder, i) IF SliceCollidePoint(hover_sl, readmouse.pos) THEN hover = i NEXT i IF hover = -1 ANDALSO ((readmouse.release AND mouseLeft) ORELSE (readmouse.release AND mouseRight)) THEN 'Clicked outside the box, close the menu menusound gen(genCancelSFX) EXIT DO END IF IF spread THEN 'Spread targetting IF (hover >= 0) ANDALSO (readmouse.release AND mouseLeft) THEN 'Trigger action do_effect = YES END IF IF NOT must_spread ANDALSO (readmouse.release AND mouseRight) THEN 'Right-click on the box can cancel spread spread = NO END IF ELSE 'Focused targetting IF hover >= 0 THEN IF allow_spread THEN IF (readmouse.release AND mouseRight) THEN 'Toggle spread with a right-click in the box spread = YES END IF IF (readmouse.release AND mouseLeft) ANDALSO readmouse.drag_dist > 20 THEN 'Turn spread on with a left-drag in the box spread = YES END IF END IF IF NOT spread THEN 'Only proceed if we have not just switched to spread mode IF hover <> targ ANDALSO (readmouse.release AND mouseLeft) THEN 'Change focus IF chkOOBtarg(attacker, hover, attack_id) THEN targ = hover spread = NO ELSE 'Play the cancel sound if you tried to select an invalid hero menusound gen(genCancelSFX) END IF ELSEIF hover = targ ANDALSO (readmouse.release AND mouseLeft) THEN 'Trigger action do_effect = YES END IF END IF END IF END IF END IF IF targ <> old_targ ORELSE spread <> old_spread THEN MenuSound gen(genCursorSFX) END IF IF do_effect THEN 'DO ACTUAL EFFECT IF targ = -1 THEN menusound gen(genCancelSFX) EXIT DO END IF 'if can teach a spell IF learn_id >= 0 THEN '--teach spell IF trylearn(targ, learn_id + 1) THEN '--announce learn menusound gen(genItemLearnSFX) caption = gam.hero(targ).name & " " & readglobalstring(124, "learned", 10) & " " & learn_attack.name centerbox 160, 100, small(LEN(caption) * 8 + 16, 320), 24, 1, page edgeprint caption, pCenteredLeft, 95, uilook(uiText), page IF learn_attack.learn_sound_effect > 0 THEN playsfx learn_attack.learn_sound_effect - 1 setvispage vpage waitforanykey menu_attack_targ_picker = YES ELSE menusound gen(genCantLearnSFX) END IF END IF '--do attack outside of battle (cure) IF attack_id >= 0 ANDALSO really_use_attack THEN IF outside_battle_cure(attack_id, targ, attacker, spread) THEN 'Re-check validity of target IF chkOOBtarg(attacker, targ, attack_id) = NO THEN getOOBtarg 1, attacker, targ, attack_id END IF 'We will return success, but don't leave menu yet; you might be able to use the attack again menu_attack_targ_picker = YES END IF END IF EXIT DO END IF '--done using attack '--draw the targ picker menu copypage holdscreen, page FOR i as integer = 0 TO 3 DIM holder as Slice ptr = LookupSlice(SL_PLANK_HOLDER, ps.m) DIM plank as Slice Ptr = SliceChildByIndex(holder, i) IF plank <> NULL THEN DIM targetted as bool = (targ = i ORELSE (spread ANDALSO chkOOBtarg(attacker, i, attack_id))) IF targetted THEN ps.cur = holder 'Dubious set_plank_state ps, plank, iif(targetted, plankSEL, plankNORMAL) IF gam.hero(i).id >= 0 THEN IF targetted THEN loopvar wtog(i), 0, max_wtog(plank, dirDown) ELSE wtog(i) = 0 END IF set_walkabout_frame plank, dirDown, wtog_to_frame(wtog(i)) END IF caption = "" IF attack_id >= 0 THEN IF atk.targ_stat = statHP or atk.targ_stat = statMP THEN caption = gam.hero(i).stat.cur.sta(atk.targ_stat) & "/" & gam.hero(i).stat.max.sta(atk.targ_stat) & " " & statnames(atk.targ_stat) ELSEIF atk.targ_stat <= statLast THEN caption = gam.hero(i).stat.cur.sta(atk.targ_stat) & " " & statnames(atk.targ_stat) ELSE 'Targets a register, which does nothing, but maybe the attack has some side effect caption = gam.hero(i).name END IF ELSEIF learn_id >= 0 THEN caption = gam.hero(i).name END IF DIM txt as Slice Ptr = LookupSlice(SL_PLANK_MENU_SELECTABLE, plank, slText) ChangeTextSlice txt, caption END IF NEXT i DIM txt as Slice Ptr = LookupSlice(SL_SINGLE_CAPTION_TEXT, ps.m, slText) ChangeTextSlice txt, use_caption DrawSlice ps.m, page setvispage vpage dowait LOOP setkeys freepage page freepage holdscreen DeleteSlice @(ps.m) END FUNCTION FUNCTION menu_attack_targ_picker_make_slices(x_offset as integer) as Slice Ptr DIM tallness as integer = 20 FOR i as integer = 0 to 3 IF gam.hero(i).id >= 0 THEN DIM spr as Slice Ptr = LookupSlice(SL_WALKABOUT_SPRITE_COMPONENT, gam.hero(i).sl) tallness = large(tallness, spr->Height) END IF NEXT i DIM root_sl as Slice Ptr = NewSliceOfType(slSpecial) root_sl->Fill = YES RefreshSliceScreenPos root_sl DIM box as Slice Ptr = NewSliceOfType(slRectangle, root_sl) WITH *box .x = x_offset .y = 3 .Width = 160 .Height = tallness * 4 + 2 .AnchorHoriz = alignCenter .AnchorVert = alignTop .AlignHoriz = alignCenter .AlignVert = alignTop .PaddingLeft = 4 .PaddingRight = 4 .PaddingTop = 5 .PaddingBottom = 0 END WITH ChangeRectangleSlice box, 1 DIM grid as Slice Ptr = NewSliceOfType(slGrid, box, SL_PLANK_HOLDER) WITH *grid .Fill = YES END WITH ChangeGridSlice grid, 4, 1 FOR i as integer = 0 TO 3 '--Plank DIM plank as Slice Ptr = NewSliceOfType(slContainer, grid) plank->Fill = YES '--Rectangle that highlights the selected one DIM target_rect as Slice Ptr = NewSliceOfType(slRectangle, plank, SL_PLANK_MENU_SELECTABLE) target_rect->Fill = YES ChangeRectangleSlice target_rect, , uiHighlight2 * -1 - 1, , borderNone IF gam.hero(i).id >= 0 THEN '--Copy of the hero's walkabout slices DIM walk as Slice Ptr = CloneSliceTree(gam.hero(i).sl) SetSliceParent walk, plank walk->X = 5 walk->Y = 0 walk->Visible = YES RealignSlice walk, alignLeft, alignBottom, alignLeft, alignBottom DIM spr as Slice Ptr = LookupSlice(SL_WALKABOUT_SPRITE_COMPONENT, walk) 'Cancel any foot offset copied from the current map spr->Y = 0 '--Text caption DIM txt as Slice Ptr = NewSliceOfType(slText, plank, SL_PLANK_MENU_SELECTABLE) ChangeTextSlice txt, "placeholder", uiMenuItem * -1 - 1, YES RealignSlice txt, alignLeft, alignCenter, alignLeft, alignCenter txt->X = 35 txt->Y = 3 END IF NEXT i DIM caption_rect as Slice ptr = NewSliceOfType(slRectangle, box) RealignSlice caption_rect, alignCenter, alignBottom, alignCenter, alignTop WITH *caption_rect .Y = 1 .Width = LEN("placeholder") * 8 + 32 .Height = 16 END WITH ChangeRectangleSlice caption_rect, 3, , , , transFuzzy DIM caption as Slice ptr = NewSliceOfType(slText, caption_rect, SL_SINGLE_CAPTION_TEXT) CenterSlice caption ChangeTextSlice caption, "placeholder", uilook(uiText), YES RETURN root_sl END FUNCTION SUB menu_targpick_plank_state_callback (byval sl as Slice Ptr, byval state as PlankItemState) 'First call the default callback set_plank_state_default_callback sl, state 'Now custom stuff for the menu attack target picker SELECT CASE sl->SliceType CASE slRectangle: 'This menu uses a different bgcol for the highlight SELECT CASE state CASE plankSEL: ChangeRectangleSlice sl, , uiHighlight2 * -1 - 1 END SELECT END SELECT END SUB SUB spells_menu_refresh_list(sp as SpellsMenuState) IF sp.lists(sp.listnum).magic_type < 0 THEN EXIT SUB DIM atk as AttackData FOR i as integer = 0 TO 23 WITH sp.spell(i) .name = "" .desc = "" .cost = "" .id = -1 .can_use = NO .targt = 0 .tstat = 0 IF gam.hero(sp.hero).spells(sp.lists(sp.listnum).menu_index, i) > 0 THEN .id = gam.hero(sp.hero).spells(sp.lists(sp.listnum).menu_index, i) - 1 loadattackdata atk, .id IF atk.useable_outside_battle THEN .can_use = atkallowed(atk, sp.hero, sp.lists(sp.listnum).magic_type, i \ spellsPerLMP) .targt = atk.targ_set .tstat = atk.targ_stat END IF .name = atk.name .desc = atk.description .cost = hero_attack_cost_info(atk, sp.hero, sp.lists(sp.listnum).magic_type, i \ spellsPerLMP) END IF .name = rpad(.name, " ", 10) END WITH NEXT i END SUB SUB spells_menu_refresh_hero(sp as SpellsMenuState) DIM her as HeroDef loadherodata her, gam.hero(sp.hero).id '--first blank out lists FOR i as integer = 0 TO UBOUND(sp.lists) WITH sp.lists(i) .magic_type = -1 .menu_index = -1 .name = "" END WITH NEXT i DIM lists_added as integer 'bitvector DIM slot as integer = 0 DIM listnum as integer DIM hero_node as NodePtr = her.reld READNODE hero_node."battle_menus" as batmenus WITHNODE batmenus."menu" as m DIM k as NodePtr k = m."kind".ptr IF k."spells".exists THEN listnum = k."spells".integer IF listnum < 0 ORELSE listnum > UBOUND(her.list_type) THEN showerror "Invalid spell list " & listnum FreeNode m EXIT READNODE END IF 'Only add each spell list once DIM list_bit as integer = 1 SHL listnum IF lists_added AND list_bit THEN CONTINUE READNODE WITH sp.lists(slot) .menu_index = listnum .magic_type = her.list_type(.menu_index) IF .magic_type = 0 OR .magic_type = 1 THEN 'Only display MP-based and LMP-based spell lists. Ignore Random lists. IF readbit(her.bits(), 0, 26) = NO ORELSE count_available_spells(sp.hero, .menu_index) > 0 THEN .name = her.list_name(.menu_index) IF .name <> "" THEN 'Only show lists with non-blank names .name = rpad(.name, " ", 10) slot += 1 lists_added OR= list_bit END IF END IF END IF END WITH END IF END WITHNODE END READNODE sp.last = slot WITH sp.lists(sp.last) .name = rpad(readglobalstring(46, "EXIT", 10), " ", 10) .menu_index = -1 .magic_type = -1 END WITH IF sp.listnum > sp.last THEN sp.listnum = sp.last spells_menu_refresh_list sp END SUB FUNCTION spells_menu_control_hover() as integer 'Returns which spell slot the mouse is over or 24 for "cancel" or -1 for none FOR i as integer = 0 to 23 DIM x as integer = (i MOD 3) DIM y as integer = (i \ 3) IF rect_collide_point(XYWH(12 + x * 104, 90 + y * 8, 96, 8), readmouse.pos) THEN RETURN i NEXT i IF rect_collide_point(XYWH(9, 171, 96, 8), readmouse.pos) THEN RETURN 24 'The cancel button RETURN -1 END FUNCTION SUB spells_menu_control_which_list(sp as SpellsMenuState) IF game_check_cancel_key() THEN sp.quit = YES : EXIT SUB IF carray(ccLeft) > 1 THEN DO loopvar sp.hero, 0, 3, -1 LOOP UNTIL gam.hero(sp.hero).id >= 0 menusound gen(genCursorSFX) spells_menu_refresh_hero sp END IF IF carray(ccRight) > 1 THEN DO loopvar sp.hero, 0, 3 LOOP UNTIL gam.hero(sp.hero).id >= 0 menusound gen(genCursorSFX) spells_menu_refresh_hero sp END IF usemenusounds IF usemenu (sp.listnum, 0, 0, sp.last, 5) THEN spells_menu_refresh_list sp END IF DIM do_pick as bool = NO IF game_check_use_key() THEN do_pick = YES IF get_gen_bool("/mouse/mouse_menus") THEN DIM hover as integer = -1 DIM old_listnum as integer = sp.listnum IF rect_collide_point(XYWH(6, 86, 308, 96), readmouse.pos) THEN 'Mouse click over the spell area switches to that mode IF (readmouse.release AND mouseLeft) ORELSE (readmouse.release AND mouseRight) THEN sp.mset = 1 sp.cursor = spells_menu_control_hover() IF sp.cursor = -1 THEN sp.cursor = 0 EXIT SUB END IF ELSEIF rect_collide_point(XYWH(14, 20, 84, 60), readmouse.pos) THEN 'Mouse over list of spell lists FOR i as integer = 0 TO sp.last IF rect_collide_point(XYWH(16, 25 + i * 10, 16 * 8, 10), readmouse.pos) THEN hover = i NEXT i IF (readmouse.release AND mouseLeft) ANDALSO hover >= 0 THEN IF sp.listnum = hover ORELSE hover = sp.last THEN do_pick = YES END IF IF (readmouse.buttons AND mouseLeft) ANDALSO hover >= 0 THEN sp.listnum = hover END IF ELSEIF rect_collide_point(XYWH(106, 28, 200, 17), readmouse.pos) THEN IF (readmouse.release AND mouseLeft) THEN 'Clicked on hero name box DO loopvar sp.hero, 0, 3 LOOP UNTIL gam.hero(sp.hero).id >= 0 menusound gen(genCursorSFX) spells_menu_refresh_hero sp END IF ELSE 'Mouse anywhere else IF (readmouse.release AND mouseLeft) THEN sp.quit = YES EXIT SUB END IF END IF IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN IF hover >= 0 THEN 'right-click select on the list of spell lists sp.listnum = hover ELSE 'Right-click anywhere else exits sp.quit = YES EXIT SUB END IF END IF IF old_listnum <> sp.listnum THEN menusound gen(genCursorSFX) END IF IF do_pick THEN IF sp.lists(sp.listnum).menu_index = -1 THEN sp.quit = YES : EXIT SUB menusound gen(genAcceptSFX) sp.mset = 1 sp.cursor = 0 END IF END SUB SUB spells_menu_control_pick_spell(sp as SpellsMenuState) DIM do_pick as bool = NO 'Keyboard controls IF sp.re_use = NO THEN IF game_check_cancel_key() THEN sp.mset = 0 menusound gen(genCancelSFX) END IF IF carray(ccUp) > 1 THEN sp.cursor = sp.cursor - 3 menusound gen(genCursorSFX) IF sp.cursor < 0 THEN sp.cursor = 24 END IF IF carray(ccDown) > 1 THEN IF sp.cursor < 24 THEN sp.cursor = small(sp.cursor + 3, 24) ELSE sp.cursor = 0 END IF menusound gen(genCursorSFX) END IF IF sp.cursor < 24 THEN 'EXIT not selected IF carray(ccLeft) > 1 THEN IF sp.cursor MOD 3 THEN sp.cursor = sp.cursor - 1 ELSE sp.cursor = sp.cursor + 2 END IF menusound gen(genCursorSFX) END IF IF carray(ccRight) > 1 THEN IF sp.cursor MOD 3 = 2 THEN sp.cursor = sp.cursor - 2 ELSE sp.cursor += 1 menusound gen(genCursorSFX) END IF IF keyval(scPageUp) > 1 THEN sp.cursor = sp.cursor MOD 3 menusound gen(genCursorSFX) END IF IF keyval(scPageDown) > 1 THEN sp.cursor = (sp.cursor MOD 3) + 21 menusound gen(genCursorSFX) END IF END IF END IF IF game_check_use_key() OR sp.re_use THEN do_pick = YES END IF IF get_gen_bool("/mouse/mouse_menus") ANDALSO NOT sp.re_use THEN DIM hover as integer = spells_menu_control_hover() DIM old_cursor as integer = sp.cursor IF rect_collide_point(XYWH(6, 86, 308, 96), readmouse.pos) THEN ' Mouse is over the spell area IF hover >= 0 THEN IF (readmouse.release AND mouseLeft) ANDALSO (sp.cursor = hover ORELSE hover = 24) THEN do_pick = YES IF (readmouse.release AND mouseLeft) THEN sp.cursor = hover END IF ELSEIF rect_collide_point(XYWH(14, 20, 84, 60), readmouse.pos) THEN 'Mouse over list of spell lists IF (readmouse.release AND mouseLeft) THEN 'Click back into the spell list picker sp.mset = 0 EXIT SUB END IF ELSEIF rect_collide_point(XYWH(106, 28, 200, 17), readmouse.pos) THEN IF (readmouse.release AND mouseLeft) THEN 'Clicked on hero name box sp.mset = 0 DO loopvar sp.hero, 0, 3 LOOP UNTIL gam.hero(sp.hero).id >= 0 menusound gen(genCursorSFX) spells_menu_refresh_hero sp END IF ELSE 'Mouse anywhere else IF (readmouse.release AND mouseLeft) THEN sp.quit = YES EXIT SUB END IF END IF IF (readmouse.release AND mouseRight) ANDALSO readmouse.drag_dist < 10 THEN IF hover >= 0 THEN 'Right click select sp.cursor = hover ELSE 'Right anywhere else goes back sp.mset = 0 END IF END IF IF old_cursor <> sp.cursor THEN menusound gen(genCursorSFX) END IF IF do_pick THEN sp.re_use = NO IF sp.cursor = 24 THEN sp.mset = 0 IF sp.spell(sp.cursor).can_use THEN '--spell that can be used oob DIM lmplev as integer = sp.cursor \ spellsPerLMP DIM atk as AttackData loadattackdata atk, sp.spell(sp.cursor).id '--NOTE: atkallowed isn't needed here because the check is done elswehere... '--still, it would be nice if we could use it anyway... 'IF atkallowed(atk, sp.hero, sp.lists(sp.listnum).magic_type, lmplev) '--repaint the screen so it will show up under the menu attack targ picker spells_menu_paint sp IF menu_attack_targ_picker(sp.spell(sp.cursor).id, -1, sp.hero, TRIM(sp.spell(sp.cursor).name), 36) THEN '--attack was actually used subtract_attack_costs atk, sp.hero, sp.lists(sp.listnum).magic_type, lmplev evalitemtags evalherotags tag_updates spells_menu_refresh_hero sp sp.re_use = YES ELSE menusound gen(genCancelSFX) END IF ELSE menusound gen(genCancelSFX) END IF END IF END SUB SUB spells_menu_control(sp as SpellsMenuState) IF sp.mset = 0 THEN '--picking which spell list spells_menu_control_which_list sp ELSE '--picking a specific spell from a list spells_menu_control_pick_spell sp END IF END SUB SUB old_spells_menu (byval who as integer) DIM sp as SpellsMenuState sp.hero = who sp.listnum = 0 sp.cancel_menu_caption = readglobalstring(51, "(CANCEL)", 10) sp.has_none_caption = readglobalstring(133, "has no spells", 20) spells_menu_refresh_hero sp '--Preserve background for display beneath the spells menu sp.page = compatpage DIM holdscreen as integer = allocatepage copypage sp.page, holdscreen menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys sp.tog = sp.tog XOR 1 playtimer spells_menu_control sp IF sp.quit THEN EXIT DO copypage holdscreen, sp.page spells_menu_paint sp setvispage vpage IF sp.re_use = NO THEN dowait END IF LOOP menusound gen(genCancelSFX) setkeys freepage sp.page freepage holdscreen checkfatal = YES END SUB SUB spells_menu_paint (byref sp as SpellsMenuState) centerfuz 160, 100, 312, 184, 1, sp.page 'outer box centerbox 206, 36, 200, 17, 2, sp.page 'name box centerbox 56, 50, 84, 60, 2, sp.page 'spell lists menu box centerbox 160, 134, 308, 96, 2, sp.page 'spell list rectangle 6, 168, 308, 1, boxlook(1).edgecol, sp.page 'divider 2 'top menu (spell lists) FOR i as integer = 0 TO sp.last textcolor uilook(uiMenuItem), 0 IF sp.listnum = i THEN textcolor uilook(uiSelectedItem + sp.tog), uilook(uiHighlight2): IF sp.mset = 1 THEN textcolor uilook(uiMenuItem), uilook(uiHighlight2) printstr sp.lists(i).name, 16, 25 + i * 10, sp.page 'spell menu NEXT i 'bottom menu (spells in spell list) IF sp.lists(sp.listnum).menu_index >= 0 THEN FOR o as integer = 0 TO 23 IF sp.cursor = o AND sp.mset = 1 THEN IF sp.spell(o).can_use THEN textcolor uilook(uiSelectedItem + sp.tog), uilook(uiHighlight) ELSE textcolor uilook(uiMenuItem), uilook(uiHighlight) END IF ELSE IF sp.spell(o).can_use THEN textcolor uilook(uiMenuItem), 0 ELSE textcolor uilook(uiDisabledItem), 0 END IF END IF printstr sp.spell(o).name, 12 + (o MOD 3) * 104, 90 + (o \ 3) * 8, sp.page 'spells NEXT o IF sp.mset = 1 THEN textcolor uilook(uiMenuItem), 0 IF sp.cursor = 24 AND sp.mset = 1 THEN textcolor uilook(uiSelectedItem + sp.tog), uilook(uiHighlight) printstr sp.cancel_menu_caption, 9, 171, sp.page 'cancel IF sp.spell(sp.cursor).desc <> "" THEN rectangle 6, 155, 308, 1, boxlook(2).edgecol, sp.page 'description divider END IF textcolor uilook(uiDescription), 0 DIM cost_caption as string = RIGHT(sp.spell(sp.cursor).cost, 30) printstr cost_caption, 311 - LEN(cost_caption) * 8, 171, sp.page 'cost printstr sp.spell(sp.cursor).desc, 9, 158, sp.page 'description END IF END IF IF sp.last = 0 THEN edgeprint gam.hero(sp.hero).name & " " & sp.has_none_caption, pCentered, 120, uilook(uiText), sp.page edgeprint gam.hero(sp.hero).name, ancCenter + 206, 31, uilook(uiText), sp.page END SUB '----------------------------------------------------------------------- SUB get_virtual_keyboard_buttons (byval sl as Slice Ptr, byref arr as any ptr vector) IF sl = 0 THEN debug "get_virtual_keyboard_buttons null ptr": EXIT SUB DIM ch as Slice Ptr = sl->FirstChild DO WHILE ch IF ch->Lookup = SL_VIRTUAL_KEYBOARD_BUTTON THEN v_append arr, ch ELSEIF ch->Lookup = SL_VIRTUAL_KEYBOARD_ENTER THEN v_append arr, ch ELSEIF ch->Lookup = SL_VIRTUAL_KEYBOARD_DEL THEN v_append arr, ch ELSEIF ch->Lookup = SL_VIRTUAL_KEYBOARD_SHIFT THEN v_append arr, ch ELSEIF ch->Lookup = SL_VIRTUAL_KEYBOARD_SYMBOLS THEN v_append arr, ch ELSEIF ch->Lookup = SL_VIRTUAL_KEYBOARD_SELECT THEN 'If this is the select, only explore the active child get_virtual_keyboard_buttons SliceChildByIndex(ch, ch->SelectData->index), arr ELSE IF ch->NumChildren > 0 THEN get_virtual_keyboard_buttons ch, arr END IF END IF ch = ch->NextSibling LOOP END SUB SUB virtual_keyboard_refresh_array (byref st as VirtualKeyboardScreenState) v_new st.arr get_virtual_keyboard_buttons st.sl, st.arr END SUB SUB virtual_keyboard_button_defocus (byval button as Slice Ptr) IF button THEN IF button->Lookup = SL_VIRTUAL_KEYBOARD_ENTER THEN ChangeRectangleSlice button, , uiLook(uiHighlight2) ELSEIF button->Lookup = SL_VIRTUAL_KEYBOARD_DEL THEN ChangeRectangleSlice button, , uiLook(uiSelectedDisabled) ELSEIF button->Lookup = SL_VIRTUAL_KEYBOARD_SHIFT THEN ChangeRectangleSlice button, , uiLook(uiSelectedDisabled) ELSEIF button->Lookup = SL_VIRTUAL_KEYBOARD_SYMBOLS THEN ChangeRectangleSlice button, , uiLook(uiSelectedDisabled) ELSE ChangeRectangleSlice button, , uiLook(uiMenuItem) END IF END IF END SUB SUB virtual_keyboard_button_focus (byval button as Slice Ptr) IF button THEN IF button->Lookup = SL_VIRTUAL_KEYBOARD_ENTER THEN ChangeRectangleSlice button, , uiLook(uiHighlight) ELSEIF button->Lookup = SL_VIRTUAL_KEYBOARD_DEL THEN ChangeRectangleSlice button, , uiLook(uiDisabledItem) ELSEIF button->Lookup = SL_VIRTUAL_KEYBOARD_SHIFT THEN ChangeRectangleSlice button, , uiLook(uiDisabledItem) ELSEIF button->Lookup = SL_VIRTUAL_KEYBOARD_SYMBOLS THEN ChangeRectangleSlice button, , uiLook(uiDisabledItem) ELSE ChangeRectangleSlice button, , uiLook(uiDisabledItem) END IF END IF END SUB FUNCTION virtual_keyboard_button_at_pos (byref st as VirtualKeyboardScreenState, byval pos as XYPair) as Slice Ptr DIM button as Slice Ptr FOR i as integer = 0 TO v_len(st.arr) - 1 button = st.arr[i] IF SliceCollidePoint(button, pos) THEN RETURN button END IF NEXT i RETURN 0 END FUNCTION SUB virtual_keyboard_push_button (byref st as VirtualKeyboardScreenState, byval button as Slice Ptr) IF button = 0 THEN EXIT SUB menusound gen(genCursorSFX) SELECT CASE button->Lookup CASE SL_VIRTUAL_KEYBOARD_BUTTON: IF st.max_length = -1 ORELSE LEN(st.result) < st.max_length THEN DIM txt as Slice Ptr txt = LookupSlice(SL_VIRTUAL_KEYBOARD_BUTTONTEXT, button) IF txt THEN DIM dat as TextSliceData Ptr = txt->SliceData st.result &= dat->s END IF END IF CASE SL_VIRTUAL_KEYBOARD_DEL: IF LEN(st.result) > 0 THEN st.result = LEFT(st.result, LEN(st.result) - 1) CASE SL_VIRTUAL_KEYBOARD_ENTER: st.done = YES CASE SL_VIRTUAL_KEYBOARD_SHIFT: IF st.select_sl THEN st.shift = NOT st.shift IF st.shift THEN st.symbols = NO IF st.shift THEN ChangeSelectSlice st.select_sl, 1 ELSE ChangeSelectSlice st.select_sl, 0 END IF virtual_keyboard_refresh_array st END IF CASE SL_VIRTUAL_KEYBOARD_SYMBOLS: IF st.select_sl THEN st.symbols = NOT st.symbols IF st.symbols THEN st.shift = NO IF st.symbols THEN ChangeSelectSlice st.select_sl, 2 ELSE ChangeSelectSlice st.select_sl, 0 END IF virtual_keyboard_refresh_array st END IF END SELECT END SUB SUB virtual_keyboard_display_refresh (byref st as VirtualKeyboardScreenState) IF st.entry_sl THEN ChangeTextSlice st.entry_sl, st.result DIM button as Slice Ptr FOR i as integer = 0 TO v_len(st.arr) - 1 button = st.arr[i] IF st.pushed_sl <> 0 ANDALSO st.pushed_sl = button THEN virtual_keyboard_button_focus button ELSE virtual_keyboard_button_defocus button END IF NEXT i END SUB FUNCTION touch_virtual_keyboard (default_str as string, max_length as integer=-1, prompt as string="") as string DIM st as VirtualKeyboardScreenState st.result = default_str st.max_length = max_length st.prompt = prompt st.sl = LoadSliceCollection(SL_COLLECT_VIRTUALKEYBOARDSCREEN) ERROR_IF(st.sl = NULL, "Couldn't load slices!", st.result) expand_slice_text_insert_codes st.sl, @ExpandTextVirtualKeyboard, @st virtual_keyboard_refresh_array st st.entry_sl = LookupSlice(SL_VIRTUAL_KEYBOARD_ENTRYTEXT, st.sl) IF st.entry_sl = 0 THEN visible_debug "touch_virtual_keyboard: no text slice has SL_VIRTUAL_KEYBOARD_ENTRYTEXT lookup code" END IF st.select_sl = LookupSlice(SL_VIRTUAL_KEYBOARD_SELECT, st.sl) menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys IF readmouse.buttons AND mouseLeft THEN IF st.pushed_sl = 0 THEN IF NOT st.drag_off THEN st.pushed_sl = virtual_keyboard_button_at_pos(st, readmouse.pos) END IF ELSE IF st.pushed_sl <> 0 ANDALSO st.pushed_sl <> virtual_keyboard_button_at_pos(st, readmouse.pos) THEN st.drag_off = YES st.pushed_sl = 0 END IF END IF ELSE st.drag_off = NO IF st.pushed_sl <> 0 THEN virtual_keyboard_push_button st, st.pushed_sl IF st.done THEN EXIT DO END IF st.pushed_sl = 0 END IF 'IF game_check_cancel_key() THEN EXIT DO 'IF game_check_use_key() THEN 'END IF IF keyval(scF8) > 1 THEN slice_editor st.sl END IF virtual_keyboard_display_refresh st 'Draw the screen clearpage vpage DrawSlice st.sl, vpage setvispage vpage dowait LOOP menusound gen(genAcceptSFX) v_free st.arr DeleteSlice @(st.sl) RETURN st.result END FUNCTION FUNCTION game_check_use_key() as bool IF prefbit(58) THEN 'no key repeats RETURN (carray(ccUse) AND 4) > 0 ELSE 'allow key repeats RETURN carray(ccUse) > 1 END IF END FUNCTION FUNCTION game_battle_check_use_key() as bool IF prefbit(58) ANDALSO NOT prefbit(59) THEN 'no key repeats RETURN (carray(ccUse) AND 4) > 0 ELSE 'allow key repeats RETURN carray(ccUse) > 1 END IF END FUNCTION FUNCTION game_check_cancel_key() as bool IF prefbit(58) THEN 'no key repeats RETURN (carray(ccCancel) AND 4) > 0 ELSE 'allow key repeats RETURN carray(ccCancel) > 1 END IF END FUNCTION FUNCTION game_check_menu_key() as bool IF prefbit(58) THEN 'no key repeats RETURN (carray(ccMenu) AND 4) > 0 ELSE 'allow key repeats RETURN carray(ccMenu) > 1 END IF END FUNCTION