'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 ;) #ifdef LANG_DEPRECATED #define __langtok #lang __langtok "deprecated" OPTION STATIC OPTION EXPLICIT #endif #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 "game.bi" #include "yetmore.bi" #include "yetmore2.bi" #include "moresubs.bi" #include "menustuf.bi" #include "bmodsubs.bi" #include "bmod.bi" '--SUBs and FUNCTIONS only used locally DECLARE SUB buystuff_loadtrades (buyst as ShopBuyState, byval index as integer, stufbuf() as integer) DECLARE SUB buystuff_setshopstock (buyst as ShopBuyState, stufbuf() as integer) DECLARE SUB buystuff_buildmenu (buyst as ShopBuyState, stuff() as SimpleMenuItem, stufbuf() as integer) DECLARE SUB buystuff_curinfo (buyst as ShopBuyState, stufbuf() as integer, byval selected_stuff as integer, itembuf() as integer) 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_update_equipped_item_info(st as EquipMenuState, eqstuf() as integer) as string DECLARE FUNCTION equip_menu_update_available_item_info(st as EquipMenuState) as string DECLARE SUB items_menu_paint (istate as ItemsMenuState, iuse() as integer, permask() as integer) DECLARE SUB items_menu_infostr(state as ItemsMenuState, permask() as integer) DECLARE SUB items_menu_autosort(iuse() as integer, permask() as integer) DECLARE SUB item_menu_use_item(byval slot as integer, istate as ItemsMenuState, iuse() as integer, permask() as integer) 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 items_menu_control (istate as ItemsMenuState, iuse() as integer, permask() 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 SUB picksave_draw(menu() as string, byval loading as integer, 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 integer, 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 status_buildmenu(her as HeroDef, byval pt as integer, mtype() as integer, elementaldmg() as single, elementnames() as string, elementalmenu() as string, elementalmenu_st as MenuState, portrait as GraphicPair) SUB buystuff (byval shop_id as integer, byval shoptype as integer, storebuf() as integer) DIM buyst as ShopBuyState WITH buyst .shop_id = shop_id .shoptype = shoptype .last_stuff = storebuf(16) .recordsize = curbinsize(binSTF) \ 2 ' get size in INTs END WITH DIM stufbuf((getbinsize(binSTF) \ 2) * 50 - 1) as integer DIM walks(15) as integer DIM itembuf(dimbinsize(binITM)) as integer DIM heroframe as integer DIM heropos as XYPair REDIM stuff(-1 TO -1) as SimpleMenuItem ' .dat of each menu item is item index '--Preserve background for display beneath the buy menu DIM page as integer DIM holdscreen as integer page = compatpage holdscreen = allocatepage copypage page, holdscreen DIM left_panel as RectType = (5, 10, 150, 168) DIM right_panel as RectType = (165, 10, 150, 168) buyst.buytype(0, 0) = readglobalstring(85, "Trade for", 20) + " " buyst.buytype(0, 1) = readglobalstring(87, "Joins for", 20) + " " buyst.buytype(1, 0) = readglobalstring(89, "Cannot Afford", 20) + " " buyst.buytype(1, 1) = readglobalstring(91, "Cannot Hire", 20) + " " buyst.purchased = readglobalstring(93, "Purchased", 20) buyst.joined = readglobalstring(95, "Joined!", 20) buyst.instock = readglobalstring(97, "in stock", 20) DIM slot as integer FOR i as integer = 0 TO 3 buyst.herosprite(i).sprite = frame_load(0, gam.hero(i).battle_pic) IF buyst.herosprite(i).sprite = 0 THEN debug "Couldn't load hero sprite: " & game & ".pt0#" & gam.hero(i).battle_pic buyst.herosprite(i).pal = palette16_load(gam.hero(i).battle_pal, 0, gam.hero(i).battle_pic) IF buyst.herosprite(i).pal = 0 THEN debug "Failed to load palette for hero (#" & i & ")" NEXT i 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 loadshopstuf stufbuf(), buyst.shop_id buystuff_setshopstock buyst, stufbuf() buyst.st.pt = 0 buyst.st.size = 15 buystuff_buildmenu buyst, stuff(), stufbuf() IF buyst.st.last = -1 THEN GOTO cleanupquit buystuff_curinfo buyst, stufbuf(), stuff(buyst.st.pt).dat, itembuf() 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) playtimer control usemenusounds IF usemenu(buyst.st) THEN buystuff_curinfo buyst, stufbuf(), stuff(buyst.st.pt).dat, itembuf() IF carray(ccMenu) > 1 THEN EXIT DO IF carray(ccUse) > 1 THEN IF stuff(buyst.st.pt).disabled = NO THEN '--CHECK TO SEE IF YOU CAN BUY IT IF gam.stock(buyst.shop_id, buyst.itemno) > 1 THEN gam.stock(buyst.shop_id, buyst.itemno) -= 1 settag stufbuf(buyst.itemno * buyst.recordsize + 22) gold = gold - stufbuf(buyst.itemno * buyst.recordsize + 24) IF buyst.tradingitems THEN '--TRADE IN ITEMS FOR i as integer = 0 TO 3 IF buyst.tradestf(i, 0) > -1 THEN delitem buyst.tradestf(i, 0), buyst.tradestf(i, 1) END IF NEXT END IF '--END TRADE IN ITEM IF buyst.shoptype = 0 THEN '--BUY ITEM menusound gen(genBuySFX) getitem stufbuf(buyst.itemno * buyst.recordsize + 18) buyst.alert_box_style = 4 buyst.alert_ticks = 18 buyst.alert_str = buyst.purchased & " " & stuff(buyst.st.pt).text END IF '--END IF ITEM IF buyst.shoptype = 1 THEN '--HIRE HERO menusound gen(genHireSFX) slot = first_free_slot_in_active_party() IF slot >= 0 THEN addhero stufbuf(buyst.itemno * buyst.recordsize + 18) + 1, slot, stufbuf(buyst.itemno * buyst.recordsize + 26) buyst.alert_box_style = 4 buyst.alert_ticks = 18 buyst.alert_str = stuff(buyst.st.pt).text & " " & buyst.joined END IF END IF '--END IF HERO '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 evalherotags evalitemtags ELSE ' WHEN CANNOT BUY menusound gen(genCantBuySFX) buyst.alert_box_style = 3 buyst.alert_ticks = 18 IF buyst.shoptype = 0 ANDALSO NOT room_for_item(stuff(buyst.st.pt).dat) THEN 'No room in inventory buyst.alert_str = readglobalstring(305, "No room in inventory", 30) ELSE 'Cannot afford buyst.alert_str = buyst.buytype(1, buyst.shoptype) & stuff(buyst.st.pt).text END IF END IF '--------END BUY THING------------ buystuff_buildmenu buyst, stuff(), stufbuf() IF buyst.st.last = -1 THEN GOTO cleanupquit buystuff_curinfo buyst, stufbuf(), stuff(buyst.st.pt).dat, itembuf() END IF '---------END TRY BUY THING-------- 'Draw the screen copypage holdscreen, page edgeboxstyle left_panel, 0, page edgeboxstyle right_panel, 0, page '-----RIGHT PANEL------------------------------------------ DIM gold_str as string = gold & " " & readglobalstring(32, "Money") centerbox 240, 19, LEN(gold_str) * 8 + 8, 14, 4, page edgeprint gold_str, xstring(gold_str, 240), 14, uilook(uiText), page DIM linenum as integer = 0 edgeprint stuff(buyst.st.pt).text, xstring(stuff(buyst.st.pt).text, 240), 30 + linenum * 10, uilook(uiMenuItem), page: linenum += 1 IF buyst.info1 <> "" THEN edgeprint buyst.info1, xstring(buyst.info1, 240), 30 + linenum * 10, uilook(uiDisabledItem), page: linenum += 1 IF buyst.info2 <> "" THEN edgeprint buyst.info2, xstring(buyst.info2, 240), 30 + linenum * 10, uilook(uiDisabledItem), page: linenum += 1 IF buyst.eqinfo <> "" THEN edgeprint buyst.eqinfo, xstring(buyst.eqinfo, 240), 30 + linenum * 10, uilook(uiMenuItem), page: linenum += 1 IF gam.stock(buyst.shop_id, buyst.itemno) > 1 THEN edgeprint (gam.stock(buyst.shop_id, buyst.itemno) - 1) & " " & buyst.instock & " ", xstring((gam.stock(buyst.shop_id, buyst.itemno) - 1) & " " & buyst.instock & " ", 240), 30 + linenum * 10, uilook(uiMenuItem), page: linenum += 1 END IF IF buyst.showhero > -1 THEN 'This happens only if a hireable hero is selected centerbox 240, 130, 36, 44, 4, page frame_draw(buyst.hiresprite.sprite + walks(buyst.walk), buyst.hiresprite.pal, 224, 110, 1, -1, page) END IF IF buyst.is_equipable THEN FOR i as integer = 0 TO 3 heropos.x = 170 + i * 36 heropos.y = 130 heroframe = 0 DIM col as integer = 0 IF hero(i) > 0 THEN 'If there is a hero in this slot IF readbit(itembuf(), 66, hero(i) - 1) <> 0 THEN '-- animation heroes when this item is equipable heroframe = walks(buyst.walk) col = 3 END IF END IF edgeboxstyle heropos.x - 1, heropos.y - 2, 34, 44, col, page, , YES IF hero(i) > 0 THEN 'If there is a hero in this slot frame_draw(buyst.herosprite(i).sprite + heroframe, buyst.herosprite(i).pal, heropos.x, heropos.y, 1, -1, page) END IF NEXT i END IF '-----LEFT PANEL------------------------------------------- FOR i as integer = buyst.st.top TO small(buyst.st.top + buyst.st.size, UBOUND(stuff)) DIM c as integer = uilook(uiMenuItem) IF buyst.st.pt = i THEN c = uilook(uiSelectedItem + buyst.tog) IF stuff(i).disabled THEN c = uilook(uiDisabledItem): IF buyst.st.pt = i THEN c = uilook(uiMenuItem + buyst.tog) END IF edgeprint stuff(i).text, 10, 15 + (i - buyst.st.top) * 10, c, page NEXT i draw_scrollbar buyst.st, left_panel, , page IF buyst.price <> "" THEN centerbox 160, 187, LEN(buyst.price) * 8 + 8, 14 + buyst.xtralines * 10, 1, page edgeprint buyst.price, xstring(buyst.price, 160), 182 - buyst.xtralines * 5, uilook(uiText), page IF buyst.xtralines >= 1 THEN edgeprint buyst.price2, xstring(buyst.price2, 160), 187, uilook(uiText), page END IF IF buyst.alert_ticks > 0 THEN buyst.alert_ticks -= 1 centerbox 160, 178, LEN(buyst.alert_str) * 8 + 8, 14, buyst.alert_box_style, page edgeprint buyst.alert_str, xstring(buyst.alert_str, 160), 173, uilook(uiSelectedItem + buyst.tog), page END IF setvispage vpage dowait LOOP cleanupquit: 'Unload the sprites used to display the heroes FOR i as integer = 0 TO 3 frame_unload(@buyst.herosprite(i).sprite) palette16_unload(@buyst.herosprite(i).pal) NEXT i frame_unload(@buyst.hiresprite.sprite) palette16_unload(@buyst.hiresprite.pal) freepage page freepage holdscreen menusound gen(genCancelSFX) evalitemtags party_change_updates EXIT SUB END SUB SUB buystuff_curinfo (buyst as ShopBuyState, stufbuf() as integer, byval selected_stuff as integer, itembuf() as integer) buyst.itemno = selected_stuff buyst.tradingitems = NO buyst.xtralines = 0 buyst.showhero = -1 buyst.is_equipable = NO buyst.price = "" buyst.price2 = "" buyst.eqinfo = "" buyst.info1 = "" buyst.info2 = "" IF stufbuf(buyst.itemno * buyst.recordsize + 24) > 0 THEN buyst.price = stufbuf(buyst.itemno * buyst.recordsize + 24) & " " & readglobalstring(32, "Money") '--load must trade in item types+amounts buystuff_loadtrades buyst, buyst.itemno, stufbuf() FOR i as integer = 0 TO 3 IF buyst.tradestf(i, 0) > -1 THEN buyst.tradingitems = YES IF buyst.price = "" THEN buyst.price = buyst.buytype(0, buyst.shoptype) ELSE IF buyst.tradestf(i, 1) = 1 THEN buyst.price = buyst.price & " " & readglobalstring(81, "and a", 10) & " " ELSE buyst.price = buyst.price & " " & readglobalstring(153, "and", 10) & " " END IF END IF IF buyst.tradestf(i, 1) = 1 THEN buyst.price = buyst.price & readitemname(buyst.tradestf(i, 0)) ELSE buyst.price = buyst.price & STR(buyst.tradestf(i, 1)) & " " & readitemname(buyst.tradestf(i, 0)) END IF END IF NEXT IF LEN(buyst.price) > 38 THEN '--have to split in 2! ARGH DIM i as integer = 38 WHILE i > 19 AND MID(buyst.price, i, 1) <> " ": i = i - 1: WEND buyst.price2 = MID(buyst.price, i + 1) buyst.price = LEFT(buyst.price, i - 1) buyst.xtralines = 1 END IF IF stufbuf(buyst.itemno * buyst.recordsize + 17) = 0 THEN 'This is an item loaditemdata itembuf(), stufbuf(buyst.itemno * buyst.recordsize + 18) 'The itembuf remains and is used later to show equipability. DIM eqprefix as string = readglobalstring(99, "Equip:", 10) IF itembuf(49) = 1 THEN buyst.eqinfo = eqprefix & " " & readglobalstring(38, "Weapon", 10) IF itembuf(49) > 1 THEN buyst.eqinfo = eqprefix & " " & readglobalstring(23 + itembuf(49), "Armor" & itembuf(49)-1) buyst.info1 = readbadbinstring(itembuf(), 9, 36, 0) IF LEN(buyst.info1) > 17 THEN DIM o as integer FOR o = 18 TO 1 STEP -1 IF MID(buyst.info1, o, 1) = " " OR MID(buyst.info1, o, 1) = "-" OR MID(buyst.info1, o, 1) = "," OR MID(buyst.info1, o, 1) = "." THEN EXIT FOR NEXT o IF o > 1 THEN buyst.info2 = RIGHT(buyst.info1, LEN(buyst.info1) - o) buyst.info1 = LEFT(buyst.info1, o) END IF IF RIGHT(buyst.info1, 1) = " " THEN buyst.info1 = LEFT(buyst.info1, LEN(buyst.info1) - 1) buyst.info1 = LEFT(buyst.info1, 18) END IF IF itembuf(49) > 0 THEN 'This item is equippable buyst.is_equipable = YES END IF END IF IF stufbuf(buyst.itemno * buyst.recordsize + 17) = 1 THEN 'hire dim her as herodef loadherodata her, stufbuf(buyst.itemno * buyst.recordsize + 18) DIM wbuf(dimbinsize(binITM)) as integer loaditemdata wbuf(), her.def_weapon DIM hire_level as integer = stufbuf(buyst.itemno * buyst.recordsize + 26) IF hire_level < 0 THEN hire_level = her.def_level IF hire_level < 0 THEN hire_level = averagelev() buyst.eqinfo = (atlevel(hire_level, her.lev0.hp, her.levMax.hp) + wbuf(54 + 0)) & " " & statnames(statHP) buyst.showhero = her.sprite 'Load the sprite for the hireable hero frame_unload @buyst.hiresprite.sprite buyst.hiresprite.sprite = frame_load(0, buyst.showhero) IF buyst.hiresprite.sprite = 0 THEN debug "Couldn't load hero sprite: " & game & ".pt0#" & buyst.showhero palette16_unload @buyst.hiresprite.pal buyst.hiresprite.pal = palette16_load(her.sprite_pal, 0, buyst.showhero) IF buyst.hiresprite.pal = 0 THEN debug "Failed to load palette for hireable hero (#" & her.sprite_pal & ")" IF buyst.room_to_hire = NO THEN buyst.info1 = readglobalstring(100, "No Room in Party", 20) END IF END SUB SUB buystuff_buildmenu (buyst as ShopBuyState, stuff() as SimpleMenuItem, stufbuf() as integer) '--this figures out if it is okay to buy (or hire) particular stuff. REDIM stuff(-1 TO -1) buyst.room_to_hire = herocount(3) < 4 ANDALSO free_slots_in_party() > 0 FOR i as integer = 0 TO buyst.last_stuff '--for each shop-thing IF gam.stock(buyst.shop_id, i) = 1 THEN CONTINUE FOR IF stufbuf(i * buyst.recordsize + 17) <> buyst.shoptype THEN CONTINUE FOR IF NOT istag(stufbuf(i * buyst.recordsize + 20), -1) THEN CONTINUE FOR DIM itemname as string = readbadbinstring(stufbuf(), i * buyst.recordsize, 16) REDIM PRESERVE stuff(-1 TO UBOUND(stuff) + 1) WITH stuff(UBOUND(stuff)) .text = itemname .dat = i IF stufbuf(i * buyst.recordsize + 24) > gold THEN .disabled = YES buystuff_loadtrades buyst, i, stufbuf() FOR j as integer = 0 TO 3 IF buyst.tradestf(j, 0) > -1 THEN IF countitem(buyst.tradestf(j, 0)) < buyst.tradestf(j, 1) THEN .disabled = YES END IF NEXT '---PREVENT OVERFLOW IF buyst.shoptype = 0 THEN '--is an item IF NOT room_for_item(stufbuf(i * buyst.recordsize + 18)) THEN .disabled = YES ELSEIF buyst.shoptype = 1 THEN '--is a hero IF buyst.room_to_hire = NO THEN .disabled = YES END IF END WITH NEXT i init_menu_state buyst.st, stuff() END SUB SUB buystuff_setshopstock (buyst as ShopBuyState, stufbuf() as integer) DIM i as integer FOR i = 0 TO buyst.last_stuff '--for each shop-stuff IF gam.stock(buyst.shop_id, i) = 0 THEN '--if unloaded, reload stock gam.stock(buyst.shop_id, i) = stufbuf(i * buyst.recordsize + 19) '--zero means unloaded, 1 means no-stock, 2+n means 1+n in stock IF gam.stock(buyst.shop_id, i) > -1 THEN gam.stock(buyst.shop_id, i) = gam.stock(buyst.shop_id, i) + 1 END IF NEXT i END SUB SUB buystuff_loadtrades(buyst as ShopBuyState, byval index as integer, stufbuf() as integer) buyst.tradestf(0, 0) = stufbuf(index * buyst.recordsize + 25) - 1 buyst.tradestf(0, 1) = stufbuf(index * buyst.recordsize + 30) + 1 FOR i as integer = 1 TO 3 buyst.tradestf(i, 0) = stufbuf(index * buyst.recordsize + i * 2 + 29) - 1 buyst.tradestf(i, 1) = stufbuf(index * buyst.recordsize + i * 2 + 30) + 1 NEXT i 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 hero(target) = 0 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 itemmenuswap (invent() as InventSlot, iuse() as integer, permask() as integer, byval it1 as integer, byval it2 as integer) 'this sub called from items() SWAP invent(it1), invent(it2) DIM t1 as integer = readbit(iuse(), 0, 3 + it1) DIM t2 as integer = readbit(iuse(), 0, 3 + it2) setbit iuse(), 0, 3 + it1, t2 setbit iuse(), 0, 3 + it2, t1 t1 = readbit(permask(), 0, 3 + it1) t2 = readbit(permask(), 0, 3 + it2) setbit permask(), 0, 3 + it1, t2 setbit permask(), 0, 3 + it2, t1 END SUB SUB update_inventory_caption (byval i as integer) 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(XSTR(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 hero(o) > 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 setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 IF keyval(scEsc) > 1 THEN EXIT SUB 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", 120, 50, uilook(uiText), dpage centerbox 160, 100, 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), 160 - LEN(num(i)) * 4, 80 + i * 10, c, dpage NEXT i edgeprint "0123456789ABCDEF", 96, 110, uilook(uiSelectedDisabled), dpage SWAP vpage, dpage setvispage vpage dowait LOOP END SUB FUNCTION picksave (byval loading as integer) as integer DIM mapname(3) as string DIM lev(3) as string DIM menu(1) as string DIM sprites(3, 3) as GraphicPair DIM st as MenuState st.first = -1 'Note that st.pt can actually go down to -2 st.last = 3 st.size = 4 DIM allow as integer '--loading 0 is the save menu, 1 is load menu, and 2 is load with no titlescreen. it fades the screen in '--loading 0+1 use vpage as background, loading 2 uses none. pages 2 and 3 are preserved '--terribly sorry for the dirtyness gam.need_fade_in = NO 'FIXME: this looks like a tacky hack. IF loading = 2 THEN queue_fade_in 1 END IF '--menu array holds the names of the options '--at the top of the screeen (only one appears when saving) IF loading THEN st.pt = 0 menu(0) = readglobalstring(52, "New Game", 10) menu(1) = readglobalstring(53, "Exit", 10) ELSE st.pt = lastsaveslot - 1 menu(0) = readglobalstring(59, "CANCEL", 10) END IF DIM holdscreen as integer DIM page as integer page = compatpage holdscreen = allocatepage IF loading < 2 THEN '--preserve background for display beneath the save/load picker copypage page, holdscreen END IF 'otherwise, holdscreen is black DIM pv(3) as SaveSlotPreview FOR i as integer = 0 TO 3 get_save_slot_preview i, pv(i) IF pv(i).valid THEN mapname(i) = getmapname(pv(i).cur_map) '--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 sprites(i, o).sprite = frame_load(0, pv(i).hero(o).battle_pic) sprites(i, o).pal = palette16_load(pv(i).hero(o).battle_pal, 0, pv(i).hero(o).battle_pic) 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 3 IF pv(i).valid THEN nofull = NO NEXT i IF nofull = YES THEN picksave = -1 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 = 0 THEN playtimer control IF carray(ccMenu) > 1 THEN MenuSound gen(genCancelSFX) IF loading THEN picksave = -2 ELSE picksave = -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 loading 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 st.pt = -2 THEN MenuSound gen(genCancelSFX) picksave = st.pt EXIT DO ELSEIF st.pt = -1 THEN MenuSound gen(genAcceptSFX) picksave = st.pt 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, sprites(), pv(), mapname(), lev(), st, holdscreen, page) END IF IF allow = 1 THEN MenuSound gen(genAcceptSFX) picksave = st.pt lastsaveslot = st.pt + 1 EXIT DO ELSE MenuSound gen(genCancelSFX) END IF END IF END IF copypage holdscreen, page picksave_draw menu(), loading, 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 3 FOR o as integer = 0 TO 3 frame_unload(@sprites(i, o).sprite) palette16_unload(@sprites(i, o).pal) NEXT NEXT EXIT FUNCTION END FUNCTION 'Returns whether confirmed FUNCTION picksave_confirm(menu() as string, byval loading as integer, 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) 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, 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 integer, sprites() as GraphicPair, pv() as SaveSlotPreview, mapname() as string, lev() as string, byref st as MenuState, byval page as integer) DIM col as integer DIM activec as integer STATIC tog as integer STATIC walk as integer tog = tog XOR 1 walk = walk XOR tog centerbox 50, 11, 80, 14, 15, page IF loading THEN centerbox 270, 11, 80, 14, 15, page FOR i as integer = 0 TO 3 centerbox 160, 44 + i * 44, 310, 42, 15, page NEXT i 'load and save menus enjoy different colour schemes IF loading THEN activec = 2 ELSE activec = 1 SELECT CASE st.pt CASE -2 centerbox 270, 11, 82, 16, activec, page CASE -1 centerbox 50, 11, 82, 16, activec, page CASE ELSE centerbox 160, 44 + st.pt * 44, 312, 44, activec, page END SELECT FOR i as integer = 0 TO 3 IF pv(i).valid THEN 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 + i * 44, 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 + i * 44, col, page edgeprint lev(i), 14, 34 + i * 44, col, page edgeprint pv(i).playtime, 14, 43 + i * 44, col, page edgeprint mapname(i), 14, 52 + i * 44, col, page END IF NEXT i col = uilook(uiMenuItem): IF st.pt = -1 THEN col = uilook(uiSelectedItem + tog) edgeprint menu(0), xstring(menu(0), 50), 6, col, page IF loading THEN col = uilook(uiMenuItem): IF st.pt = -2 THEN col = uilook(uiSelectedItem + tog) edgeprint menu(1), xstring(menu(1), 270), 6, col, page END IF 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 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) DIM sold as string = readglobalstring(84, "Sold", 10) loadshopstuf b(), id FOR i as integer = 0 TO storebuf(16) IF gam.stock(id, i) = 0 THEN gam.stock(id, i) = b(i * recordsize + 19) IF gam.stock(id, i) > -1 THEN gam.stock(id, i) = gam.stock(id, i) + 1 END IF NEXT i 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() GOSUB sellinfostr setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer control GOSUB keysell 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, xstring(info, 160), 175, uilook(uiText), page edgeprint gold & " " & readglobalstring(32, "Money"), 310 - LEN(gold & " " & readglobalstring(32, "Money")) * 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, xstring(alert_str, 160), 173, uilook(uiSelectedItem + tog), page END IF setvispage vpage dowait LOOP freepage page freepage holdscreen menusound gen(genCancelSFX) evalitemtags tag_updates EXIT SUB sellinfostr: info = "" IF inventory(ic).used = 0 THEN RETRACE IF readbit(permask(), 0, ic) = 1 THEN info = cannotsell: RETRACE IF price(ic) > 0 THEN info = worth & " " & price(ic) & " " & readglobalstring(32, "Money") FOR i as integer = 0 TO storebuf(16) 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 RETRACE keysell: 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 IF b(i * recordsize + 26) = 1 THEN gam.stock(id, i) = -1 IF b(i * recordsize + 26) = 2 AND gam.stock(id, i) > 0 THEN gam.stock(id, i) = gam.stock(id, i) + 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() GOSUB sellinfostr ELSE menusound gen(genCantSellSFX) END IF END IF IF carray(ccUp) > 1 AND ic >= 3 THEN menusound gen(genCursorSFX) ic = ic - 3 GOSUB sellinfostr 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 GOSUB sellinfostr 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 GOSUB sellinfostr ELSE ic = ic + 2 GOSUB sellinfostr END IF END IF IF carray(ccRight) > 1 THEN menusound gen(genCursorSFX) IF ic MOD 3 < 2 THEN ic = ic + 1 GOSUB sellinfostr ELSE ic = ic - 2 GOSUB sellinfostr END IF END IF RETRACE 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 SUB status (byval pt as integer) DIM mtype(5) as integer DIM her as HeroDef DIM portrait as GraphicPair DIM page as integer DIM holdscreen as integer DIM exper_caption as string = readglobalstring(33, "Experience", 10) DIM level_caption as string = readglobalstring(43, "Level", 10) DIM level_mp_caption as string = readglobalstring(160, "Level MP", 20) DIM elementalmenu() as string DIM elementalmenu_st as MenuState DIM elementalmenu_scrollrect as RectType WITH elementalmenu_scrollrect .x = 14 .y = 60 .wide = 292 .high = 120 END WITH DIM elementnames() as string getelementnames elementnames() DIM elementaldmg(maxElements - 1) as SINGLE DIM mode as integer = 0 DIM show_base_stats as bool = NO 'debugging DIM tog as integer status_buildmenu her, pt, mtype(), elementaldmg(), elementnames(), elementalmenu(), elementalmenu_st, portrait '--Preserve background for display under status menu page = compatpage holdscreen = allocatepage copypage page, holdscreen menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys tog = tog XOR 1 playtimer control IF carray(ccMenu) > 1 THEN EXIT DO IF carray(ccUse) > 1 THEN mode = loopvar(mode, 0, 2, 1): menusound gen(genCursorSFX) IF carray(ccLeft) > 1 THEN DO: pt = loopvar(pt, 0, 3, -1): LOOP UNTIL hero(pt) > 0: menusound gen(genCursorSFX): status_buildmenu her, pt, mtype(), elementaldmg(), elementnames(), elementalmenu(), elementalmenu_st, portrait IF carray(ccRight) > 1 THEN DO: pt = loopvar(pt, 0, 3, 1): LOOP UNTIL hero(pt) > 0: menusound gen(genCursorSFX): status_buildmenu her, pt, mtype(), elementaldmg(), elementnames(), elementalmenu(), elementalmenu_st, portrait IF keyval(scF4) > 1 THEN show_base_stats XOR= YES copypage holdscreen, page centerfuz 160, 100, 304, 184, 1, page centerbox 160, 36, 292, 40, 4, page SELECT CASE mode CASE 0 centerbox 84, 120, 140, 120, 4, page centerbox 236, 120, 140, 120, 4, page CASE 1, 2 centerbox 160, 120, 292, 120, 4, page END SELECT IF her.portrait >= 0 THEN edgeboxstyle 262, 8, 50, 50, 3, page frame_draw portrait.sprite, portrait.pal, 262, 8,,,page END IF '--name edgeprint names(pt), 142 - LEN(names(pt)) * 4, 20, uilook(uiText), page '--level and experience edgeprint level_caption & " " & gam.hero(pt).lev, 142 - LEN(level_caption & " " & gam.hero(pt).lev) * 4, 30, uilook(uiText), page IF gam.hero(pt).lev < current_max_level THEN 'Can't level further, so hide experience required DIM exp_str as string = (gam.hero(pt).exp_next - gam.hero(pt).exp_cur) & " " & exper_caption & " " & readglobalstring(47, "for next", 10) & " " & level_caption edgeprint exp_str, 142 - LEN(exp_str) * 4, 40, uilook(uiText), page END IF SELECT CASE mode CASE 0 '--show stats DIM stat_v_offset as integer = 0 FOR statnum as integer = 2 TO statLast IF should_hide_hero_stat(her, statnum) THEN CONTINUE FOR edgeprint statnames(statnum), 20, 62 + stat_v_offset * 10, uilook(uiText), page DIM stat_str as string = STR(gam.hero(pt).stat.cur.sta(statnum)) IF show_base_stats THEN stat_str &= "(" & gam.hero(pt).stat.base.sta(statnum) & ")" edgeprint stat_str, 148 - LEN(stat_str) * 8, 62 + stat_v_offset * 10, uilook(uiText), page stat_v_offset += 1 NEXT statnum 'current/max HP edgeprint statnames(statHP), 236 - LEN(statnames(statHP)) * 4, 65, uilook(uiText), page DIM hp_str as string = STR(ABS(gam.hero(pt).stat.cur.hp)) & "/" & STR(ABS(gam.hero(pt).stat.max.hp)) IF show_base_stats THEN hp_str &= "(" & gam.hero(pt).stat.base.hp & ")" edgeprint hp_str, 236 - LEN(hp_str) * 4, 75, uilook(uiText), page '--MP and level MP DIM mp_str as string FOR i as integer = 0 TO 5 IF mtype(i) = 0 THEN IF NOT should_hide_hero_stat(her, statMP) THEN edgeprint statnames(statMP), 236 - LEN(statnames(statMP)) * 4, 95, uilook(uiText), page mp_str = STR(ABS(gam.hero(pt).stat.cur.mp)) & "/" & STR(ABS(gam.hero(pt).stat.max.mp)) IF show_base_stats THEN mp_str &= "(" & gam.hero(pt).stat.base.mp & ")" edgeprint mp_str, 236 - LEN(mp_str) * 4, 105, uilook(uiText), page END IF END IF IF mtype(i) = 1 THEN edgeprint level_mp_caption, 236 - LEN(level_mp_caption) * 4, 125, uilook(uiText), page mp_str = "" FOR o as integer = 0 TO 3 mp_str = mp_str & STR(ABS(lmp(pt, o))) & "/" NEXT o mp_str = LEFT(mp_str, LEN(mp_str) - 1) edgeprint mp_str, 236 - LEN(mp_str) * 4, 135, uilook(uiText), page mp_str = "" FOR o as integer = 4 TO 7 mp_str = mp_str & STR(ABS(lmp(pt, o))) & "/" NEXT o mp_str = LEFT(mp_str, LEN(mp_str) - 1) edgeprint mp_str, 236 - LEN(mp_str) * 4, 145, uilook(uiText), page END IF NEXT i '--gold edgeprint gold & " " & readglobalstring(32, "Money"), 236 - LEN(gold & " " & readglobalstring(32, "Money")) * 4, 167, uilook(uiGold), page CASE 1 '--show elementals WITH elementalmenu_st usemenusounds scrollmenu elementalmenu_st draw_scrollbar elementalmenu_st, elementalmenu_scrollrect, , page FOR i as integer = 0 TO .size IF .top + i <= .last THEN edgeprint elementalmenu(.top + i), 20, 64 + i * 10, uilook(uiText), page END IF NEXT i END WITH CASE 2 '--tigger rename IF gam.hero(pt).rename_on_status THEN '--status-screen rename is allowed renamehero pt, YES IF carray(ccMenu) > 1 THEN EXIT DO mode = 0 END IF END SELECT setvispage vpage dowait LOOP menusound gen(genCancelSFX) IF portrait.sprite THEN frame_unload @portrait.sprite IF portrait.pal THEN palette16_unload @portrait.pal freepage page freepage holdscreen FOR t as integer = 4 TO 5 carray(t) = 0 NEXT t END SUB SUB status_buildmenu(her as HeroDef, byval pt as integer, mtype() as integer, elementaldmg() as single, elementnames() as string, elementalmenu() as string, elementalmenu_st as MenuState, portrait as GraphicPair) '--loads the hero whose slot is held in pt loadherodata her, hero(pt) - 1 FOR i as integer = 0 TO UBOUND(mtype) mtype(i) = -1 NEXT i DIM mtype_i as integer = 0 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 mtype(mtype_i) = her.list_type(listnum) mtype_i += 1 END IF END IF END WITHNODE END READNODE '--get this hero's elemental resists, with worn equipment calc_hero_elementals elementaldmg(), pt '--build elemental strings REDIM elementalmenu(-1 TO -1) DIM msg as string = readglobalstring(302, "Elemental Effects:", 30) IF LEN(msg) THEN str_array_append elementalmenu(), msg FOR i as integer = 0 TO gen(genNumElements) - 1 msg = hero_elemental_resist_msg(elementnames(i), elementaldmg(i)) IF LEN(msg) THEN str_array_append elementalmenu(), msg NEXT 'Well, if you set some blank global text strings, you could get this message 'even if not everything is level, but that's a bonus. IF UBOUND(elementalmenu) = 0 THEN elementalmenu(0) = readglobalstring(130, "No Elemental Effects", 30) WITH elementalmenu_st .last = UBOUND(elementalmenu) .size = 10 END WITH IF portrait.sprite THEN frame_unload @portrait.sprite IF portrait.pal THEN palette16_unload @portrait.pal IF her.portrait >= 0 THEN portrait.sprite = frame_load(8, her.portrait) portrait.pal = palette16_load(her.portrait_pal, 8, her.portrait) END IF 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 hero(who) = 0 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, hero(who) - 1 '--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 setpicstuf buffer(), ol * 2 * 50, -1 loadset game + ".stf", id, 0 'in case shop data has been resized, scale records to new size FOR i as integer = 0 TO ol - 1 FOR o as integer = 0 to 49 array(o * nw + i) = buffer(o * ol + i) NEXT o NEXT i 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 SUB equip (byval who as integer) 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_update_equipped_item_info(st, eqstuf()) '--prepare the backdrop 'preserve the background behind the equip menu copypage page, holdscreen '--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 carray(ccLeft) > 1 THEN 'Left: previous hero DO: st.who = loopvar(st.who, 0, 3, -1): LOOP UNTIL hero(st.who) > 0 equip_menu_setup st, menu() MenuSound gen(genCursorSFX) item_info = equip_menu_update_equipped_item_info(st, eqstuf()) END IF IF carray(ccRight) > 1 THEN 'Right: next hero DO: st.who = loopvar(st.who, 0, 3, 1): LOOP UNTIL hero(st.who) > 0 equip_menu_setup st, menu() MenuSound gen(genCursorSFX) item_info = equip_menu_update_equipped_item_info(st, eqstuf()) END IF usemenusounds IF usemenu(st.slot, 0, 0, 6, 6) THEN item_info = equip_menu_update_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_update_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 flusharray st.stat_bonus() MenuSound gen(genCancelSFX) item_info = equip_menu_update_equipped_item_info(st, eqstuf()) END IF usemenusounds IF usemenu(st.eq_cursor) THEN equip_menu_stat_bonus st item_info = equip_menu_update_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 names(st.who), 84 - LEN(names(st.who)) * 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 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) ELSE statcol2 = uilook(uiMenuItem) END IF DIM resultant_stat as integer = gam.hero(st.who).stat.max.sta(i) + st.stat_bonus(i) DIM cap as integer = gen(genStatCap + i) IF cap > 0 AND resultant_stat > cap THEN resultant_stat = cap stat_caption = fgcol_text(stat_caption, uilook(uiSelectedDisabled)) statcol2 = uilook(uiSelectedDisabled + tog) END IF stat_caption2 = STR(resultant_stat) edgeprint statnames(i) & stat_caption, 20, 42 + stat_v_offset * 10, uilook(uiMenuItem), page 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), uilook(uiTextBox) 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 printstr inventory(st.eq(st.slot).offset(i)).text, 192, 28 + (i - st.eq_cursor.top) * 8, page ELSEIF i = st.eq(st.slot).count THEN '--unequip option IF st.slot = 0 THEN printstr st.default_weapon_name, 192, 28 + (i - st.eq_cursor.top) * 8, page ELSE printstr st.unequip_caption, 192, 28 + (i - st.eq_cursor.top) * 8, page END IF ELSE '--all done! EXIT FOR END IF NEXT i END IF centerbox 160, 192, 312, 16, 4, page edgeprint item_info, xstring(item_info, 160), 187, uilook(uiText), page setvispage vpage dowait LOOP freepage page freepage holdscreen MenuSound gen(genCancelSFX) 'tags handled in unequip, doequip END SUB FUNCTION equip_menu_update_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_update_available_item_info(st as EquipMenuState) as string DIM slot_id as integer = st.eq(st.slot).offset(st.eq_cursor.pt) IF slot_id >= 0 THEN RETURN readitemdescription(inventory(slot_id).id) END IF RETURN "" END FUNCTION SUB equip_menu_setup (byref st as EquipMenuState, menu() as string) loadherodata st.hero, hero(st.who) - 1 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 readbit(itembuf(), 66, hero(st.who) - 1) 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 flusharray st.stat_bonus() 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) DIM item as integer = 0 ' Will be set to item ID + 1 IF st.eq_cursor.pt = st.eq(st.slot).count THEN '--last option: unequip IF st.slot = 0 THEN '--special handling for weapon '--load the default weapon info and continue as normal item = st.default_weapon ELSE 'non-weapon unequip sets item to 0 to warn to skip that step item = 0 END IF ELSE '--equip item = inventory(st.eq(st.slot).offset(st.eq_cursor.pt)).id + 1 END IF DIM itembuf(dimbinsize(binITM)) as integer IF item = 0 THEN '--nothing to load! flusharray st.stat_bonus() ELSE loaditemdata itembuf(), item - 1 FOR i as integer = 0 TO statLast st.stat_bonus(i) = itembuf(54 + i) NEXT i END IF 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 END SUB 'Returns 0, or ID of textbox to open (box 0 can't be opened) FUNCTION items_menu () as integer DIM istate as ItemsMenuState WITH istate .trigger_box = 0 .cursor = -3 .top = -3 .sel = -4 .info = "" .re_use = NO END WITH DIM itemtemp(dimbinsize(binITM)) as integer DIM iuse((inventoryMax + 3) / 16) as integer 'bit 0 of iuse, permask, correspond to item -3 DIM permask((inventoryMax + 3) / 16) as integer istate.special(-3) = rpad(readglobalstring(35, "DONE", 10), " ", 11) istate.special(-2) = rpad(readglobalstring(36, "AUTOSORT", 10), " ", 11) istate.special(-1) = rpad(readglobalstring(37, "TRASH", 10), " ", 11) '--Preserve background for display beneath the item menu DIM holdscreen as integer istate.page = compatpage holdscreen = allocatepage copypage istate.page, holdscreen DIM i as integer FOR i = 0 TO 2 setbit iuse(), 0, i, 1 NEXT i FOR i = 0 TO last_inv_slot() IF inventory(i).used THEN loaditemdata itemtemp(), inventory(i).id IF itemtemp(73) = 2 THEN setbit permask(), 0, 3 + i, 1 IF itemtemp(50) > 0 THEN '--teach spell setbit iuse(), 0, 3 + i, 1 ELSEIF itemtemp(51) > 0 THEN setbit iuse(), 0, 3 + i, 1 ELSEIF itemtemp(51) < 0 THEN setbit iuse(), 0, 3 + i, 1 END IF END IF NEXT i WITH istate.rect .x = 8 .y = 5 .wide = 304 .high = small(180, 12 + (((last_inv_slot() + 1) \ 3) + 1) * 8) END WITH WITH istate.scrollrect .x = 20 .y = 12 .wide = 287 .high = 168 END WITH WITH istate.scroll .first = -1 .last = last_inv_slot() \ 3 .size = 20 END WITH items_menu_infostr istate, permask() DIM wtogl as integer = 0 menusound gen(genAcceptSFX) setkeys DO setwait speedcontrol setkeys istate.tog = istate.tog XOR 1 wtogl = loopvar(wtogl, 0, 3, 1) playtimer control items_menu_control istate, iuse(), permask() IF istate.trigger_box > 0 THEN '--return the box number to trigger items_menu = istate.trigger_box EXIT DO END IF IF istate.quit THEN menusound gen(genCancelSFX) EXIT DO END IF IF istate.refresh THEN items_menu_infostr istate, permask() END IF IF istate.re_use = NO THEN copypage holdscreen, istate.page END IF items_menu_paint istate, iuse(), permask() IF istate.re_use = NO THEN setvispage vpage dowait END IF LOOP carray(ccUse) = 0 carray(ccMenu) = 0 freepage istate.page freepage holdscreen checkfatal = YES evalherotags evalitemtags tag_updates END FUNCTION SUB items_menu_paint (istate as ItemsMenuState, iuse() as integer, permask() as integer) edgeboxstyle istate.rect.x, istate.rect.y, istate.rect.wide, istate.rect.high, 0, istate.page DIM display as string FOR i as integer = istate.top TO small(istate.top + 62, last_inv_slot()) textcolor uilook(uiDisabledItem), 0 IF readbit(iuse(), 0, 3 + i) = 1 THEN textcolor uilook(uiMenuItem), 0 IF readbit(permask(), 0, 3 + i) THEN textcolor uilook(uiSelectedDisabled), 0 IF istate.cursor = i THEN textcolor uilook(uiMenuItem), uilook(uiHighlight2) IF readbit(iuse(), 0, 3 + i) = 1 THEN textcolor uilook(uiText), uilook(uiHighlight2) IF readbit(permask(), 0, 3 + i) THEN textcolor uilook(uiGold), uilook(uiHighlight2) END IF IF istate.sel = i THEN textcolor uilook(uiMenuItem), uilook(uiHighlight + istate.tog) IF istate.cursor = i THEN textcolor uilook(uiSelectedItem + istate.tog), uilook(uiHighlight + istate.tog) END IF IF i >= 0 THEN display = inventory(i).text ELSE display = istate.special(i) END IF printstr display, 20 + 96 * ((i + 3) MOD 3), 12 + 8 * ((i - istate.top) \ 3), istate.page NEXT i centerfuz 160, 192, 312, 16, 4, istate.page edgeprint istate.info, xstring(istate.info, 160), 187, uilook(uiText), istate.page WITH istate.scroll .top = INT(istate.top / 3) .pt = INT(istate.cursor / 3) END WITH draw_scrollbar istate.scroll, istate.scrollrect, , istate.page END SUB SUB items_menu_infostr(istate as ItemsMenuState, permask() as integer) istate.info = "" IF istate.sel >= 0 AND istate.cursor = -1 THEN IF inventory(istate.sel).used THEN istate.info = readglobalstring(41, "Discard", 10) & " " & inventory(istate.sel).text IF readbit(permask(), 0, 3 + istate.sel) THEN istate.info = readglobalstring(42, "Cannot", 10) & " " & istate.info & "!" END IF END IF IF istate.cursor < 0 THEN EXIT SUB IF inventory(istate.cursor).used = 0 THEN EXIT SUB istate.info = readitemdescription(inventory(istate.cursor).id) END SUB SUB items_menu_autosort(iuse() as integer, permask() as integer) 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 itemmenuswap inventory(), iuse(), permask(), i, 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 IF readbit(iuse(), 0, 3 + best) = 0 AND readbit(iuse(), 0, 3 + o) = 1 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 itemmenuswap inventory(), iuse(), permask(), i, best autosort_changed = YES END IF NEXT i IF autosort_changed THEN menusound gen(genAcceptSFX) ELSE menusound gen(genCancelSFX) END IF END SUB SUB item_menu_use_item(byval slot as integer, istate as ItemsMenuState, iuse() as integer, permask() as integer) IF inventory(slot).used = NO THEN EXIT SUB DIM consumed as integer = NO '--repaint the item menu so it can be the background for the menu_attack_targ_picker items_menu_paint istate, iuse(), permask() IF use_item_in_slot(slot, istate.trigger_box, consumed) THEN IF consumed THEN setbit iuse(), 0, 3 + slot, 0 IF istate.trigger_box > 0 THEN EXIT SUB istate.re_use = YES istate.refresh = YES END IF END SUB FUNCTION use_item_in_slot(byval slot as integer, byref trigger_box as integer, byref consumed as integer) 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 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 = names(targ) & " " & readglobalstring(124, "learned", 10) & " " & learn_attack.name centerbox 160, 100, small(LEN(caption) * 8 + 16, 320), 24, 1, page edgeprint caption, large(xstring(caption, 160), 0), 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 hero(i) > 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 names(cater_slot), 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, xstring(use_caption, 160 + x_offset), 95, uilook(uiText), page setvispage vpage dowait LOOP carray(ccUse) = 0 carray(ccMenu) = 0 freepage page freepage holdscreen END FUNCTION SUB items_menu_control (istate as ItemsMenuState, iuse() as integer, permask() as integer) istate.refresh = NO IF istate.re_use THEN istate.re_use = NO item_menu_use_item istate.cursor, istate, iuse(), permask() EXIT SUB END IF IF carray(ccMenu) > 1 THEN '--deselect currently selected item IF istate.sel > -1 THEN istate.sel = -4 menusound gen(genCancelSFX) ELSE istate.quit = YES END IF END IF IF carray(ccUse) > 1 THEN '--exit IF istate.cursor = -3 THEN istate.quit = YES '--sort IF istate.cursor = -2 THEN items_menu_autosort iuse(), permask() IF istate.cursor = -1 AND istate.sel >= 0 AND readbit(permask(), 0, 3 + istate.sel) = 0 THEN '--try to thow item away IF inventory(istate.sel).used THEN MenuSound gen(genAcceptSFX) WITH inventory(istate.sel) .used = 0 .id = 0 .num = 0 END WITH update_inventory_caption istate.sel setbit iuse(), 0, 3 + istate.sel, 0 istate.sel = -4 istate.refresh = YES EXIT SUB END IF IF istate.sel >= 0 THEN IF istate.cursor >= 0 AND istate.cursor <> istate.sel THEN '--swap the selected item and the item under the cursor itemmenuswap inventory(), iuse(), permask(), istate.cursor, istate.sel istate.sel = -4 istate.refresh = YES MenuSound gen(genAcceptSFX) EXIT SUB END IF IF istate.cursor >= 0 AND istate.sel = istate.cursor THEN '--try to use the current item istate.sel = -4 '--if the usability bit is off, or you dont have any of the item, exit IF readbit(iuse(), 0, 3 + istate.cursor) = 0 OR inventory(istate.cursor).used = 0 THEN EXIT SUB item_menu_use_item istate.cursor, istate, iuse(), permask() EXIT SUB END IF END IF IF istate.sel < -3 AND istate.cursor >= 0 THEN istate.sel = istate.cursor MenuSound gen(genAcceptSFX) EXIT SUB END IF END IF IF carray(ccUp) > 1 AND istate.cursor >= 0 THEN menusound gen(genCursorSFX) istate.cursor -= 3 istate.refresh = YES IF istate.cursor < istate.top THEN istate.top -= 3 END IF IF carray(ccDown) > 1 AND istate.cursor <= last_inv_slot() - 3 THEN menusound gen(genCursorSFX) istate.cursor = istate.cursor + 3 istate.refresh = YES IF istate.cursor > istate.top + 62 THEN istate.top += 3 END IF IF carray(ccLeft) > 1 THEN menusound gen(genCursorSFX) IF (istate.cursor MOD 3) = 0 THEN istate.cursor = istate.cursor + 2 ELSE IF istate.cursor > -3 THEN istate.cursor -= 1 END IF istate.refresh = YES END IF IF carray(ccRight) > 1 THEN menusound gen(genCursorSFX) IF ((istate.cursor + 3) MOD 3) = 2 THEN ' the +3 adjust for the first negative row istate.cursor = istate.cursor - 2 ELSE IF istate.cursor < last_inv_slot() THEN istate.cursor += 1 END IF istate.refresh = YES END IF IF keyval(scPageUp) > 1 THEN menusound gen(genCursorSFX) istate.cursor -= (istate.scroll.size+1) * 3 WHILE istate.cursor < -3 : istate.cursor += 3: WEND WHILE istate.cursor < istate.top : istate.top -= 3 : WEND istate.refresh = YES END IF IF keyval(scPageDown) > 1 THEN menusound gen(genCursorSFX) istate.cursor += (istate.scroll.size+1) * 3 WHILE istate.cursor > last_inv_slot(): istate.cursor -= 3: WEND WHILE istate.cursor >= istate.top + (istate.scroll.size+1) * 3 : istate.top += 3 : WEND istate.refresh = YES END IF END SUB SUB spells_menu_refresh_list(sp as SpellsMenuState) IF sp.lists(sp.listnum).magic_type < 0 THEN EXIT SUB DIM atk as AttackData 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.foc) '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 IF sp.lists(sp.listnum).magic_type = 0 THEN .cost = cost & " " & statnames(statMP) & " " & gam.hero(sp.hero).stat.cur.mp & "/" & gam.hero(sp.hero).stat.max.mp END IF IF sp.lists(sp.listnum).magic_type = 1 THEN .cost = readglobalstring(160, "Level MP", 20) & " " & (INT(i / 3) + 1) & ": " & lmp(sp.hero, INT(i / 3)) END IF 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, hero(sp.hero) - 1 'hero() is a global '--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 hero(sp.hero) > 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 hero(sp.hero) > 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.foc) 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 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, uilook(uiTextBox + 3), 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, 16, 171, sp.page 'cancel IF sp.mset = 1 THEN IF sp.spell(sp.cursor).desc <> "" THEN rectangle 6, 155, 308, 1, uilook(uiTextBox + 3), sp.page 'description divider END IF textcolor uilook(uiDescription), 0 printstr sp.spell(sp.cursor).cost, 303 - LEN(sp.spell(sp.cursor).cost) * 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 names(sp.hero) & " " & sp.has_none_caption, xstring(names(sp.hero) & " " & sp.has_none_caption, 160), 120, uilook(uiText), sp.page edgeprint names(sp.hero), xstring(names(sp.hero), 206), 31, uilook(uiText), sp.page END SUB