'OHRRPGCE GAME - Mostly user-interface related routines '(C) Copyright 1997-2005 James Paige and Hamster Republic Productions 'Please read LICENSE.txt for GPL License details and disclaimer of liability 'See README.txt for code docs and apologies for crappyness of this code ;) #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, buyst as ShopBuyState) 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, menu() as string) DECLARE SUB equip_menu_do_equip(byval item as integer, byref st as EquipMenuState, menu() as string) DECLARE SUB equip_menu_back_to_menu(byref st as EquipMenuState, menu() as string) 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, eqstuf() as integer) 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 integer=YES) as integer 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, 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, 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 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) 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 '--Preserve the screen underneath DIM page as integer DIM holdscreen as integer page = compatpage holdscreen = allocatepage copypage page, holdscreen '--walking frame numbers used for animating heroes DIM walks(15) as integer FOR i as integer = 0 TO 10 STEP 2 walks(i) = 1 NEXT i walks(11) = 2 walks(12) = 2 walks(13) = 3 walks(14) = 3 buystuff_make_slices buyst '--Create the menu DIM menu as MenuDef ClearMenuData menu DIM st as MenuState st.active = YES st.need_update = YES show_virtual_gamepad() menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys buyst.tog = buyst.tog XOR 1 IF buyst.tog THEN buyst.walk = loopvar(buyst.walk, 0, 15, 1) IF st.need_update THEN buystuff_refresh_menu buyst, menu, st st.need_update = NO END IF playtimer control usemenusounds IF usemenu(st) THEN IF menu.numitems > 0 THEN buystuff_refresh_selected buyst, menu.items[st.pt]->dataptr END IF END IF WITH *(buyst.alert_box) IF .extra(0) > 0 THEN .extra(0) -= 1 IF .extra(0) <= 0 THEN .Visible = NO END IF END IF END WITH 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 carray(ccMenu) > 1 THEN EXIT DO IF menu.numitems > 0 THEN IF carray(ccUse) > 1 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 END IF '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, page DrawSlice buyst.root_sl, page draw_menu buyst.info, buyst.info_st, page draw_menu menu, st, page setvispage vpage dowait LOOP '--Restore the saved screen freepage page freepage holdscreen DeleteSlice @(buyst.root_sl) menusound gen(genCancelSFX) evalitemtags party_change_updates 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 + 1, 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 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) & " " & gold & " " & readglobalstring(32, "$", 10) 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 itembuf(49) > 0 THEN 'This item is equippable buyst.hero_box->Visible = YES DIM eqprefix as string = readglobalstring(99, "Equip:", 10) IF itembuf(49) = 1 THEN append_menu_item buyst.info, eqprefix & " " & readglobalstring(38, "Weapon", 10) IF itembuf(49) > 1 THEN append_menu_item buyst.info, eqprefix & " " & readglobalstring(23 + itembuf(49), "Armor" & itembuf(49)-1) 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 & " " & readglobalstring(32, "$", 10) 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 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) & " " & gold & " " & readglobalstring(32, "$", 10) 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 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 - 160 + buyst.info_sl->Width / 2 .offset.y = buyst.info_sl->ScreenY - 102 .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 FUNCTION buystuff_can_show(byval slot as NodePtr, buyst as ShopBuyState) 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 'There's no real need to provide a default for stockidx, it should always exist DIM stockidx as integer = thing."stockidx".default(GetInteger(slot)) 'Load stock data if uninitialised. IF gam.stock(buyst.shop_id, stockidx) = 0 THEN IF thing."stock".exists THEN gam.stock(buyst.shop_id, stockidx) = thing."stock" + 1 END IF END IF IF gam.stock(buyst.shop_id, stockidx) = 1 THEN 'This item is out of stock RETURN NO END IF IF buy."require_tag".exists THEN IF NOT istag(tag(), buy."require_tag".integer, 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 herocount(3) >= 4 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 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) 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 = 0 'item 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 = 1 'hero 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 = 2 .AlignHoriz = 2 .Width = 168 .Height = 176 .PaddingTop = 25 END WITH DIM your_money_box as Slice Ptr your_money_box = NewSliceOfType(slRectangle, right_panel) WITH *your_money_box .AnchorHoriz = 1 .AlignHoriz = 1 .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 = 1 .AnchorVert = 1 .AlignHoriz = 1 .AlignVert = 1 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 = 2 .AlignVert = 2 .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 = 1 .AlignVert = 1 .AnchorHoriz = 1 .AlignHoriz = 1 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 = 2 .AlignVert = 2 .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 = 1 .AlignVert = 1 .AnchorHoriz = 1 .AlignHoriz = 1 END WITH ChangeSpriteSlice buyst.hire_sl, sprTypeHero buyst.portrait_box = NewSliceOfType(slRectangle, right_panel) WITH *(buyst.portrait_box) .AnchorVert = 2 .AlignVert = 2 .AnchorHoriz = 2 .AlignHoriz = 2 .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 = 1 .AlignVert = 1 .AnchorHoriz = 1 .AlignHoriz = 1 END WITH ChangeSpriteSlice buyst.portrait_sl, sprTypePortrait buyst.price_box = NewSliceOfType(slRectangle, buyst.root_sl) WITH *(buyst.price_box) .AnchorHoriz = 1 .AlignHoriz = 1 .AnchorVert = 2 .AlignVert = 2 .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 = 1 .AlignVert = 1 .AnchorHoriz = 1 .AlignHoriz = 1 .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 = 1 .AlignHoriz = 1 .AnchorVert = 2 .AlignVert = 2 .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 = 1 .AlignVert = 1 .AnchorHoriz = 1 .AlignHoriz = 1 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 .extra(0) = ticks .Width = buyst.alert_sl->Width + 16 END WITH END SUB FUNCTION chkOOBtarg (byval target as integer, byval atk as integer) as integer 'true if valid, false if not valid 'atk id can be -1 for when no attack is relevant IF target < 0 OR target > 40 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 IF hp = 0 AND (attack.targ_class = 4 OR attack.targ_class = 10) THEN RETURN YES IF hp > 0 AND attack.targ_class = 10 THEN RETURN NO END IF IF hp = 0 THEN RETURN NO 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 SUB doequip (byval toequip as integer, byval who as integer, byval where as integer, byval defwep as integer) IF eqstuf(who, where) THEN debugc errPromptBug, "doequip called without unequipping first" '--set equipment eqstuf(who, where) = toequip update_hero_max_and_cur_stats who '--special handling for weapons IF where = 0 THEN DIM itembuf(dimbinsize(binITM)) as integer loaditemdata itembuf(), toequip - 1 gam.hero(who).wep_pic = itembuf(52) 'remember weapon pic gam.hero(who).wep_pal = itembuf(53) 'remember weapon pal END IF '--equipping the default weapon does not delete it from inventory IF toequip = defwep AND where = 0 THEN ELSE '--delete the item from inventory delitem toequip - 1 END IF evalitemtags evalherotags 'You could kill someone, right? tag_updates END SUB FUNCTION getOOBtarg (byval search_direction as integer, byref target as integer, byval atk as integer, byval recheck as integer=NO) as integer '--return true on success, false on failure '--atk id can be -1 for when no attack is relevant IF recheck THEN target -= 1 ' For a re-check, back the cursor up so if the current target is still valid, it won't change DIM safety as integer = 0 DO target = loopvar(target, 0, 3, search_direction) IF chkOOBtarg(target, atk) THEN RETURN YES safety += 1 IF safety >= 4 THEN EXIT DO LOOP 'Failure target = -1 RETURN NO END FUNCTION SUB update_inventory_caption (byval i as integer) 'WARNING: this can't be removed until all three of the Items, Equip, and Battle items menus have been re-written IF inventory(i).used = 0 THEN inventory(i).text = SPACE(11) ELSE inventory(i).text = readitemname(inventory(i).id) inventory(i).text = rpad(inventory(i).text, " ", 8) + CHR(1) + RIGHT(" " & inventory(i).num, 2) END IF 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 = statMP IF readbit(gen(), genBits2, 15) = 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) FOR statnum as integer = 2 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 = 2 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(scEsc) > 1 THEN EXIT DO IF keyval(scUp) > 1 THEN csr = large(0, csr - 1) IF keyval(scDown) > 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 ' 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 no saves available/New Game ' -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 ' -1: 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. ' See pickload for other argument documentation. ' Return value: ' 0 or higher: save slot ' -1 no saves available/New Game (loading only) or cancelled (saving only) ' -2 cancelled/Exit (loading only) show_virtual_gamepad() DIM ret_slot as integer DIM max_slot as integer = 31 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 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). '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 allow as integer 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 IF loading THEN 'check for no slots DIM nofull as integer = YES FOR i as integer = 0 TO last_slot IF pv(i).valid THEN nofull = NO NEXT i IF nofull = YES THEN ret_slot = -1 IF beep_if_no_saves THEN MenuSound gen(genCancelSFX) GOTO freesprites END IF END IF '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 control IF carray(ccMenu) > 1 THEN MenuSound gen(genCancelSFX) IF loading THEN ret_slot = -2 ELSE ret_slot = -1 EXIT DO 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 st.pt < 0 AND newgame_opt THEN IF carray(ccLeft) > 1 THEN st.pt = -1: MenuSound gen(genCursorSFX) IF carray(ccRight) > 1 THEN st.pt = -2: MenuSound gen(genCursorSFX) END IF IF carray(ccUse) > 1 THEN IF newgame_opt AND st.pt = -1 THEN 'Selected "New Game" MenuSound gen(genAcceptSFX) ret_slot = st.pt EXIT DO ELSEIF st.pt < 0 THEN 'Selected "Cancel" or "Exit" MenuSound gen(genCancelSFX) IF loading THEN ret_slot = -2 ELSE ret_slot = -1 EXIT DO ELSE allow = 1 IF loading THEN '--normal load of an existing save IF pv(st.pt).valid = 0 THEN allow = 0 ELSE '--normal save in a slot IF pv(st.pt).valid THEN allow = picksave_confirm(menu(), loading, newgame_opt, sprites(), pv(), mapname(), lev(), st, holdscreen, page) END IF IF allow = 1 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, sprites(), pv(), mapname(), lev(), st, page setvispage vpage check_for_queued_fade_in dowait LOOP freesprites: freepage page freepage holdscreen FOR t as integer = 4 TO 5: carray(t) = 0: NEXT t 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 whether confirmed FUNCTION picksave_confirm(menu() as string, byval loading as bool, byval newgame_opt as bool, 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 integer 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 MenuSound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer control IF carray(ccMenu) > 1 THEN MenuSound gen(genCancelSFX) RETURN NO END IF IF carray(ccUse) > 1 THEN RETURN state.pt usemenu state usemenusounds copypage holdscreen, page picksave_draw menu(), loading, newgame_opt, 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(uiSelectedItem + tog) IF state.pt = i THEN col = uilook(uiMenuItem) 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, 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 walk as integer tog = tog XOR 1 walk = walk XOR tog DIM last_slot as integer = UBOUND(pv) centerbox 50, 11, 80, 14, 1 + 14, page IF newgame_opt 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) IF pv(i).valid THEN yoff = (i - top) * 44 FOR o as integer = 0 TO 3 IF sprites(i, o).sprite THEN frame_draw sprites(i, o).sprite + iif(st.pt = i, walk, 0), sprites(i, o).pal, 140 + (o * 42), 24 + yoff, 1, -1, page END IF NEXT o col = uilook(uiMenuItem) IF st.pt = i THEN col = uilook(uiSelectedItem + tog) 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 NEXT i col = uilook(IIF(st.pt = -1, uiSelectedItem + tog, uiMenuItem)) edgeprint menu(0), ancCenter + 50, 6, col, page IF newgame_opt THEN 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 sellstuff (byval id as integer, storebuf() as integer) DIM b((getbinsize(binSTF) \ 2) * 50 - 1) as integer DIM permask(15) as integer DIM price((inventoryMax + 1) \ 3) 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) FOR slot as integer = 0 TO storebuf(16) DIM stockidx as integer = b(slot * recordsize + 37) - 1 IF gam.stock(id, stockidx) = 0 THEN DIM stockamount as integer = b(stockidx * recordsize + 19) gam.stock(id, stockidx) = IIF(stockamount < 0, stockamount, stockamount + 1) END IF 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 quit as bool = NO menusound gen(genAcceptSFX) sellstuff_refresh storebuf(16), recordsize, permask(), price(), b() sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize show_virtual_gamepad() setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer control IF carray(ccMenu) > 1 THEN quit = YES IF carray(ccUse) > 1 AND 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 IF b(i * recordsize + 26) = 2 AND gam.stock(id, stockidx) > 0 THEN gam.stock(id, stockidx) += 1 END IF END IF NEXT i 'DECREMENT ITEM----------- consumeitem ic 'UPDATE ITEM POSESSION TAGS-------- evalitemtags '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 carray(ccUp) > 1 AND ic >= 3 THEN menusound gen(genCursorSFX) ic = ic - 3 sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize IF ic < top THEN top = top - 3 END IF IF carray(ccDown) > 1 AND ic <= last_inv_slot() - 3 THEN menusound gen(genCursorSFX) ic = ic + 3 sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize IF ic > top + 62 THEN top = top + 3 END IF IF carray(ccLeft) > 1 THEN menusound gen(genCursorSFX) IF ic MOD 3 > 0 THEN ic = ic - 1 sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize ELSE ic = ic + 2 sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize END IF END IF IF carray(ccRight) > 1 THEN menusound gen(genCursorSFX) IF ic MOD 3 < 2 THEN ic = ic + 1 sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize ELSE ic = ic - 2 sellstuff_infostr ic, info, b(), permask(), price(), storebuf(16), recordsize END IF END IF IF quit THEN EXIT DO '--Draw the sell screen 'FIXME: Some of this is redundant to the inventory display code. ' figure out how much can reasonably be re-used copypage holdscreen, page centerbox 160, 92, 304, 176, 1, page FOR i as integer = top TO top + 62 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 printstr inventory(i).text, 20 + 96 * (i MOD 3), 12 + 8 * ((i - top) \ 3), page NEXT i centerfuz 160, 180, 312, 20, 4, page edgeprint info, pCenteredLeft, 175, uilook(uiText), page edgeprint gold & " " & readglobalstring(32, "$"), 310 - LEN(gold & " " & readglobalstring(32, "$")) * 8, 1, uilook(uiGold), page IF alert THEN alert = alert - 1 centerbox 160, 178, LEN(alert_str) * 8 + 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 EXIT SUB 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(ic) & " " & readglobalstring(32, "$") 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 = info & " " & readglobalstring(153, "and", 10) & " " ELSE info = info & " " & readglobalstring(81, "and a", 10) & " " END IF END IF IF b(i * recordsize + 29) > 0 THEN info = info & STR(b(i * recordsize + 29) + 1) & " " info = 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 hero_node = CAST(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 = STR(gold) CASE "MONEYLABEL": result = readglobalstring(32, "$", 10) 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(lmp(hero_slot, 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 DIM info as string = readitemname(id) & CHR(1) & lpad(STR(inventory(swapslot).num), " ", 2) 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 = CHR(1) & lpad(STR(inventory(slot).num), " ", 2) 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_MP, (NOT hero_has_mp(st.slot) ORELSE should_hide_hero_stat(her, 1)) 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, SL_COLLECT_STATUSSTATPLANK, @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 st.sl = NewSliceOfType(slSpecial) st.sl->Fill = YES RefreshSliceScreenPos st.sl DIM holdscreen as integer = duplicatepage(vpage) load_slice_collection st.sl, SL_COLLECT_STATUSSCREEN status_screen_refresh st show_virtual_gamepad() menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys playtimer control IF carray(ccMenu) > 1 THEN EXIT DO IF carray(ccUse) > 1 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 carray(ccMenu) > 1 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 flusharray carray(), 7 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 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 plank_menu_clear st.ps.m, SL_ITEM_ITEMLIST DIM collection as Slice Ptr = NewSliceOfType(slSpecial) load_slice_collection collection, SL_COLLECT_ITEMPLANK IF collection = 0 THEN debug "item_screen_refresh: plank collection not found": EXIT SUB DIM pl as Slice Ptr FOR i as integer = 0 TO last_inv_slot() pl = plank_menu_append(st.ps.m, SL_ITEM_ITEMLIST, collection, @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 DeleteSlice @collection '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 FUNCTION item_can_be_used (byval item_id as integer) as bool DIM itemtemp(dimbinsize(binITM)) as integer loaditemdata itemtemp(), item_id IF itemtemp(50) > 0 THEN RETURN YES 'Teaches a spell IF itemtemp(51) > 0 THEN RETURN YES 'Triggers an attack (cure) IF itemtemp(51) < 0 THEN RETURN YES 'Triggers a text box RETURN NO 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 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(item_id) 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 END IF END IF st.need_update = YES st.refocus = YES st.swapcur = 0 END IF END SUB 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 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.m = NewSliceOfType(slSpecial) st.ps.m->Fill = YES RefreshSliceScreenPos st.ps.m st.ps.is_plank_callback = @is_item_plank st.ps.state_callback = @set_item_plank_state DIM holdscreen as integer = duplicatepage(vpage) load_slice_collection st.ps.m, SL_COLLECT_ITEMSCREEN item_screen_refresh st show_virtual_gamepad() menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys playtimer control IF carray(ccMenu) > 1 THEN item_screen_cancel_action st END IF IF carray(ccUse) > 1 ORELSE st.re_use THEN item_screen_use_action 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 DIM slot as integer = -1 IF st.ps.cur THEN slot = st.ps.cur->Extra(0) 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 flusharray carray(), 7, 0 RETURN st.textbox END FUNCTION SUB inventory_autosort() DIM autosort_changed as integer = NO 'First sort all items to the top FOR i as integer = 0 TO last_inv_slot() - 1 IF inventory(i).used THEN CONTINUE FOR FOR o as integer = i + 1 TO last_inv_slot() IF inventory(o).used THEN SWAP inventory(i), inventory(o) 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 last_inv_slot() IF inventory(slot).used = NO THEN CONTINUE FOR 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 itemdata(49) THEN 'Equippable inventory(slot).sortorder = 40 + itemdata(49) 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 END IF 'Then sort by the autosort criterion (insertion sort) FOR i as integer = 0 TO last_inv_slot() - 1 IF inventory(i).used = NO THEN EXIT FOR DIM best as integer = i FOR o as integer = i TO last_inv_slot() IF inventory(o).used = NO THEN EXIT FOR SELECT CASE gen(genAutosortScheme) CASE 0 'type IF inventory(best).sortorder > inventory(o).sortorder THEN best = o CASE 1 'use 'FIXME: This comparison could be slow IF item_can_be_used(inventory(best).id) = NO ANDALSO item_can_be_used(inventory(o).id) = YES THEN best = o CASE 2 'alphabetical IF string_compare(@inventory(best).text, @inventory(o).text) > 0 THEN best = o CASE 3 'id IF inventory(best).id > inventory(o).id THEN best = o CASE 4 'nothing END SELECT NEXT o IF best <> i THEN SWAP inventory(i), inventory(best) autosort_changed = YES END IF 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 collection as Slice Ptr = NewSliceOfType(slSpecial) 'load_slice_collection collection, SL_COLLECT_ITEMPLANK 'IF collection = 0 THEN debug "item_screen_refresh: plank collection not found": EXIT SUB ' ' DIM pl as Slice Ptr ' FOR i as integer = 0 TO last_inv_slot() ' pl = plank_menu_append(st.ps.m, SL_ITEM_ITEMLIST, collection, @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 ' ' DeleteSlice @collection ' ' '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 st.ps.m = NewSliceOfType(slSpecial) st.ps.m->Fill = YES RefreshSliceScreenPos st.ps.m DIM holdscreen as integer = duplicatepage(vpage) load_slice_collection st.ps.m, SL_COLLECT_SPELLSCREEN spell_screen_refresh st show_virtual_gamepad() menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys playtimer control IF carray(ccMenu) > 1 THEN EXIT DO IF carray(ccUse) > 1 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 spell(who, j, o) = 0 AND her.spell_lists(j,o).attack = atk AND her.spell_lists(j,o).learned = 0 THEN spell(who, j, o) = atk result = YES END IF NEXT o NEXT j RETURN result END FUNCTION SUB unequip (byval who as integer, byval where as integer, byval defwep as integer, byval resetdw as integer) '--exit if nothing is equiped IF eqstuf(who, where) = 0 THEN EXIT SUB '--return item to inventory (if not the default weapon) IF where = 0 AND eqstuf(who, where) = defwep THEN ELSE getitem eqstuf(who, where) - 1 END IF '--blank out equipment eqstuf(who, where) = 0 update_hero_max_and_cur_stats who IF where = 0 AND resetdw THEN '--restore default weapon doequip defwep, who, where, defwep END IF evalitemtags evalherotags 'You could kill someone, right? tag_updates END SUB 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 spell(who, list, i) > 0 THEN n + = 1 NEXT i RETURN n END FUNCTION FUNCTION outside_battle_cure (byval atk as integer, byref target as integer, byval attacker as integer, byval spread as integer) as integer DIM i as integer DIM didcure as integer = NO IF spread = NO THEN IF chkOOBtarg(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(i, atk) THEN target_count += 1 NEXT i FOR i = 0 TO 3 IF chkOOBtarg(i, atk) THEN oobcure attacker, i, atk, target_count didcure = YES END IF NEXT i END IF IF didcure THEN 're-check validify of target getOOBtarg 1, target, atk, YES END IF checkfatal = YES evalherotags evalitemtags tag_updates RETURN didcure END FUNCTION 'Equip menu. 'who is party slot. 'allow_switch is whether to allow switching left/right to other heroes SUB equip_menu (who as integer, allow_switch as bool = YES) DIM m(4) as string DIM menu(6) as string DIM page as integer = compatpage DIM holdscreen as integer = allocatepage DIM st as EquipMenuState DIM i as integer DIM tog as integer = 0 DIM item_id as integer DIM item_info as string '--get names m(0) = readglobalstring(38, "Weapon", 10) FOR i = 0 TO 3 m(i + 1) = readglobalstring(25 + i, "Armor" & i+1) NEXT i menu(5) = rpad(readglobalstring(39, "-REMOVE-", 8), " ", 8) menu(6) = rpad(readglobalstring(40, "-EXIT-", 8), " ", 8) '--initialize WITH st .mode = 0 .who = who .eq_cursor.size = 17 .default_weapon = 0 .default_weapon_name = "" .unequip_caption = rpad(readglobalstring(110, "Nothing", 10), " ", 11) END WITH equip_menu_setup st, menu() item_info = equip_menu_equipped_item_info(st, eqstuf()) '--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 control IF st.mode = 0 THEN '--primary menu IF carray(ccMenu) > 1 THEN carray(ccUse) = 0 carray(ccMenu) = 0 EXIT DO END IF IF allow_switch THEN IF carray(ccLeft) > 1 THEN 'Left: previous hero st.who = loop_active_party_slot(st.who, -1) equip_menu_setup st, menu() MenuSound gen(genCursorSFX) item_info = equip_menu_equipped_item_info(st, eqstuf()) END IF IF carray(ccRight) > 1 THEN 'Right: next hero st.who = loop_active_party_slot(st.who, 1) equip_menu_setup st, menu() MenuSound gen(genCursorSFX) item_info = equip_menu_equipped_item_info(st, eqstuf()) END IF END IF usemenusounds IF usemenu(st.slot, 0, 0, 6, 6) THEN item_info = equip_menu_equipped_item_info(st, eqstuf()) END IF IF carray(ccUse) > 1 THEN IF st.slot < 5 THEN '--change equipment IF st.eq(st.slot).count > 0 OR eqstuf(st.who, st.slot) > 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) 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 unequip st.who, i, st.default_weapon, 1 NEXT i equip_menu_setup st, menu() 'UPDATE ITEM POSSESSION BITSETS evalitemtags END IF IF st.slot = 6 THEN carray(ccUse) = 0: EXIT DO END IF ELSE '--change equip menu IF carray(ccMenu) > 1 THEN st.mode = 0 MenuSound gen(genCancelSFX) item_info = equip_menu_equipped_item_info(st, eqstuf()) END IF usemenusounds IF usemenu(st.eq_cursor) THEN equip_menu_stat_bonus st item_info = equip_menu_available_item_info(st) END IF IF carray(ccUse) > 1 THEN IF st.eq_cursor.pt = st.eq(st.slot).count THEN '--unequip unequip st.who, st.slot, st.default_weapon, 1 equip_menu_back_to_menu st, menu() MenuSound gen(genCancelSFX) ELSE '--normal equip item_id = inventory(st.eq(st.slot).offset(st.eq_cursor.pt)).id equip_menu_do_equip item_id + 1, st, menu() MenuSound gen(genAcceptSFX) END IF END IF END IF '--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 - LEN(gam.hero(st.who).name) * 4, 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) 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 eqstuf(st.who, i) = 0 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 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 item_info, pCenteredLeft, 187, uilook(uiText), page setvispage vpage dowait LOOP 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 st.default_weapon - 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, eqstuf() as integer) as string IF st.slot < 5 ANDALSO eqstuf(st.who, st.slot) > 0 THEN RETURN readitemdescription(eqstuf(st.who, st.slot) - 1) END IF 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, menu() as string) loadherodata st.hero, gam.hero(st.who).id st.default_weapon = gam.hero(st.who).def_wep st.default_weapon_name = rpad(readitemname(st.default_weapon - 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 menu(i) = " " IF eqstuf(st.who, i) > 0 THEN menu(i) = rpad(readitemname(eqstuf(st.who, i) - 1), " ", 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 DIM eq_slot as integer = 0 FOR i as integer = 0 TO last_inv_slot() IF inventory(i).used THEN '--load item data loaditemdata itembuf(), inventory(i).id eq_slot = itembuf(49) - 1 IF eq_slot >= 0 THEN '--if this item is equipable IF item_read_equipbit(itembuf(), gam.hero(st.who).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 END IF NEXT i END SUB SUB equip_menu_do_equip(byval item as integer, byref st as EquipMenuState, menu() as string) unequip st.who, st.slot, st.default_weapon, 0 doequip item, st.who, st.slot, st.default_weapon equip_menu_back_to_menu st, menu() END SUB SUB equip_menu_back_to_menu(byref st as EquipMenuState, menu() as string) st.mode = 0 equip_menu_setup st, menu() 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 IF eqstuf(st.who, st.slot) > 0 THEN loaditemdata itembuf(), eqstuf(st.who, st.slot) - 1 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 integer '--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. 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) & "x 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 integer '--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. 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 integer=YES) as integer 'Lets the player pick a target, and then performs an attack or teaches a spell '(FIXME: should move to separate function!) '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 '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 STATIC spread as integer 'boolean '--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 wtogl 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 integer = NO DIM must_spread as integer = NO 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(targ, attack_id) = NO THEN targ = -1 FOR i as integer = 0 TO 3 IF chkOOBtarg(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 setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 wtogl = loopvar(wtogl, 0, 3, 1) playtimer control '--handle keys IF carray(ccMenu) > 1 THEN menusound gen(genCancelSFX) EXIT DO END IF IF spread = NO THEN IF carray(ccUp) > 1 THEN getOOBtarg -1, targ, attack_id MenuSound gen(genCursorSFX) END IF IF carray(ccDown) > 1 THEN getOOBtarg 1, targ, attack_id MenuSound gen(genCursorSFX) END IF END IF IF allow_spread THEN IF carray(ccLeft) > 1 OR carray(ccRight) > 1 THEN MenuSound gen(genCursorSFX) spread = spread XOR YES END IF END IF IF carray(ccUse) > 1 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 page 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 menu_attack_targ_picker = YES END IF END IF EXIT DO END IF '--done using attack '--draw the targ picker menu copypage holdscreen, page centerbox 160 + x_offset, 47, 160, 88, 2, page IF spread = NO AND targ >= 0 THEN rectangle 84 + x_offset, 8 + targ * 20, 152, 20, uilook(uiHighlight2), page ELSEIF spread THEN rectangle 84 + x_offset, 8, 152, 80, uilook(uiHighlight2 * tog), page END IF DIM cater_slot as integer = 0 FOR i as integer = 0 TO 3 IF gam.hero(i).id >= 0 THEN DIM frame as integer = 0 IF targ = i THEN frame = wtogl \ 2 set_walkabout_frame herow(cater_slot).sl, dirDown, frame DrawSliceAt LookupSlice(SL_WALKABOUT_SPRITE_COMPONENT, herow(cater_slot).sl), 89 + x_offset, 8 + i * 20, 20, 20, page, YES col = uilook(uiMenuItem) IF i = targ THEN col = uilook(uiSelectedItem + tog) IF attack_id >= 0 THEN IF atk.targ_stat = 0 or atk.targ_stat = 1 THEN caption = gam.hero(i).stat.cur.sta(atk.targ_stat) & "/" & gam.hero(i).stat.max.sta(atk.targ_stat) & " " & statnames(atk.targ_stat) ELSE caption = gam.hero(i).stat.cur.sta(atk.targ_stat) & " " & statnames(atk.targ_stat) END IF edgeprint caption, 119 + x_offset, 16 + i * 20, col, page ELSEIF learn_id >= 0 THEN edgeprint gam.hero(i).name, 119 + x_offset, 16 + i * 20, col, page END IF cater_slot += 1 END IF NEXT i centerfuz 160 + x_offset, 100, LEN(use_caption) * 8 + 32, 16, 4, page edgeprint use_caption, pCentered + x_offset, 95, uilook(uiText), page setvispage vpage dowait LOOP carray(ccUse) = 0 carray(ccMenu) = 0 freepage page freepage holdscreen END FUNCTION SUB spells_menu_refresh_list(sp as SpellsMenuState) IF sp.lists(sp.listnum).magic_type < 0 THEN EXIT SUB DIM atk as AttackData DIM cost as integer FOR i as integer = 0 TO 23 WITH sp.spell(i) .name = "" .desc = "" .cost = "" .id = -1 .can_use = NO .targt = 0 .tstat = 0 'NOTE: spell() is a global IF spell(sp.hero, sp.lists(sp.listnum).menu_index, i) > 0 THEN .id = spell(sp.hero, sp.lists(sp.listnum).menu_index, i) - 1 loadattackdata atk, .id IF atk.useable_outside_battle THEN .can_use = YES .targt = atk.targ_set .tstat = atk.targ_stat END IF cost = focuscost(atk.mp_cost, gam.hero(sp.hero).stat.cur.focus) 'FIXME: should use the same cost-checking sub that the battle spell menu uses IF sp.lists(sp.listnum).magic_type = 0 AND gam.hero(sp.hero).stat.cur.mp < cost THEN .can_use = NO END IF IF sp.lists(sp.listnum).magic_type = 1 AND lmp(sp.hero, INT(i / 3)) = 0 THEN .can_use = NO END IF IF gam.hero(sp.hero).stat.cur.hp = 0 THEN .can_use = NO END IF .name = atk.name .desc = atk.description .cost = hero_attack_cost_info(atk, sp.hero, sp.lists(sp.listnum).magic_type, INT(i / 3) + 1) 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 slot as integer = 0 DIM listnum as integer DIM hero_node as NodePtr hero_node = CAST(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 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 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 SUB spells_menu_control(sp as SpellsMenuState) IF sp.mset = 0 THEN '--picking which spell list IF carray(ccMenu) > 1 THEN sp.quit = YES : EXIT SUB IF carray(ccLeft) > 1 THEN DO sp.hero = 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 sp.hero = loopvar(sp.hero, 0, 3, 1) 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 IF carray(ccUse) > 1 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 ELSE '--picking a specific spell from a list IF sp.re_use = NO THEN IF carray(ccMenu) > 1 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 carray(ccUse) > 1 OR sp.re_use 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 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, INT(sp.cursor / 3), ) '--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 'FIXME: outside-battle and inside-battle attack cost consumption should be unified '--deduct MP DIM cost as integer cost = focuscost(atk.mp_cost, gam.hero(sp.hero).stat.cur.focus) gam.hero(sp.hero).stat.cur.mp = small(large(gam.hero(sp.hero).stat.cur.mp - cost, 0), gam.hero(sp.hero).stat.max.mp) IF sp.lists(sp.listnum).magic_type = 1 THEN '--deduct LMP lmp(sp.hero, INT(sp.cursor / 3)) = lmp(sp.hero, INT(sp.cursor / 3)) - 1 END IF spells_menu_refresh_hero sp sp.re_use = YES ELSE menusound gen(genCancelSFX) END IF ELSE menusound gen(genCancelSFX) END IF END IF 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) DIM wtogl as integer = 0 setkeys DO setwait speedcontrol setkeys sp.tog = sp.tog XOR 1 wtogl = loopvar(wtogl, 0, 3, 1) playtimer control 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 flusharray carray(), 7 freepage sp.page freepage holdscreen checkfatal = YES evalherotags evalitemtags tag_updates 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 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.mset = 1 THEN 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 DIM dat as SelectSliceData Ptr dat = GetSelectSliceData(ch) get_virtual_keyboard_buttons SliceChildByIndex(ch, dat->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.sl = NewSliceOfType(slSpecial) st.sl->Fill = YES RefreshSliceScreenPos st.sl st.result = default_str st.max_length = max_length st.prompt = prompt load_slice_collection st.sl, SL_COLLECT_VIRTUALKEYBOARDSCREEN 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 control 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 carray(ccMenu) > 1 THEN EXIT DO 'IF carray(ccUse) > 1 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