'OHRRPGCE GAME - Main battle-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 ;) 'misc #include "config.bi" #include "common.bi" #include "loading.bi" #INCLUDE "gglobals.bi" #INCLUDE "const.bi" #INCLUDE "uiconst.bi" #INCLUDE "udts.bi" #INCLUDE "battle_udts.bi" 'modules #include "bmod.bi" #include "bmodsubs.bi" #include "bcommon.bi" #include "game.bi" #include "yetmore2.bi" #include "moresubs.bi" #include "allmodex.bi" #include "scriptcommands.bi" #include "menustuf.bi" '--local subs and functions DECLARE FUNCTION count_dissolving_enemies(bslot() as BattleSprite) as integer DECLARE FUNCTION find_empty_enemy_slot(formdata as Formation) as integer DECLARE SUB spawn_on_death(byval deadguy as integer, byval killing_attack as integer, byref bat as BattleState, formdata as Formation, bslot() as BattleSprite) DECLARE SUB triggerfade(byval who as integer, bslot() as BattleSprite) DECLARE SUB check_death(byval deadguy as integer, byval killing_attack as integer, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB checkitemusability(iuse() as integer, bslot() as BattleSprite, byval who as integer) DECLARE SUB reset_battle_state (byref bat as BattleState) DECLARE SUB reset_targetting (byref bat as BattleState) DECLARE SUB reset_attack (byref bat as BattleState) DECLARE SUB reset_victory_state (byref vic as VictoryState) DECLARE SUB reset_rewards_state (byref rew as RewardsState) DECLARE SUB show_victory (byref bat as BattleState, bslot() as BattleSprite, page as integer) DECLARE SUB trigger_victory(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB fulldeathcheck (byval killing_attack as integer, bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB anim_flinchstart(byval who as integer, bslot() as BattleSprite, byref attack as AttackData) DECLARE SUB anim_flinchdone(byval who as integer, bslot() as BattleSprite, byref attack as AttackData) DECLARE SUB draw_battle_sprites(bslot() as BattleSprite, page as integer) DECLARE FUNCTION battle_time_can_pass(byref bat as BattleState) as bool DECLARE FUNCTION battle_meters_can_advance(byref bat as BattleState, bslot() as BattleSprite) as bool DECLARE SUB battle_crappy_run_handler(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB show_enemy_meters(bat as BattleState, bslot() as BattleSprite, formdata as Formation, page as integer) DECLARE SUB battle_animate(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB battle_meters (byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB battle_display (byref bat as BattleState, bslot() as BattleSprite, st() as HeroDef, page as integer) DECLARE SUB battle_confirm_target(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB battle_targetting(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB battle_spawn_on_hit(byval targ as integer, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB battle_attack_anim_cleanup (byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB battle_attack_anim_playback (byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB battle_attack_do_inflict(byval targ as integer, byval tcount as integer, byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB battle_pause () DECLARE SUB battle_debug_menu (bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB check_battle_debug_keys (bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB battle_cleanup(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB battle_init(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB battle_background_anim(byref bat as BattleState, formdata as Formation) DECLARE FUNCTION battle_run_away(byref bat as BattleState, bslot() as BattleSprite) as bool DECLARE SUB battle_animate_running_away (bslot() as BattleSprite) DECLARE SUB battle_check_delays(byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB battle_check_for_hero_turns(byref bat as BattleState, bslot() as BattleSprite) DECLARE FUNCTION battle_check_a_hero_turn(byref bat as BattleState, bslot() as BattleSprite, byval index as integer) as integer DECLARE SUB battle_check_for_enemy_turns(byref bat as BattleState, bslot() as BattleSprite) DECLARE FUNCTION battle_check_an_enemy_turn(byref bat as BattleState, bslot() as BattleSprite, byval index as integer) as integer DECLARE SUB battle_attack_cancel_target_attack(byval targ as integer, byref bat as BattleState, bslot() as BattleSprite, byref attack as AttackData) DECLARE FUNCTION check_has_remaining_targets(bat as BattleState, bslot() as BattleSprite, targs() as integer) as bool DECLARE SUB battle_reevaluate_dead_targets (byval deadguy as integer, byref bat as BattleState, bslot() as BattleSprite) DECLARE SUB battle_sort_away_dead_t_target(byval deadguy as integer, t() as integer) DECLARE SUB battle_counterattacks(bat as BattleState, byval h as integer, byval targstat as integer, byval who as integer, attack as AttackData, bslot() as BattleSprite) DECLARE SUB show_first_battle_timer (page as integer) DECLARE FUNCTION has_queued_attacks(byval who as integer) as integer DECLARE FUNCTION pending_attacks_for_this_turn(bat as BattleState, bslot() as BattleSprite) as bool DECLARE SUB decrement_attack_queue_delays(bslot() as BattleSprite) DECLARE SUB ready_all_valid_units(bslot() as BattleSprite, formdata as Formation) DECLARE SUB active_mode_state_machine (bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB turn_mode_state_machine (bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB do_poison(byval who as integer, bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB do_regen(byval who as integer, bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB start_next_turn (bat as BattleState, bslot() as BattleSprite, formdata as Formation) DECLARE SUB calc_initiative_order (bslot() as BattleSprite, formdata as Formation) DECLARE SUB apply_initiative_order (bslot() as BattleSprite) DECLARE SUB turn_mode_time_passage (bat as BattleState, bslot() as battleSprite) DECLARE FUNCTION hero_or_enemy_can_take_a_turn (byval who as integer, bat as BattleState, bslot() as BattleSprite) as integer DECLARE SUB cancel_blocking_attacks_for_hero_or_enemy(byval who as integer) DECLARE SUB update_turn_delays_in_attack_queue (byval who as integer) DECLARE FUNCTION has_blocking_turn_delayed_attacks(byval who as integer) as integer DECLARE SUB populate_battle_menu_menudef (byval hero_id as integer, menu as MenuDef, hero as HeroDef) DECLARE SUB update_battle_menu (bat as BattleState, bslot() as BattleSprite) DECLARE SUB init_spell_menu (bat as BattleState, bslot() as BattleSprite, st() as HeroDef) DECLARE FUNCTION enemy_is_weak(byval who as integer, bslot() as BattleSprite) as bool DECLARE FUNCTION check_for_unhittable_invisible_foe(byval index as integer, byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, t() as integer) as bool DECLARE FUNCTION is_among_targets(byval slot as integer, t() as integer) as bool DECLARE FUNCTION should_victory_advance(byref bat as BattleState) as bool CONST ANIM_SKIP_SENTINEL = &h6EEFDEEF 'these are the battle global variables DIM bstackstart as integer REDIM learnmask(245) as integer '6 shorts of bits per hero REDIM atkq(15) as AttackQueue FUNCTION battle (byval form as integer) as bool DIM result as bool 'Return value: whether victorious script_log_out !"\nFighting battle (formation " & form & ")" gam.pad.in_battle = YES remap_virtual_gamepad "virtual_gamepad_battle" set_animation_framerate 55 DIM formdata as Formation DIM attack as AttackData DIM st(3) as HeroDef DIM bat as BattleState clear_attack_queue() DIM bslot(24) as BattleSprite SELECT CASE gen(genBattleMode) CASE 0: bat.turn.mode = turnACTIVE CASE 1: bat.turn.mode = turnTURN CASE ELSE: debug "WARNING: invalid gen(genBattleMode) " & gen(genBattleMode) & " resorting to turnACTIVE" bat.turn.mode = turnACTIVE END SELECT '--lastformation is a global lastformation = form battle_init bat, bslot() DIM page as integer = compatpage() 'fade to near white fadeout 240, 240, 240 queue_fade_in battle_loadall form, bat, bslot(), st(), formdata '--main battle loop setkeys DO setwait 55 'Adjusting framerate in-battle is not implemented IF running_as_slave THEN try_to_reload_files_inbattle setkeys bat.tog XOR= 1 playtimer control '--background animation battle_background_anim bat, formdata IF always_enable_debug_keys <> 0 OR readbit(gen(), genBits, 8) = 0 THEN '"Disable debugging keys" check_battle_debug_keys bat, bslot(), formdata /' Currently unused... IF keyval(scF12) > 1 THEN bat.test_view_mode = loopvar(bat.test_view_mode, 0, 2, 1) SELECT CASE bat.test_view_mode CASE 0: bat.test_future = NO : debuginfo "bat.test_view_mode Classic" CASE 1: debuginfo "bat.test_view_mode Flicker" CASE 2: bat.test_future = YES : debuginfo "bat.test_view_mode Future" END SELECT END IF '/ END IF IF keyval(scPause) > 1 THEN battle_pause IF battle_run_away(bat, bslot()) THEN result = NO EXIT DO END IF '--An attack should happen, prepare its animation IF bat.atk.id >= 0 AND bat.anim_ready = NO AND bat.vic.state = vicNONE THEN generate_atkscript attack, bat, bslot(), bat.anim_t() END IF '--Playback the current attack animation IF bat.atk.id >= 0 AND bat.anim_ready = YES AND bat.vic.state = vicNONE AND bat.away = 0 THEN battle_attack_anim_playback attack, bat, bslot(), formdata END IF '--Apply more generic animation effects. These are often triggered by '--battle_attack_anim_playback() but do not have to be battle_animate bat, bslot() SELECT CASE bat.turn.mode CASE turnACTIVE: active_mode_state_machine bat, bslot(), formdata CASE turnTURN: turn_mode_state_machine bat, bslot(), formdata END SELECT IF bat.vic.state = vicNONE THEN IF bat.enemy_turn >= 0 THEN enemy_ai bat, bslot(), formdata IF bat.hero_turn >= 0 AND bat.targ.mode = targNONE THEN IF bat.menu_mode = batMENUITEM THEN itemmenu bat, bslot() IF bat.menu_mode = batMENUSPELL THEN spellmenu bat, st(), bslot() IF bat.menu_mode = batMENUHERO THEN heromenu bat, bslot(), st() END IF IF bat.hero_turn >= 0 AND bat.targ.mode > targNONE THEN battle_targetting bat, bslot() END IF IF bat.test_view_mode = 1 THEN bat.test_future = NOT bat.test_future '-------------------- Draw the screen -------------------- '--display the backdrop, centered clearpage vpage frame_draw bat.backdrop, , pCentered, pCentered, , NO, vpage '--display the sprites draw_battle_sprites bslot(), page '--display menus and meters battle_display bat, bslot(), st(), page IF bat.vic.state = vicEXITDELAY THEN bat.vic.state = vicEXIT IF bat.vic.state > vicNONE THEN show_victory bat, bslot(), page IF bat.show_info_mode = 1 THEN show_enemy_meters bat, bslot(), formdata, page ELSEIF bat.show_info_mode = 2 THEN display_attack_queue bslot() END IF IF bat.death_mode = deathENEMIES AND bat.vic.state = vicNONE THEN IF count_dissolving_enemies(bslot()) = 0 THEN trigger_victory bat, bslot() END IF IF bat.vic.state = vicEXIT THEN 'Victory result = YES EXIT DO END IF IF bat.death_mode = deathHEROES THEN IF formdata.death_action = 0 THEN 'normal game over/trigger death script fatal = YES ELSEIF formdata.death_action = -1 THEN 'continue game END IF result = NO EXIT DO END IF ' Display CANNOT RUN message (attack captions are drawn in battle_display!) IF bat.alert_ticks > 0 THEN bat.alert_ticks -= 1 centerfuz rCenter, rBottom - 10, textwidth(bat.alert) + 16, 16, 3, page edgeprint bat.alert, pCentered, rBottom - 15, uilook(uiSelectedItem + bat.tog), page END IF IF dotimerbattle THEN result = NO EXIT DO END IF show_first_battle_timer(page) setvispage vpage check_for_queued_fade_in bat.ticks += 1 dowait LOOP battle_cleanup bat, bslot() freepage page evalherotags evalitemtags IF formdata.victory_tag THEN settag formdata.victory_tag, result END IF tag_updates set_animation_framerate gen(genMillisecPerFrame) gam.pad.in_battle = NO remap_virtual_gamepad "virtual_gamepad" setkeys 'This only seems to matter in some contexts, such as triggered from a textbox RETURN result END FUNCTION SUB battle_init(byref bat as BattleState, bslot() as BattleSprite) '--prepare stack bstackstart = stackpos 'Remember the music that was playing on the map so that the prepare_map() sub can restart it later gam.remembermusic = presentsong reset_battle_state bat '--Init BattleState FOR i as integer = 0 TO 11 bslot(i).consume_item = -1 bslot(i).revenge = -1 bslot(i).thankvenge = -1 bslot(i).harm.col = uilook(uiText) '--init affliction registers '--it should be clear by the fact that BattleStats is a separate type that '--that bslot().stat inside battle is not the same as gam.hero().stat outside battle WITH bslot(i).stat.cur .poison = 1000 .regen = 1000 .stun = 1000 .mute = 1000 END WITH WITH bslot(i).stat.max .poison = 1000 .regen = 1000 .stun = 1000 .mute = 1000 END WITH bslot(i).poison_repeat = randint(2000) bslot(i).regen_repeat = randint(2000) NEXT i '--sanity-check affliction indicators and set defaults for old games that don't have them yet. IF gen(genPoisonChar) <= 0 THEN gen(genPoisonChar) = 161 IF gen(genStunChar) <= 0 THEN gen(genStunChar) = 159 IF gen(genMuteChar) <= 0 THEN gen(genMuteChar) = 163 IF gen(genRegenChar) <= 0 THEN gen(genRegenChar) = 32 END SUB SUB battle_cleanup(byref bat as BattleState, bslot() as BattleSprite) export_battle_hero_stats bslot() '--overflow checking for the battle stack IF stackpos() - bstackstart > 0 THEN '--an overflow is not unusual. This happens if the battle terminates '--while an attack is still going on DIM dummy as integer WHILE stackpos > bstackstart: dummy = popdw: WEND END IF '--underflow checking IF stackpos() - bstackstart < 0 THEN '--an underflow is bad. It used to mean that whatever script was on '--the top of the stack has been corrupted, but now scripts don't use this stack '--but an underflow is still bad in principle. showerror "bstack underflow " & stackpos & " " & bstackstart END IF fadeout 0, 0, 0 frame_unload(@bat.backdrop) FOR i as integer = LBOUND(bslot) TO UBOUND(bslot) frame_unload(@bslot(i).sprites) palette16_unload(@bslot(i).pal) NEXT i END SUB SUB battle_pause () DIM pause as string = readglobalstring(54, "PAUSE", 10) fuzzyrect 0, 0, , , boxlook(0).bgcol, vpage edgeprint pause, pCentered, 95, uilook(uiText), vpage setvispage vpage waitforanykey END SUB SUB battle_attack_anim_playback (byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) '--this plays back the animation sequence built when the attack starts. DIM i as integer '--decrement the animation wait ticks, and don't proceed until they are zero IF bat.wait_frames > 0 THEN bat.wait_frames -= 1: IF bat.wait_frames > 0 THEN EXIT SUB '--special handling when we are waiting for all motion to stop IF bat.wait_frames = -1 THEN bat.wait_frames = 0 FOR i = 0 TO 23 IF bslot(i).xmov <> 0 OR bslot(i).ymov <> 0 OR bslot(i).zmov <> 0 THEN bat.wait_frames = -1 NEXT i IF bat.wait_frames = -1 THEN EXIT SUB END IF bat.wait_frames = 0 DIM act as integer '--these are used to temporarily store "who" arguments DIM ww as integer DIM w1 as integer DIM w2 as integer DO: 'INTERPRET THE ANIMATION SCRIPT act = popdw SELECT CASE act CASE 0 '--end() FOR i = 0 TO 3 enforce_weak_picture i, bslot(), bat '--re-enforce party's X/Y positions... bslot(i).x = bslot(i).basex bslot(i).y = bslot(i).basey NEXT i FOR i = 0 TO 7 IF bslot(4 + i).flee = 0 THEN bslot(4 + i).x = bslot(4 + i).basex bslot(4 + i).y = bslot(4 + i).basey END IF NEXT i bat.atk.id = -1 CASE 1 '??? Reset hero positions, I guess? FIXME: figure out when this is used FOR i = 0 TO 3 formdata.slots(i).pos.x = bslot(4 + i).x formdata.slots(i).pos.y = bslot(4 + i).y NEXT i bat.atk.id = -1 CASE 2 'setmove(who,xticks,yticks,xstep,ystep) ww = popdw bslot(ww).xmov = popdw bslot(ww).ymov = popdw bslot(ww).xspeed = popdw bslot(ww).yspeed = popdw CASE 3 'setpos(who,x,y,d) ww = popdw bslot(ww).x = popdw bslot(ww).y = popdw bslot(ww).d = popdw CASE 4 '???() '--undefined CASE 5 'appear(who) ww = popdw bslot(ww).vis = 1 CASE 6 'disappear(who) ww = popdw bslot(ww).vis = 0 CASE 7 'setframe(who,frame) ww = popdw DIM fr as integer = popdw bslot(ww).frame = fr IF is_hero(ww) THEN bslot(ww).walk = 0 CASE 8 'absmove(who,x,y,xticks,yticks) ww = popdw DIM destpos as XYPair destpos.x = popdw destpos.y = popdw DIM moveticks as XYPair moveticks.x = popdw moveticks.y = popdw bslot(ww).xspeed = (destpos.x - bslot(ww).x) / moveticks.x bslot(ww).yspeed = (destpos.y - bslot(ww).y) / moveticks.y bslot(ww).xmov = moveticks.x bslot(ww).ymov = moveticks.y CASE 9 'waitforall() bat.wait_frames = -1 CASE 10 'inflict(targ, target_count) DIM targ as integer = popdw DIM tcount as integer = popdw battle_attack_do_inflict targ, tcount, attack, bat, bslot(), formdata CASE 11 'setz(who,z) ww = popdw bslot(ww).z = popdw CASE 12 'unimplemented CASE 13 'wait(ticks) bat.wait_frames = popdw CASE 14 'walktoggle(who) ww = popdw bslot(ww).frame = frameSTAND IF is_hero(ww) THEN bslot(ww).walk XOR= 1 CASE 15 'zmove(who,zticks,zstep) ww = popdw bslot(ww).zmov = popdw bslot(ww).zspeed = popdw CASE 16 'sound(which) DIM soundnum as integer = popdw stopsfx(soundnum) 'Restart the sound effect playsfx(soundnum) CASE 17 'align(who, target, edge, offset) w1 = popdw w2 = popdw select case popdw 'which edge? case dirUp bslot(w1).y = bslot(w2).y + popdw case dirDown bslot(w1).y = bslot(w2).y + bslot(w2).h - bslot(w1).h + popdw case dirLeft bslot(w1).x = bslot(w2).x + popdw case dirRight bslot(w1).x = bslot(w2).x + bslot(w2).w - bslot(w1).w + popdw end select CASE 18 'setcenter(who, target, offx, offy) w1 = popdw w2 = popdw bslot(w1).x = (bslot(w2).w - bslot(w1).w) / 2 + bslot(w2).x + popdw bslot(w1).y = (bslot(w2).h - bslot(w1).h) / 2 + bslot(w2).y + popdw CASE 19 'align2(who, target, edgex, edgey, offx, offy) w1 = popdw w2 = popdw DIM xd as integer = popdw DIM yd as integer = popdw if xd then bslot(w1).x = bslot(w2).x + bslot(w2).w - bslot(w1).w + popdw else bslot(w1).x = bslot(w2).x + popdw end if if yd then bslot(w1).y = bslot(w2).y + bslot(w2).h - bslot(w1).h + popdw else bslot(w1).y = bslot(w2).y + popdw end if CASE 20 'relmove(who, x, y, xticks, yticks) ww = popdw 'who DIM movedist as XYPair movedist.x = popdw movedist.y = popdw DIM moveticks as XYPair moveticks.x = popdw moveticks.y = popdw with bslot(ww) if moveticks.x <> 0 then .xspeed = movedist.x / moveticks.x else .xspeed = 0 if moveticks.y <> 0 then .yspeed = movedist.y / moveticks.y else .yspeed = 0 .xmov = moveticks.x .ymov = moveticks.y end with CASE 21 'setdir(who, d) ww = popdw DIM newdir as integer = popdw bslot(ww).d = newdir CASE 22 'anim_skip_ahead_if_targetless 'For multihit attacks, skip rest of hits if no targets left. 'The flaw is that dead-and-dissolved targets are still hit until the 'whole attack is cancelled. IF check_has_remaining_targets(bat, bslot(), bat.anim_t()) THEN CONTINUE DO 'Skip ahead, looking for command 23. We are looking for a 10 byte pattern DO IF stackpos <= 0 THEN debugc errBug, "anim_skip_ahead_if_targetless: couldn't find point to skip to" EXIT DO END IF DIM word as integer = popdw IF word = 23 AND readstackdw(-1) = ANIM_SKIP_SENTINEL AND readstackdw(-2) = ANIM_SKIP_SENTINEL THEN popdw popdw EXIT DO END IF LOOP CASE 23 'anim_skip_to_here 'Pop the sentinel markers popdw popdw CASE 24 'anim_abszmove(who,z,zticks) ww = popdw DIM destz as integer = popdw DIM moveticks as integer = popdw bslot(ww).zspeed = (destz - bslot(ww).z) / moveticks bslot(ww).zmov = moveticks END SELECT LOOP UNTIL bat.wait_frames <> 0 OR bat.atk.id = -1 IF bat.atk.id = -1 THEN battle_attack_anim_cleanup attack, bat, bslot(), formdata END IF END SUB SUB battle_attack_do_inflict(byval targ as integer, byval tcount as integer, byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) 'targ is the target slot number 'tcount is the total number of targets (used only for dividing spread damage) DIM i as integer 'set tag, if there is one checkTagCond attack.tagset(0), atktagOnUse checkTagCond attack.tagset(1), atktagOnUse '--attempt inflict the damage to the target DIM h as integer = 0 '--set inside inflict DIM targstat as integer = 0 '--set inside inflict IF inflict(h, targstat, bat.acting, targ, bslot(bat.acting), bslot(targ), attack, tcount, attack_can_hit_dead(bat.acting, attack)) THEN '--attack succeeded IF attack.transmog_enemy > 0 ANDALSO is_enemy(targ) THEN changefoe targ - 4, attack.transmog_enemy, formdata, bslot(), attack.transmog_hp, attack.transmog_stats END IF battle_attack_cancel_target_attack targ, bat, bslot(), attack WITH bslot(targ).enemy.reward IF attack.erase_rewards = YES THEN .gold = 0 .exper = 0 .item_rate = 0 .rare_item_rate = 0 END IF END WITH IF attack.force_run = YES THEN 'force heroes to run away IF checkNoRunBit(bslot()) THEN bat.alert = bat.cannot_run_caption bat.alert_ticks = 10 ELSE bat.away = 1 END IF ELSEIF attack.force_victory = YES THEN trigger_victory bat, bslot() ELSEIF attack.force_battle_exit = YES THEN bat.away = 11 END IF checkTagCond attack.tagset(0), atktagOnHit checkTagCond attack.tagset(1), atktagOnHit IF bslot(targ).stat.cur.hp = 0 THEN checkTagCond attack.tagset(0), atktagOnKill checkTagCond attack.tagset(1), atktagOnKill END IF IF trytheft(bat, bat.acting, targ, attack, bslot()) THEN IF bat.hero_turn >= 0 THEN checkitemusability bat.iuse(), bslot(), bat.hero_turn END IF END IF ELSE checkTagCond attack.tagset(0), atktagOnMiss checkTagCond attack.tagset(1), atktagOnMiss END IF triggerfade targ, bslot() IF bslot(targ).stat.cur.hp > 0 THEN '---REVIVE--- bslot(targ).vis = 1 bslot(targ).dissolve = 0 END IF IF is_enemy(targ) AND attack.no_spawn_on_attack = NO THEN battle_spawn_on_hit targ, bat, bslot(), formdata battle_counterattacks bat, h, targstat, targ, attack, bslot() IF bat.atk.has_consumed_costs = NO THEN '--if the attack costs MP, we want to actually consume MP IF attack.mp_cost > 0 THEN bslot(bat.acting).stat.cur.mp = large(bslot(bat.acting).stat.cur.mp - focuscost(attack.mp_cost, bslot(bat.acting).stat.cur.focus), 0) '--ditto for HP IF attack.hp_cost > 0 THEN WITH bslot(bat.acting) .stat.cur.hp = large(.stat.cur.hp - attack.hp_cost, 0) .harm.ticks = gen(genDamageDisplayTicks) .harm.pos.x = .x + (.w * .5) .harm.pos.y = .y + (.h * .5) .harm.text = STR(attack.hp_cost) END WITH 'This triggerfade is needed because the hp cost might have killed the attacker triggerfade bat.acting, bslot() END IF '--ditto for money IF attack.money_cost <> 0 THEN gold = large(gold - attack.money_cost, 0) WITH bslot(bat.acting) .harm.ticks = gen(genDamageDisplayTicks) .harm.pos.x = .x + (.w * .5) .harm.pos.y = .y + (.h * .5) .harm.text = ABS(attack.money_cost) & "$" IF attack.money_cost < 0 THEN .harm.text += "+" END WITH IF gold > 2000000000 THEN gold = 2000000000 IF gold < 0 THEN gold = 0 END IF '--if the attack consumes items, we want to consume those too FOR i = 0 to 2 WITH attack.item(i) IF .id > 0 THEN 'this slot is used IF .number > 0 THEN 'remove items delitem(.id - 1, .number) ELSEIF .number < 0 THEN 'add items getitem(.id - 1, abs(.number)) END IF 'Update tags when items have changed because it could affect chain conditionals evalitemtags END IF END WITH NEXT i '--set the flag to prevent re-consuming MP bat.atk.has_consumed_costs = YES END IF IF bslot(bat.acting).consume_lmp > 0 THEN lmp(bat.acting, bslot(bat.acting).consume_lmp - 1) -= 1 bslot(bat.acting).consume_lmp = 0 END IF IF bslot(bat.acting).consume_item >= 0 THEN IF consumeitem(bslot(bat.acting).consume_item) THEN setbit bat.iuse(), 0, bslot(bat.acting).consume_item, 0 evalitemtags END IF bslot(bat.acting).consume_item = -1 END IF IF liveherocount(bslot()) = 0 THEN bat.atk.id = -1 END SUB SUB battle_attack_anim_cleanup (byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) '--hide the caption when the animation is done IF attack.caption_time = 0 THEN '--clear duration-timed caption bat.caption_time = 0 bat.caption_delay = 0 END IF '--clean up .self_bequesting ' (we don't have to clean up .bequesting here because it only stays on until the bequest attack is triggered) IF bat.acting >= 0 THEN bslot(bat.acting).self_bequesting = NO ELSE debuginfo "cleaning up attack " & attack.name & " with no attacker set" END IF '--check to see if anybody is dead fulldeathcheck bat.atk.was_id, bat, bslot(), formdata '--FIXME: further cleanup to remove was_id entirely? bat.atk.was_id = -1 '--clean up animation stack 'DEBUG debug "discarding " & (stackpos - bstackstart) & " from stack" DIM dummy as integer WHILE stackpos > bstackstart: dummy = popdw: WEND '--spawn the next after-chained attack (if any) IF spawn_chained_attack(attack.chain, attack, bat, bslot()) = NO THEN spawn_chained_attack(attack.elsechain, attack, bat, bslot()) END IF END SUB SUB battle_spawn_on_hit(byval targ as integer, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DIM i as integer DIM j as integer DIM slot as integer WITH bslot(targ) '--non-elemental hit IF .enemy.spawn.non_elemental_hit > 0 AND bat.atk.non_elemental THEN FOR j = 1 TO .enemy.spawn.how_many slot = find_empty_enemy_slot(formdata) IF slot > -1 THEN formdata.slots(slot).id = .enemy.spawn.non_elemental_hit - 1 loadfoe slot, formdata, bat, bslot() END IF NEXT j EXIT SUB '--skip further checks END IF FOR i = 0 TO gen(genNumElements) - 1 IF .enemy.spawn.elemental_hit(i) > 0 AND bat.atk.elemental(i) THEN FOR j = 1 TO .enemy.spawn.how_many slot = find_empty_enemy_slot(formdata) IF slot > -1 THEN formdata.slots(slot).id = .enemy.spawn.elemental_hit(i) - 1 loadfoe slot, formdata, bat, bslot() END IF NEXT j EXIT FOR END IF NEXT i END WITH END SUB SUB battle_targetting(byref bat as BattleState, bslot() as BattleSprite) 'Just for heroes DIM i as integer 'cancel IF carray(ccMenu) > 1 THEN menusound gen(genCancelSFX) bslot(bat.hero_turn).attack = 0 bslot(bat.hero_turn).consume_lmp = 0 bat.targ.mode = targNONE flusharray carray(), 7, 0 EXIT SUB END IF IF bat.targ.mode = targSETUP THEN setup_targetting bat, bslot() IF bat.targ.mode = targAUTO THEN autotarget bat.hero_turn, bslot(bat.hero_turn).attack - 1, bslot() bslot(bat.hero_turn).ready_meter = 0 bslot(bat.hero_turn).ready = NO bslot(bat.hero_turn).attack = 0 bat.hero_turn = -1 bat.targ.mode = targNONE EXIT SUB END IF 'check to see if the hero is still allowed to do the attack. 'This may have changed since we started targetting because the attacker 'might have been muted, or hit with an MP-damaging attack ' (NOTE: Does not attempt to re-check LMP) IF NOT atkallowed(bat.targ.atk, bat.hero_turn, 0, 0, bslot()) THEN bslot(bat.hero_turn).attack = 0 bslot(bat.hero_turn).consume_lmp = 0 bat.targ.mode = targNONE flusharray carray(), 7, 0 EXIT SUB END IF 'no valid targs available IF targetmaskcount(bat.targ.mask()) = 0 THEN EXIT SUB END IF 'random target IF bat.targ.roulette THEN FOR i = 0 TO randint(2) bat.targ.pointer = loopvar(bat.targ.pointer, 0, 11, 1) WHILE bat.targ.mask(bat.targ.pointer) = 0 bat.targ.pointer = loopvar(bat.targ.pointer, 0, 11, 1) WEND NEXT i END IF 'first target IF bat.targ.force_first THEN bat.targ.pointer = 0 WHILE bat.targ.mask(bat.targ.pointer) = 0 bat.targ.pointer = loopvar(bat.targ.pointer, 0, 11, 1) WEND END IF 'optional spread targetting IF bat.targ.opt_spread = 2 AND (carray(ccLeft) > 1 OR carray(ccRight) > 1) AND bat.targ.roulette = NO AND bat.targ.force_first = NO THEN menusound gen(genCursorSFX) FOR i = 0 TO 11 bat.targ.selected(i) = 0 NEXT i bat.targ.opt_spread = 1 flusharray carray(), 7, 0 END IF 'arrow keys to select IF bat.targ.interactive = YES AND bat.targ.opt_spread < 2 AND bat.targ.roulette = NO AND bat.targ.force_first = NO THEN IF carray(ccUp) > 1 THEN menusound gen(genCursorSFX) battle_target_arrows -1, 1, bslot(), bat.targ, NO END IF IF carray(ccDown) > 1 THEN menusound gen(genCursorSFX) battle_target_arrows 1, 1, bslot(), bat.targ, NO END IF IF carray(ccLeft) > 1 THEN menusound gen(genCursorSFX) battle_target_arrows -1, 0, bslot(), bat.targ, YES END IF IF carray(ccRight) > 1 THEN menusound gen(genCursorSFX) battle_target_arrows 1, 0, bslot(), bat.targ, YES END IF END IF 'confirm IF carray(ccUse) > 1 THEN menusound gen(genAcceptSFX) battle_confirm_target bat, bslot() END IF END SUB SUB battle_confirm_target(byref bat as BattleState, bslot() as BattleSprite) IF bat.turn.mode = turnACTIVE THEN update_turn_delays_in_attack_queue bat.hero_turn END IF bat.targ.selected(bat.targ.pointer) = 1 DIM t(11) as integer flusharray t(), 11, -1 DIM o as integer = 0 FOR i as integer = 0 TO 11 IF bat.targ.selected(i) = 1 THEN t(o) = i o = o + 1 END IF NEXT i queue_attack bslot(bat.hero_turn).attack - 1, bat.hero_turn, t() bslot(bat.hero_turn).attack = 0 IF bat.turn.mode = turnACTIVE THEN bslot(bat.hero_turn).active_turn_num += 1 'debug "Hero " & bat.hero_turn & " " & bslot(bat.hero_turn).name & " turn #" & bslot(bat.hero_turn).active_turn_num END IF bslot(bat.hero_turn).ready_meter = 0 bslot(bat.hero_turn).ready = NO bat.hero_turn = -1 bat.targ.mode = targNONE bat.targ.hit_dead = NO END SUB SUB battle_display (byref bat as BattleState, bslot() as BattleSprite, st() as HeroDef, page as integer) 'display: '--this sub currently draws the user-interface. In the future it will update '--user interface slices DIM i as integer DIM col as integer IF bat.vic.state = vicNONE THEN 'only display interface till you win FOR i = 0 TO 3 '--for each hero IF gam.hero(i).id >= 0 THEN '--FIXME: should use some battle state instead of global state to '--determine if the hero is present. IF readbit(gen(), genBits, 6) = 0 THEN '"Hide ready meter in battle" off col = uilook(uiTimeBar) IF bslot(i).ready = YES THEN col = uilook(uiTimeBarFull) edgeboxstyle 1, 4 + i * 10, 132, 11, 0, page, YES, YES IF bslot(i).stat.cur.hp > 0 THEN DIM j as integer = bslot(i).ready_meter / 7.7 IF blocked_by_attack(bat, i) OR bslot(i).attack > 0 OR (bat.atk.id >= 0 AND bat.acting = i) THEN col = uilook(uiTimeBar) j = 130 END IF rectangle 2, 5 + i * 10, j, 9, col, page END IF END IF IF readbit(gen(), genBits, 7) = 0 THEN '"Hide health bar in battle" off col = uiLook(uiHealthBar) DIM target_lifemeter_len as integer = CINT(bslot(i).stat.cur.hp * 87 / large(bslot(i).stat.max.hp, 1)) IF bslot(i).lifemeter < target_lifemeter_len THEN bslot(i).lifemeter += 1 IF bslot(i).lifemeter > target_lifemeter_len THEN bslot(i).lifemeter -= 1 IF bslot(i).lifemeter > 87 THEN bslot(i).lifemeter = 87 col = uiLook(uiHealthBar + bat.tog) END IF edgeboxstyle 136, 4 + i * 10, 89, 11, 0, page, YES, YES rectangle 137, 5 + i * 10, bslot(i).lifemeter, 9, col, page END IF '--name-- col = uilook(uiMenuItem): IF i = bat.hero_turn THEN col = uilook(uiSelectedItem + bat.tog) edgeprint bslot(i).name, 128 - LEN(bslot(i).name) * 8, 5 + i * 10, col, page '--hp-- edgeprint bslot(i).stat.cur.hp & "/" & bslot(i).stat.max.hp, 136, 5 + i * 10, col, page WITH bslot(i).stat DIM indicatorpos as integer = 217 'poison indicator IF .cur.poison < .max.poison THEN edgeprint CHR(gen(genPoisonChar)), indicatorpos, 5 + i * 10, col, page indicatorpos -= 8 END IF 'stun indicator IF .cur.stun < .max.stun THEN edgeprint CHR(gen(genStunChar)), indicatorpos, 5 + i * 10, col, page indicatorpos -= 8 END IF 'mute indicator IF .cur.mute < .max.mute THEN edgeprint CHR(gen(genMuteChar)), indicatorpos, 5 + i * 10, col, page indicatorpos -= 8 END IF 'regen indicator IF .cur.regen < .max.regen THEN edgeprint CHR(gen(genRegenChar)), indicatorpos, 5 + i * 10, col, page END IF END WITH END IF NEXT i '--Display caption and count-down time IF bat.caption_time > 0 THEN bat.caption_time -= 1 IF bat.caption_delay > 0 THEN bat.caption_delay -= 1 ELSE centerbox rCenter, rBottom - 14, 310, 16, 1, page edgeprint bat.caption, pCentered, rBottom - 19, uilook(uiText), page END IF END IF '--Draw menus for a hero that is currently taking a turn IF bat.hero_turn >= 0 THEN WITH bslot(bat.hero_turn) update_battle_menu bat, bslot() draw_menu .batmenu, .menust, page IF bat.targ.mode = targNONE AND readbit(gen(), genBits, 14) = 0 THEN ' "Disable Hero's Battle Cursor" 'Show cursor above hero edgeprint CHR(24), .x + (.w / 2) - 4, .y - 5 + (bat.tog * 2), uilook(uiSelectedItem + bat.tog), page END IF END WITH IF bat.menu_mode = batMENUSPELL THEN '--draw spell menu centerbox 160, 53, 310, 95, 1, page IF bat.sptr < 24 THEN IF bat.spell.slot(bat.sptr).desc <> "" THEN rectangle 5, 74, 310, 1, boxlook(0).edgecol, page END IF rectangle 5, 87, 310, 1, boxlook(0).edgecol, page FOR i = 0 TO 23 textcolor uilook(uiDisabledItem + bat.spell.slot(i).enable), 0 IF bat.sptr = i THEN textcolor uilook(uiSelectedDisabled - (2 * ABS(bat.spell.slot(i).enable)) + bat.tog), uilook(uiHighlight) END IF printstr bat.spell.slot(i).name, 16 + (i MOD 3) * 104, 8 + (i \ 3) * 8, page NEXT i textcolor uilook(uiMenuItem), 0 IF bat.sptr = 24 THEN textcolor uilook(uiSelectedItem + bat.tog), uilook(uiHighlight) printstr bat.cancel_spell_caption, 9, 90, page textcolor uilook(uiDescription), 0 IF bat.sptr < 24 THEN printstr bat.spell.slot(bat.sptr).desc, 9, 77, page DIM cost_caption as string = RIGHT(bat.spell.slot(bat.sptr).cost, 30) printstr cost_caption, 311 - LEN(cost_caption) * 8, 90, page END IF END IF 'if keyval(scS) > 1 then gen(genMaxInventory) += 3 'if keyval(scA) > 1 then gen(genMaxInventory) -= 3 IF bat.menu_mode = batMENUITEM THEN '--draw item menu DIM inv_height as integer = small(78, 8 + ((last_inv_slot() + 1) \ 3) * 8) WITH bat.inv_scroll .top = INT(bat.item.top / 3) .pt = INT(bat.item.pt / 3) .last = INT(last_inv_slot() / 3) END WITH bat.inv_scroll_rect.high = inv_height - 2 edgeboxstyle 8, 4, 304, inv_height, 0, page draw_scrollbar bat.inv_scroll, bat.inv_scroll_rect, , page FOR i = bat.item.top TO small(bat.item.top + 26, last_inv_slot()) if i < lbound(inventory) or i > ubound(inventory) then continue for textcolor uilook(uiDisabledItem - readbit(bat.iuse(), 0, i)), 0 IF bat.item.pt = i THEN textcolor uilook(uiSelectedDisabled - (2 * readbit(bat.iuse(), 0, i)) + bat.tog), uilook(uiHighlight) printstr inventory(i).text, 20 + 96 * (i MOD 3), 8 + 8 * ((i - bat.item.top) \ 3), page NEXT i edgeboxstyle 8, 4 + inv_height, 304, 16, 0, page textcolor uilook(uiDescription), 0 printstr bat.item_desc, 12, 8 + inv_height, page END IF IF bat.targ.mode > targNONE THEN FOR i = 0 TO 11 IF bat.targ.selected(i) = 1 OR bat.targ.pointer = i THEN WITH bslot(i) DIM as integer cursx, cursy cursx = bound(.x + .cursorpos.x, 10, gen(genResolutionX) - 10) cursy = bound(.y + .cursorpos.y - 6, 10, gen(genResolutionY) - 10) edgeprint CHR(24), cursx - 4, cursy, uilook(uiSelectedItem + bat.tog), page edgeprint .name, cursx + ancCenter + showLeft, cursy - 10, uilook(uiSelectedItem + bat.tog), page END WITH END IF NEXT i END IF END IF END IF'--end if bat.vic.state = vicNONE IF gam.debug_showtags THEN tagdisplay END SUB SUB battle_meters (byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) '--This advances time in turnACTIVE mode '--it also handles active-mode poison, regen, stun and mute IF bat.away > 0 THEN EXIT SUB '--skip all this if the heroes have already run away DIM i as integer FOR i = 0 TO 11 '--poison WITH bslot(i).stat IF .cur.poison < .max.poison THEN bslot(i).poison_repeat += large(.cur.spd, 7) IF bslot(i).poison_repeat >= 1500 THEN bslot(i).poison_repeat = 0 do_poison i, bat, bslot(), formdata END IF END IF END WITH '--regen WITH bslot(i).stat IF .cur.regen < .max.regen THEN bslot(i).regen_repeat += large(.cur.spd, 7) IF bslot(i).regen_repeat >= 1500 THEN bslot(i).regen_repeat = 0 do_regen i, bat, bslot(), formdata END IF END IF END WITH '--if not doing anything, not dying, not ready, and not stunned IF ready_meter_may_grow(bat, bslot(), i) THEN '--increment ctr by speed bslot(i).ready_meter = small(1000, bslot(i).ready_meter + bslot(i).stat.cur.spd) IF bslot(i).ready_meter = 1000 AND bat.wait_frames = 0 THEN IF has_blocking_turn_delayed_attacks(i) THEN 'debug "Unit " & i & " " & bslot(i).name & " blocked turn #" & bslot(i).active_turn_num bslot(i).ready_meter = 0 update_turn_delays_in_attack_queue i ELSE bslot(i).ready = YES END IF END IF END IF NEXT i '--decrement stun and mute IF bat.ticks >= bat.laststun + ideal_ticks_per_second() THEN FOR i = 0 TO 11 WITH bslot(i).stat .cur.mute = small(.cur.mute + 1, .max.mute) .cur.stun = small(.cur.stun + 1, .max.stun) IF .cur.stun < .max.stun THEN bslot(i).ready = NO IF bat.hero_turn = i THEN bat.hero_turn = -1 IF bat.enemy_turn = i THEN bat.enemy_turn = -1 END IF END WITH NEXT i bat.laststun = bat.ticks END IF decrement_attack_queue_delays bslot() END SUB SUB do_poison(byval who as integer, bat as BattleState, bslot() as BattleSprite, formdata as Formation) DIM harm as integer harm = bslot(who).stat.max.poison - bslot(who).stat.cur.poison harm = range(harm, 20) quickinflict harm, who, bslot() triggerfade who, bslot() fulldeathcheck -1, bat, bslot(), formdata END SUB SUB do_regen(byval who as integer, bat as BattleState, bslot() as BattleSprite, formdata as Formation) DIM heal as integer heal = bslot(who).stat.max.regen - bslot(who).stat.cur.regen heal = heal * -1 heal = range(heal, 20) quickinflict heal, who, bslot() triggerfade who, bslot() fulldeathcheck -1, bat, bslot(), formdata END SUB SUB decrement_attack_queue_delays(bslot() as BattleSprite) FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used THEN IF .turn_delay = 0 THEN IF bslot(.attacker).stat.cur.stun >= bslot(.attacker).stat.max.stun THEN .delay = large(0, .delay - 1) END IF END IF END IF END WITH NEXT i END SUB SUB battle_animate(byref bat as BattleState, bslot() as BattleSprite) 'This sub is intended to apply animation effects triggered elsewhere. 'FIXME: due to messy code, plenty of animation stuff might still happen elsewhere DIM i as integer '--First, things that only heroes can do FOR i = 0 TO 3 IF bslot(i).walk = 1 THEN bslot(i).frame = bslot(i).frame XOR bat.tog ' Change weak heroes to weak unless the hero is attacking. ' Note that enforce_weak_picture doesn't change from weak to normal frame if ' the hero is not weak; the whole method of deciding .frame is broken. ' enforce_weak_picture is also called at the end of an attack animation script, ' so I don't see under what conditions this check is even needed... a hero is ' injured in the middle of an animation? Battlescripting? IF (bat.atk.id >= 0 AND bat.acting = i) = NO THEN enforce_weak_picture i, bslot(), bat IF bat.vic.state > vicNONE AND bslot(i).stat.cur.hp > 0 AND bat.tog = 0 THEN IF bslot(i).frame = frameVICTORYB THEN bslot(i).frame = frameVICTORYA ELSE bslot(i).frame = frameVICTORYB END IF NEXT i '--Then apply movement forces for all things, heroes, enemies, attacks, weapons FOR i = 0 TO 23 WITH bslot(i) IF .xmov <> 0 THEN .x = .x + (.xspeed * SGN(.xmov)): .xmov = .xmov - SGN(.xmov) IF .ymov <> 0 THEN .y = .y + (.yspeed * SGN(.ymov)): .ymov = .ymov - SGN(.ymov) IF .zmov <> 0 THEN .z = .z + (.zspeed * SGN(.zmov)): .zmov = .zmov - SGN(.zmov) END WITH NEXT i '--then, stuff that only attacks can do FOR i = 12 TO 23 '--for each attack sprite IF bslot(i).vis = 1 THEN WITH bslot(i) .anim_index += 1 '--each pattern ends with a -1. If we have found it, loop around IF bat.animpat(.anim_pattern).frame(.anim_index) = -1 THEN .anim_index = 0 .frame = bat.animpat(.anim_pattern).frame(.anim_index) IF .frame = -1 THEN '--if the frame get set to -1 that indicates an empty pattern, so randomize instead .frame = randint(3) END IF END WITH END IF NEXT i '--Then death animations FOR i = 0 TO 11 IF bslot(i).dissolve > 0 THEN 'ENEMIES DEATH THROES IF is_enemy(i) THEN IF bslot(i).flee THEN 'running away bslot(i).x = bslot(i).x - 10: bslot(i).d = 1 END IF bslot(i).dissolve -= 1 IF bslot(i).dissolve = 0 THEN 'make dead enemy invisible (the check_death code will actually do the final removal, which might happen before the enemy has finished dissolving) bslot(i).vis = 0 END IF END IF IF is_hero(i) THEN bslot(i).frame = frameDEAD END IF NEXT i '--also appear animations for enemies FOR i = 0 TO 11 IF bslot(i).appeartype >= 0 ANDALSO bslot(i).dissolve_appear <= bslot(i).appeartime THEN IF is_enemy(i) THEN bslot(i).dissolve_appear += 1 END IF END IF NEXT i END SUB SUB show_enemy_meters(bat as BattleState, bslot() as BattleSprite, formdata as Formation, page as integer) 'This shows meters and extra debug info info when you press F10 the first time DIM c as integer DIM info as string info = bat.targ.mode & " " & bat.hero_turn & " " & bat.atk.id edgeprint info, 10, 70, uilook(uiText), page FOR i as integer = 0 TO 11 WITH bslot(i) c = uilook(uiSelectedDisabled) IF is_hero(i) THEN c = uilook(uiSelectedItem) rectangle 0, 80 + (i * 10), .ready_meter / 10, 4, c, page info = "hp" & .stat.cur.hp & " tm" & bat.targ.mask(i) & " sr" & (.stat.cur.stun < .stat.max.stun) & " dz" & .dissolve & " a" & .attack & " r" & .ready IF is_enemy(i) THEN info &= " fm" & formdata.slots(i-4).id edgeprint info, 10, 80 + i * 10, c, page END WITH NEXT i END SUB SUB battle_crappy_run_handler(byref bat as BattleState, bslot() as BattleSprite) '--Current running system sucks about as bad as a running system conceivably CAN suck IF carray(ccRun) > 1 AND readbit(gen(), genBits2, 1) = 0 THEN '"Disable ESC to run from battle" off bat.flee = bat.flee + 1 END IF DIM i as integer IF bat.flee > 0 AND bat.flee < 4 THEN IF carray(ccRun) = 0 THEN bat.flee = 0 FOR i = 0 TO 3 bslot(i).d = 0 bslot(i).walk = 0 NEXT i END IF END IF IF bat.flee = 4 THEN IF checkNoRunBit(bslot()) THEN bat.flee = 0 bat.alert = bat.cannot_run_caption bat.alert_ticks = 10 END IF END IF IF bat.flee > 4 THEN FOR i = 0 TO 3 '--if alive turn around IF bslot(i).stat.cur.hp THEN bslot(i).d = 1 bslot(i).walk = 1 bslot(i).attack = 0 bslot(i).ready = NO bslot(i).ready_meter = large(0, bslot(i).ready_meter - bslot(i).stat.cur.spd * 2) NEXT i IF carray(ccRun) = 0 THEN bat.flee = 0 FOR i = 0 TO 3 bslot(i).d = 0 bslot(i).walk = 0 NEXT i END IF DIM stupid_run_threshold as integer = 400 FOR i = 4 TO 11 stupid_run_threshold += bslot(i).stat.cur.spd NEXT i IF randint(stupid_run_threshold) < bat.flee THEN bat.away = 1 bat.flee = 2 FOR i = 0 TO 3 bslot(i).ready_meter = 0 bslot(i).ready = NO NEXT i END IF END IF END SUB SUB draw_battle_sprites(bslot() as BattleSprite, page as integer) DIM zbuf(24) as integer DIM basey(24) as integer DIM harm_text_offset as integer = 0 FOR i as integer = 0 TO 24 basey(i) = bslot(i).y + bslot(i).h NEXT i sort_integers_indices(zbuf(), @basey(0)) FOR i as integer = 0 TO 24 IF (bslot(zbuf(i)).vis = 1 OR bslot(zbuf(i)).dissolve > 0) THEN dim w as BattleSprite ptr w = @bslot(zbuf(i)) with bslot(zbuf(i)) dim spr as frame ptr = .sprites if .sprites = 0 then continue for 'debug(str(zbuf(i))) if .frame < .sprite_num then spr += .frame if .d then spr = frame_duplicate(spr) frame_flip_horiz(spr) else spr = frame_reference(spr) end if if is_enemy(zbuf(i)) andalso .appeartype >= 0 andalso .dissolve_appear <= .appeartime then dim spr2 as frame ptr = spr spr = frame_dissolved(spr2, .appeartime, .appeartime - .dissolve_appear, .appeartype) frame_unload(@spr2) end if if is_enemy(zbuf(i)) andalso .dissolve > 0 andalso .flee = 0 then dim spr2 as frame ptr = spr spr = frame_dissolved(spr2, .deathtime, .deathtime - .dissolve, .deathtype) frame_unload(@spr2) end if frame_draw(spr, .pal, .x, .y - .z, 1, -1, page) frame_unload(@spr) end with END IF NEXT i FOR i as integer = 0 TO 11 WITH bslot(i).harm IF .ticks > 0 THEN IF gen(genDamageDisplayTicks) <> 0 THEN harm_text_offset = gen(genDamageDisplayRise) / gen(genDamageDisplayTicks) * (gen(genDamageDisplayTicks) - .ticks) ELSE harm_text_offset = 0 'Avoid div by zero (which shouldn't be possible anyway) END IF edgeprint .text, .pos.x - LEN(.text) * 4, .pos.y - harm_text_offset, .col, page .ticks -= 1 IF .ticks = 0 THEN .col = uilook(uiText) END IF END WITH NEXT i END SUB SUB battle_loadall(byval form as integer, byref bat as BattleState, bslot() as BattleSprite, st() as HeroDef, formdata as Formation) DIM i as integer LoadFormation formdata, form for i = 0 to 24 bslot(i).frame = frameSTAND bslot(i).sprites = 0 bslot(i).pal = 0 bslot(i).attack = 0 next i IF formdata.music = -1 THEN stopsong IF formdata.music >= 0 THEN wrappedsong formdata.music 'Otherwise formdata.music = -2: same music as map DIM hero_form as HeroFormation load_hero_formation hero_form, formdata.hero_form DIM attack as AttackData FOR i = 0 TO 3 IF gam.hero(i).id >= 0 THEN loadherodata st(i), gam.hero(i).id '--init bslot() for each hero WITH bslot(i) 'hero_form specifies bottom center relative to the hero zone .basex = (240 + hero_form.slots(i).pos.x - 16) .basey = (82 + hero_form.slots(i).pos.y - 40) .x = .basex .y = .basey .w = 32 .h = 40 .vis = 1 'load hero sprites .sprite_num = 8 .sprites = frame_load(sprTypeHero, gam.hero(i).battle_pic) .pal = palette16_load(gam.hero(i).battle_pal, sprTypeHero, gam.hero(i).battle_pic) .frame = frameSTAND .death_sfx = -1 'No death sounds for heroes (for now) .cursorpos.x = .w / 2 .cursorpos.y = 0 .bequesting = NO .self_bequesting = NO populate_battle_menu_menudef i, .batmenu, st(i) .menust.active = YES END WITH '--copy hero's outside-battle stats to their inside-battle stats FOR o as integer = 0 TO statLast bslot(i).stat.cur.sta(o) = gam.hero(i).stat.cur.sta(o) bslot(i).stat.max.sta(o) = gam.hero(i).stat.max.sta(o) 'BattleSprites don't have base stats, though we might want them to have them in future 'if we allow changing equipment during battle NEXT o calc_hero_elementals bslot(i).elementaldmg(), i bslot(i).name = gam.hero(i).name ELSE '--blank empty hero slots bslot(i).sprites = 0 END IF NEXT i '--wipe spells learnt and levels gained for all heroes FOR i = 0 TO UBOUND(gam.hero) FOR o as integer = i * 6 TO i * 6 + 5 learnmask(o) = 0 NEXT gam.hero(i).lev_gain = 0 NEXT '--load monsters FOR i = 0 TO 7 loadfoe i, formdata, bat, bslot(), YES NEXT i FOR i = 0 TO 11 IF readbit(gen(), genBits2, 6) = 0 THEN '"Don't randomize battle ready meters" off bslot(i).ready_meter = randint(500) END IF NEXT i '--size attack sprites FOR i = 12 TO 23 bslot(i).w = 50 bslot(i).h = 50 NEXT i bat.curbg = formdata.background frame_unload @bat.backdrop bat.backdrop = frame_load(sprTypeBackdrop, bat.curbg) '--This checks weak/dead status for heroes '-- who are already weak/dead at the beginning of battle FOR i = 0 TO 3 enforce_weak_picture i, bslot(), bat IF gam.hero(i).id >= 0 AND bslot(i).stat.cur.hp = 0 THEN '--hero starts the battle dead bslot(i).dissolve = 1 'Keeps the dead hero from vanishing bslot(i).frame = frameDEAD END IF bslot(i).lifemeter = (88 / large(bslot(i).stat.max.hp, 1)) * bslot(i).stat.cur.hp NEXT i '--size the weapon sprite bslot(24).w = 24 bslot(24).h = 24 'trigger fades for dead enemies 'fulldeathcheck fades out only enemies set to die without a boss 'so additionally call triggerfade on 0 hp enemies here 'or might that be expected behaviour in some games? FOR i = 4 TO 11 IF bslot(i).stat.cur.hp <= 0 THEN triggerfade i, bslot() END IF NEXT i fulldeathcheck -1, bat, bslot(), formdata END SUB SUB populate_battle_menu_menudef (byval hero_id as integer, menu as MenuDef, hero as HeroDef) DIM hero_node as NodePtr hero_node = hero.reld DIM caption as string DIM k as NodePtr DIM wep_id as integer DIM atk_id as integer DIM spells_id as integer DIM itembuf(dimbinsize(binITM)) as integer ClearMenuData menu WITH menu .offset.x = 152 .offset.y = -95 .anchorhoriz = alignRight .anchorvert = alignTop .textalign = alignLeft .bordersize = -5 .itemspacing = -2 .highlight_selection = YES .min_chars = 10 .maxrows = 23 END WITH READNODE hero_node."battle_menus" as battle_menus WITHNODE battle_menus."menu" as m DIM mitem as integer = -1 k = m."kind".ptr IF k."weapon".exists THEN wep_id = eqstuf(hero_id, 0) - 1 loaditemdata itembuf(), wep_id atk_id = itembuf(48) - 1 IF atk_id < 0 THEN atk_id = 0 caption = readattackname(atk_id) mitem = append_menu_item(menu, caption, batmenu_ATTACK, atk_id) ELSEIF k."attack".exists THEN atk_id = k."attack".integer caption = readattackname(atk_id) mitem = append_menu_item(menu, caption, batmenu_ATTACK, atk_id) ELSEIF k."spells".exists THEN spells_id = k."spells".integer caption = hero.list_name(spells_id) IF LEN(caption) > 0 THEN '--only add the spell list if it is non-empty or if the bitset says we don't care if it is empty IF readbit(hero.bits(),0,26) = 0 ORELSE count_available_spells(hero_id, spells_id) THEN mitem = append_menu_item(menu, caption, batmenu_SPELLS, spells_id) END IF END IF ELSEIF k."items".exists THEN caption = readglobalstring(34, "Item", 10) IF LEN(caption) > 0 THEN mitem = append_menu_item(menu, caption, batmenu_ITEMS) END IF ELSE debug "Unknown battle menu item kind" END IF IF mitem > -1 THEN ' Set up other data WITH *menu.items[mitem] IF LEN(m."caption".string) THEN .caption = m."caption".string .col = m."color".default(0) .tag1 = m."enable_tag1".default(0) .tag2 = m."enable_tag2".default(0) .hide_if_disabled = m."hide_disabled".bool.default(NO) .disabled_overrides_color = YES END WITH END IF END WITHNODE END READNODE END SUB ' Updates which menu items are enabled SUB update_battle_menu (bat as BattleState, bslot() as BattleSprite) WITH bslot(bat.hero_turn) DIM atk as AttackData FOR i as integer = 0 TO .batmenu.numitems - 1 WITH *.batmenu.items[i] IF .t = batmenu_ATTACK THEN .disabled = NO loadattackdata atk, .sub_t ' FIXME: isn't this bitset misnamed, since it affects all battle menu attacks? IF atk.check_costs_as_weapon THEN IF atkallowed(atk, bat.hero_turn, 0, 0, bslot()) = 0 THEN .disabled = YES END IF END IF END IF IF NOT (istag(.tag1, YES) AND istag(.tag2, YES)) THEN .disabled = YES END WITH NEXT i ' Update selection, etc init_menu_state .menust, .batmenu END WITH END SUB SUB fulldeathcheck (byval killing_attack as integer, bat as BattleState, bslot() as BattleSprite, formdata as Formation) '--Runs check_death on all enemies, checks all heroes for death, and sets bat.death_mode if necessary 'killing_attack is the attack ID that was just used, or -1 for none DIM deadguy as integer DIM dead_enemies as integer DIM dead_heroes as integer FOR deadguy = 4 TO 11 IF bslot(deadguy).stat.cur.hp > 0 THEN 'this enemy hasn't just spawned; it should fade out IF dieWOboss(deadguy, bslot()) THEN triggerfade deadguy, bslot() END IF END IF NEXT FOR deadguy = 0 TO 11 check_death deadguy, killing_attack, bat, bslot(), formdata NEXT dead_enemies = 0 FOR deadguy = 4 TO 11 WITH bslot(deadguy) IF (.stat.cur.hp = 0 ANDALSO .bequesting = NO ANDALSO .self_bequesting = NO) OR .hero_untargetable = YES OR .death_unneeded = YES THEN dead_enemies += 1 END WITH NEXT IF dead_enemies >= 8 THEN bat.death_mode = deathENEMIES '--Now check the heroes dead_heroes = 0 FOR deadguy = 0 TO 3 IF bslot(deadguy).stat.cur.hp = 0 THEN dead_heroes += 1 NEXT deadguy IF dead_heroes = 4 THEN bat.death_mode = deathHEROES export_battle_hero_stats bslot() evalherotags END SUB SUB trigger_victory(byref bat as BattleState, bslot() as BattleSprite) '--Play the victory music IF gen(genVictMus) > 0 THEN wrappedsong gen(genVictMus) - 1 '--Collect gold (which is capped at 2 billion max) gold = gold + bat.rew.plunder IF gold < 0 OR gold > 2000000000 THEN gold = 2000000000 '--Give out experience export_battle_hero_stats bslot() 'Needed because distribute_party_experience() might level-up some heroes bat.rew.exper = distribute_party_experience(bat.rew.exper) import_battle_hero_stats bslot() 'Needed because we will likely be calling export_battle_hero_stats() again later '--Trigger the display of end-of-battle rewards bat.vic.state = vicGOLDEXP bat.vic.display_ticks = 0 END SUB FUNCTION distribute_party_experience (byval exper as integer) as integer '--Calculate amount of experience given to each hero, and returns the "base" experience value, '--which is the amount given to live heroes in the active party DIM as double sumheroes, xp_mult(UBOUND(gam.hero)) DIM as integer i FOR i = 0 TO UBOUND(gam.hero) IF gam.hero(i).id >= 0 THEN IF gam.hero(i).stat.cur.hp > 0 OR readbit(gen(), genBits2, 3) THEN 'alive, or "dead heroes get experience" ON IF i <= 3 THEN 'active party xp_mult(i) = 1.0 ELSEIF gam.hero(i).locked THEN 'hero is in reserve, locked xp_mult(i) = gen(genLockedReserveXP) / 100.0 ELSE 'hero is in reserve, unlocked xp_mult(i) = gen(genUnlockedReserveXP) / 100.0 END IF END IF END IF sumheroes += xp_mult(i) NEXT 'debug "distribute_party_experience: exper = " & exper & " sumheroes = " & sumheroes 'if there is more than one hero, and "Don't divide experience between heroes" is off, ' then divide the experience between the heroes IF sumheroes > 0 ANDALSO readbit(gen(), genBits2, 14) = 0 THEN exper = exper / sumheroes FOR i = 0 TO UBOUND(gam.hero) IF gam.hero(i).id >= 0 THEN giveheroexperience i, xp_mult(i) * exper 'debug "hero " & i & " got " & CINT(xp_mult(i) * exper) updatestatslevelup i, NO END IF NEXT RETURN exper END FUNCTION FUNCTION should_victory_advance(byref bat as BattleState) as bool IF carray(ccUse) > 1 ORELSE carray(ccMenu) > 1 THEN RETURN YES IF gen(genSkipBattleRewardsTicks) > 0 THEN IF bat.vic.display_ticks > gen(genSkipBattleRewardsTicks) THEN bat.vic.display_ticks = 0 RETURN YES END IF END IF RETURN NO END FUNCTION SUB show_victory (byref bat as BattleState, bslot() as BattleSprite, page as integer) DIM tempstr as string DIM as integer i, o IF bat.vic.box THEN centerfuz 160, 30, 280, 50, 1, page bat.vic.display_ticks += 1 SELECT CASE bat.vic.state CASE vicGOLDEXP '--print acquired gold and experience IF bat.rew.plunder > 0 OR bat.rew.exper > 0 THEN bat.vic.box = YES: centerfuz 160, 30, 280, 50, 1, page IF bat.rew.plunder > 0 THEN tempstr = bat.vic.gold_caption & " " & bat.rew.plunder & " " & bat.vic.gold_name & "!" edgeprint tempstr, pCentered, 16, uilook(uiText), page END IF IF bat.rew.exper > 0 THEN tempstr = bat.vic.exp_caption & " " & bat.rew.exper & " " & bat.vic.exp_name & "!" edgeprint tempstr, pCentered, 28, uilook(uiText), page END IF IF should_victory_advance(bat) ORELSE (bat.rew.plunder = 0 ANDALSO bat.rew.exper = 0) THEN bat.vic.state = vicLEVELUP END IF CASE vicLEVELUP '--print levelups o = 0 DIM numlevelled as integer = 0 FOR i = 0 TO UBOUND(gam.hero) IF gam.hero(i).lev_gain <> 0 THEN numlevelled += 1 NEXT IF numlevelled THEN numlevelled = large(numlevelled, 4) centerfuz 160, 10 + 5 * numlevelled, 280, 10 + 10 * numlevelled, 1, page bat.vic.box = YES END IF FOR i = 0 TO UBOUND(gam.hero) SELECT CASE gam.hero(i).lev_gain CASE 1 tempstr = bat.vic.level_up_caption & " " & gam.hero(i).name CASE IS > 1 tempstr = gam.hero(i).lev_gain & " " & bat.vic.levels_up_caption & " " & gam.hero(i).name END SELECT IF gam.hero(i).lev_gain > 0 THEN edgeprint tempstr, pCenteredLeft, 12 + o * 10, uilook(uiText), page o += 1 END IF NEXT i IF o = 0 ORELSE should_victory_advance(bat) THEN bat.vic.state = vicSPELLS bat.vic.showlearn = NO bat.vic.learnwho = 0 bat.vic.learnlist = 0 bat.vic.learnslot = -1 END IF CASE vicSPELLS '--print learned spells, one at a time IF bat.vic.showlearn = NO THEN '--Not showing a spell yet. find the next one DO bat.vic.learnslot += 1 IF bat.vic.learnslot > 23 THEN bat.vic.learnslot = 0: bat.vic.learnlist = bat.vic.learnlist + 1 IF bat.vic.learnlist > 3 THEN bat.vic.learnlist = 0: bat.vic.learnwho = bat.vic.learnwho + 1 IF bat.vic.learnwho > UBOUND(gam.hero) THEN ' We have iterated through all spell lists for all heroes, time to move on bat.vic.state = vicITEMS bat.vic.display_ticks = 0 bat.vic.item_name = "" bat.vic.found_index = 0 bat.vic.box = NO EXIT DO END IF IF readbit(learnmask(), 0, bat.vic.learnwho * 96 + bat.vic.learnlist * 24 + bat.vic.learnslot) THEN 'found a learned spell DIM learn_attack as AttackData loadattackdata learn_attack, spell(bat.vic.learnwho, bat.vic.learnlist, bat.vic.learnslot) - 1 bat.vic.item_name = gam.hero(bat.vic.learnwho).name + bat.vic.learned_caption + learn_attack.name bat.vic.showlearn = YES bat.vic.box = YES IF learn_attack.learn_sound_effect > 0 THEN playsfx learn_attack.learn_sound_effect - 1 EXIT DO END IF LOOP ELSE' Found a learned spell to display, show it until a keypress IF should_victory_advance(bat) THEN bat.vic.showlearn = NO ' hide the display (which causes us to search for the next learned spell) END IF edgeprint bat.vic.item_name, pCenteredLeft, 22, uilook(uiText), page END IF CASE vicITEMS '--print found items, one at a time '--check to see if we are currently displaying a gotten item IF bat.vic.item_name = "" THEN '--if not, check to see if there are any more gotten items to display IF bat.rew.found(bat.vic.found_index).num = 0 THEN bat.vic.state = vicEXITDELAY bat.vic.display_ticks = 0 EXIT SUB END IF '--get the item name bat.vic.item_name = readitemname(bat.rew.found(bat.vic.found_index).id) '--actually acquire the item getitem bat.rew.found(bat.vic.found_index).id, bat.rew.found(bat.vic.found_index).num END IF '--if the present item is gotten, show the caption IF bat.rew.found(bat.vic.found_index).num = 1 THEN tempstr = bat.vic.item_caption & " " & bat.vic.item_name ELSE tempstr = bat.vic.plural_item_caption & " " & bat.rew.found(bat.vic.found_index).num & " " & bat.vic.item_name END IF IF LEN(tempstr) THEN centerfuz 160, 30, 280, 50, 1, page edgeprint tempstr, pCenteredLeft, 22, uilook(uiText), page '--check for a keypress IF should_victory_advance(bat) THEN IF bat.rew.found(bat.vic.found_index).num = 0 THEN '--if there are no further items, exit bat.vic.state = vicEXITDELAY ELSE '--otherwize, increment the findpointer and reset the caption bat.vic.found_index = bat.vic.found_index + 1 bat.vic.item_name = "" END IF END IF END SELECT END SUB SUB reset_battle_state (byref bat as BattleState) 'This could become a constructor for BattleState when we support the -lang fb dialect WITH bat .ticks = 0 WITH .inv_scroll .first = 0 .last = INT(last_inv_slot() / 3) .size = 8 END WITH WITH .inv_scroll_rect .x = 20 .y = 5 .wide = 292 '.high set later END WITH .anim_ready = NO .wait_frames = 0 .level_mp_caption = readglobalstring(160, "Level MP", 20) .cancel_spell_caption = readglobalstring(51, "(CANCEL)", 10) .cannot_run_caption = readglobalstring(147, "CANNOT RUN!", 20) .caption_time = 0 .caption_delay = 0 .caption = "" .alert_ticks = 0 .alert = "" .acting = 0 .hero_turn = -1 .enemy_turn = -1 .next_hero = 0 .next_enemy = 0 .menu_mode = batMENUHERO .laststun = 0 END WITH reset_targetting bat reset_attack bat reset_victory_state bat.vic reset_rewards_state bat.rew END SUB SUB reset_targetting (byref bat as BattleState) WITH bat.targ .hit_dead = NO END WITH END SUB SUB reset_attack (byref bat as BattleState) DIM i as integer WITH bat.atk .id = -1 .was_id = -1 .non_elemental = NO FOR i = 0 TO UBOUND(.elemental) .elemental(i) = NO NEXT i END WITH END SUB SUB reset_rewards_state (byref rew as RewardsState) 'This could become a constructor for RewardsState when we support the -lang fb dialect DIM i as integer WITH rew .plunder = 0 .exper = 0 FOR i = 0 TO UBOUND(.found) .found(i).id = 0 .found(i).num = 0 NEXT i END WITH END SUB SUB reset_victory_state (byref vic as VictoryState) 'This could become a constructor for VictoryState when we support the -lang fb dialect WITH vic .state = 0 .box = NO .showlearn = NO .learnwho = 0 .learnlist = 0 .learnslot = -1 .item_name = "" .found_index = 0 '--Cache some global strings here .gold_caption = readglobalstring(125, "Found", 10) .exp_caption = readglobalstring(126, "Gained", 10) .item_caption = readglobalstring(139, "Found a", 20) .plural_item_caption = readglobalstring(141, "Found", 20) .gold_name = readglobalstring(32, "$", 10) .exp_name = readglobalstring(33, "Experience", 10) .level_up_caption = readglobalstring(149, "Level up for", 20) .levels_up_caption = readglobalstring(151, "levels for", 20) .learned_caption = " " + readglobalstring(124, "learned", 10) + " " END WITH END SUB SUB checkitemusability(iuse() as integer, bslot() as BattleSprite, byval who as integer) 'Iterate through the iuse() bitfield and mark any items that are usable DIM i as integer DIM itembuf(dimbinsize(binITM)) as integer DIM attack as AttackData FOR i = 0 TO inventoryMax setbit iuse(), 0, i, 0 ' Default each slot to unusable IF inventory(i).used THEN loaditemdata itembuf(), inventory(i).id IF itembuf(47) > 0 THEN ' This item is usable in battle loadattackdata attack, itembuf(47) - 1 IF attack.check_costs_as_item THEN '--This attack has the bitset that requires cost checking when used from an item IF atkallowed(attack, who, 0, 0, bslot()) THEN setbit iuse(), 0, i, 1 END IF ELSE '--No cost checking for this item setbit iuse(), 0, i, 1 END IF END IF END IF NEXT i END SUB FUNCTION checkNoRunBit (bslot() as BattleSprite) as integer DIM i as integer FOR i = 4 TO 11 IF bslot(i).stat.cur.hp > 0 AND bslot(i).vis = 1 AND bslot(i).unescapable = YES THEN RETURN 1 NEXT i RETURN 0 END FUNCTION SUB checkTagCond (byref t as AttackDataTag, byval check as AttackTagConditionEnum) 't.condition - type 'check = condition type 't.tag - the tag to be set 't.tagcheck - the tag to check IF t.condition = check THEN IF t.tagcheck <> 0 THEN IF NOT istag(t.tagcheck, 0) THEN EXIT SUB END IF settag t.tag 'Set the original damned tag! END IF END SUB SUB calc_hero_elementals (elemental_resists() as single, byval who as integer) 'Calculate a hero's elemental resists after taking equipment into account 'elemental_resists() (the destination) should be sized up to at least gen(genNumElements) - 1; 'who is a hero slot number. 'This is used both here and in the status menu. REDIM itemelementals() as single DIM allelementals(5, gen(genNumElements) - 1) as single DIM oneelement(5) as single DIM _NaN as single = 0.0f _NaN = 0.0f/_NaN '--first load data into allelementals() '--get native hero resistances FOR i as integer = 0 TO gen(genNumElements) - 1 allelementals(0, i) = gam.hero(who).elementals(i) NEXT i '--data from all equipped items fill rest of matrix FOR j as integer = 0 TO 4 IF eqstuf(who, j) > 0 THEN LoadItemElementals eqstuf(who, j) - 1, itemelementals() FOR i as integer = 0 TO gen(genNumElements) - 1 allelementals(j + 1, i) = itemelementals(i) NEXT i ELSE FOR i as integer = 0 TO gen(genNumElements) - 1 allelementals(j + 1, i) = _NaN NEXT i END IF NEXT j '--transpose the matrix (I wish), and merge equipment elemental resists FOR i as integer = 0 TO gen(genNumElements) - 1 FOR j as integer = 0 TO 5 oneelement(j) = allelementals(j, i) NEXT j elemental_resists(i) = equip_elemental_merge(oneelement(), gen(genEquipMergeFormula)) NEXT i END SUB SUB invertstack '--this is a hack so I can use the stack like a fifo DIM i as integer DIM stackdepth as integer stackdepth = stackpos() - bstackstart FOR i = 0 TO stackdepth - 1 buffer(i) = popdw NEXT i FOR i = 0 TO stackdepth - 1 pushdw buffer(i) NEXT i END SUB SUB quickinflict (byval harm as integer, byval targ as integer, bslot() as BattleSprite) '--quick damage infliction to hp. no bells and whistles DIM max_bound as integer WITH bslot(targ) IF gen(genDamageCap) > 0 THEN harm = small(harm, gen(genDamageCap)) .harm.ticks = gen(genDamageDisplayTicks) .harm.pos.x = .x + (.w * .5) .harm.pos.y = .y + (.h * .5) IF harm < 0 THEN .harm.text = "+" & ABS(harm) ELSE .harm.text = STR(harm) END IF max_bound = large(.stat.cur.hp, .stat.max.hp) .stat.cur.hp = bound(.stat.cur.hp - harm, 0, max_bound) END WITH END SUB SUB anim_end() pushdw 0 END SUB SUB anim_inflict(byval who as integer, byval target_count as integer) pushdw 10: pushdw who: pushdw target_count END SUB SUB anim_disappear(byval who as integer) pushdw 6: pushdw who END SUB SUB anim_setpos(byval who as integer, byval x as integer, byval y as integer, byval direction as integer) pushdw 3: pushdw who: pushdw x: pushdw y: pushdw direction END SUB SUB anim_setz(byval who as integer, byval z as integer) pushdw 11: pushdw who: pushdw z END SUB SUB anim_appear(byval who as integer) pushdw 5: pushdw who END SUB ' For ABS(xmove_ticks) ticks, move xstep pixels in the direction SGN(xmove_ticks), and ditto for ymove_ticks/ystep. SUB anim_setmove(who as integer, xmove_ticks as integer, ymove_ticks as integer, xstep as integer, ystep as integer) pushdw 2: pushdw who: pushdw xmove_ticks: pushdw ymove_ticks: pushdw xstep: pushdw ystep END SUB ' Move to an X position in ABS(xticks) ticks, ditto for Z. SUB anim_absmove(byval who as integer, byval tox as integer, byval toy as integer, byval xticks as integer, byval yticks as integer) pushdw 8: pushdw who: pushdw tox: pushdw toy: pushdw xticks: pushdw yticks END SUB ' Move to an Z position in ABS(zticks) ticks. SUB anim_abszmove(byval who as integer, byval toz as integer, byval zticks as integer) pushdw 24: pushdw who: pushdw toz: pushdw zticks END SUB ' For ABS(zticks) ticks, move zstep pixels in the direction SGN(zticks) SUB anim_zmove(byval who as integer, byval zticks as integer, byval zstep as integer) pushdw 15: pushdw who: pushdw zticks: pushdw zstep END SUB SUB anim_wait(byval ticks as integer) pushdw 13: pushdw ticks END SUB SUB anim_setframe(byval who as integer, byval frame as integer) pushdw 7: pushdw who: pushdw frame END SUB SUB anim_waitforall() pushdw 9 END SUB SUB anim_walktoggle(byval who as integer) pushdw 14: pushdw who END SUB SUB anim_sound(byval which as integer) pushdw 16: pushdw which END SUB SUB anim_align(byval who as integer, byval target as integer, byval dire as integer, byval offset as integer) pushdw 17: pushdw who: pushdw target: pushdw dire: pushdw offset END SUB ' Position 'who' is its center matches with 'target', plus an offset. ' Note: this doesn't make total sense, because it doesn't use Z position, so may be behind or infront. SUB anim_setcenter(byval who as integer, byval target as integer, byval offx as integer, byval offy as integer) pushdw 18: pushdw who: pushdw target: pushdw offx: pushdw offy END SUB SUB anim_align2(byval who as integer, byval target as integer, byval edgex as integer, byval edgey as integer, byval offx as integer, byval offy as integer) pushdw 19: pushdw who: pushdw target: pushdw edgex: pushdw edgey: pushdw offx: pushdw offy END SUB ' Move relative from the current X position in ABS(xticks) ticks, ditto for Y. SUB anim_relmove(byval who as integer, byval byx as integer, byval byy as integer, byval xticks as integer, byval yticks as integer) pushdw 20: pushdw who: pushdw byx: pushdw byy: pushdw xticks: pushdw yticks END SUB SUB anim_setdir(byval who as integer, byval d as integer) pushdw 21: pushdw who: pushdw d END SUB SUB anim_skip_ahead_if_targetless() pushdw 22 END SUB SUB anim_skip_to_here() 'Place markers to look for later pushdw 23: pushdw ANIM_SKIP_SENTINEL: pushdw ANIM_SKIP_SENTINEL END SUB SUB anim_flinchstart(byval who as integer, bslot() as BattleSprite, byref attack as AttackData) '--If enemy can flinch and if attack allows flinching IF bslot(who).never_flinch = NO AND attack.targ_does_not_flinch = NO THEN DIM flinch_x_dist as integer flinch_x_dist = 3 IF is_enemy(who) THEN flinch_x_dist = -3 anim_setmove who, flinch_x_dist, 0, 2, 0 IF is_hero(who) THEN IF attack.cure_instead_of_harm = NO THEN '--Show Harmed frame anim_setframe who, frameHURT ELSE '--Show attack1 frame when healed anim_setframe who, frameATTACKA END IF END IF END IF END SUB SUB anim_flinchdone(byval who as integer, bslot() as BattleSprite, byref attack as AttackData) '--If enemy can flinch and if attack allows flinching IF bslot(who).never_flinch = NO AND attack.targ_does_not_flinch = NO THEN DIM flinch_x_dist as integer flinch_x_dist = -3 IF is_enemy(who) THEN flinch_x_dist = 3 anim_setmove who, flinch_x_dist, 0, 2, 0 anim_setframe who, frameSTAND END IF END SUB FUNCTION count_dissolving_enemies(bslot() as BattleSprite) as integer 'Counts enemies that are in the process of dying, but have not finished dying yet 'This includes both dissolve animations and on-death bequest attacks DIM i as integer DIM count as integer = 0 FOR i = 4 TO 11 IF bslot(i).dissolve > 0 ORELSE bslot(i).bequesting THEN count += 1 NEXT i RETURN count END FUNCTION SUB spawn_on_death(byval deadguy as integer, byval killing_attack as integer, byref bat as BattleState, formdata as Formation, bslot() as BattleSprite) 'killing_attack is the id of the attack that killed the target or -1 if the target died without a specific attack DIM attack as AttackData DIM slot as integer DIM i as integer IF NOT is_enemy(deadguy) THEN EXIT SUB ' Only works for enemies IF killing_attack >= 0 THEN 'This death is the result of an attack loadattackdata attack, killing_attack IF attack.no_spawn_on_kill <> 0 THEN 'This attack had the "Do not trigger spawning on death" bitset EXIT SUB END IF END IF WITH bslot(deadguy) IF .enemy.spawn.non_elemental_death > 0 AND bat.atk.non_elemental = YES THEN ' spawn on non-elemental death FOR i = 1 TO .enemy.spawn.how_many slot = find_empty_enemy_slot(formdata) IF slot > -1 THEN formdata.slots(slot).id = .enemy.spawn.non_elemental_death - 1 loadfoe(slot, formdata, bat, bslot()) END IF NEXT i .enemy.spawn.non_elemental_death = 0 END IF IF .enemy.spawn.on_death > 0 THEN ' spawn on death FOR i = 1 TO .enemy.spawn.how_many slot = find_empty_enemy_slot(formdata) IF slot > -1 THEN formdata.slots(slot).id = .enemy.spawn.on_death - 1 loadfoe(slot, formdata, bat, bslot()) END IF NEXT i .enemy.spawn.on_death = 0 END IF END WITH END SUB FUNCTION find_empty_enemy_slot(formdata as Formation) as integer 'Returns index of first empty slot, or -1 if none was found DIM i as integer FOR i = 0 TO 7 IF formdata.slots(i).id = -1 THEN RETURN i NEXT i RETURN -1 END FUNCTION FUNCTION dieWOboss(byval who as integer, bslot() as BattleSprite) as integer DIM as integer j '--count bosses FOR j = 4 TO 11 '--is it a boss? IF bslot(j).is_boss = YES THEN '-- is it alive? IF bslot(j).stat.cur.hp > 0 THEN RETURN NO END IF END IF NEXT j '--if there are no bossess... '--should it die without a boss? IF bslot(who).die_without_boss = YES THEN bslot(who).stat.cur.hp = 0 RETURN YES END IF END FUNCTION SUB triggerfade(byval who as integer, bslot() as BattleSprite) 'If the target is really dead... IF bslot(who).stat.cur.hp = 0 THEN IF is_hero(who) THEN '--for heroes, a .dissolve > 1 is used to trigger the animate code ' to force the hero to use their death frame bslot(who).dissolve = 1 END IF IF is_enemy(who) THEN 'Don't restart the dissolve animation when hitting an enemy multiple times IF bslot(who).dissolve > 0 OR bslot(who).vis = 0 THEN EXIT SUB bslot(who).dissolve = bslot(who).deathtime '--flee as alternative to death IF bslot(who).flee_instead_of_die = YES THEN bslot(who).flee = 1 'the number of ticks it takes an enemy to run away is based on its distance 'from the left side of the screen and its width. Enemys flee at 10 pixels per tick bslot(who).dissolve = (bslot(who).w + bslot(who).x) / 10 END IF END IF END IF END SUB FUNCTION is_among_targets(byval slot as integer, t() as integer) as bool FOR i as integer = 0 TO UBOUND(t) IF t(i) > -1 ANDALSO t(i) = slot THEN RETURN YES NEXT i RETURN NO END FUNCTION SUB check_death(byval deadguy as integer, byval killing_attack as integer, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) 'killing_attack contains attack id or -1 when no attack is relevant. IF is_enemy(deadguy) THEN '--Ignore empty enemy slots. IF formdata.slots(deadguy - 4).id = -1 THEN EXIT SUB END IF '--Check to see if this is an already-dead enemy doing a bequest attack DIM already_bequested as integer = NO IF bslot(deadguy).bequesting = YES THEN IF has_queued_attacks(deadguy) THEN EXIT SUB END IF bslot(deadguy).bequesting = NO already_bequested = YES triggerfade deadguy, bslot() END IF IF bslot(deadguy).self_bequesting THEN 'Must wait until self-bequest attack is completely finished EXIT SUB END IF '--Do not proceed unless the target is dead IF bslot(deadguy).stat.cur.hp > 0 THEN EXIT SUB '--deadguy is really dead (includes already dead and empty hero slots??) 'Death animation is not done yet here, so be cautious about what gets cleaned up here. 'Full cleanup of bslot() records belongs in loadfoe bslot(deadguy).vis = 0 bslot(deadguy).ready = NO bslot(deadguy).attack = 0 bslot(deadguy).d = 0 '--reset poison/regen/stun/mute WITH bslot(deadguy).stat .cur.poison = .max.poison .cur.regen = .max.regen .cur.stun = .max.stun .cur.mute = .max.mute END WITH '-- remove any attack queue entries FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used ANDALSO .attacker = deadguy THEN clear_attack_queue_slot i END IF END WITH NEXT i '-- if it is a dead hero's turn, cancel menu IF bat.hero_turn = deadguy THEN bat.hero_turn = -1 bat.menu_mode = batMENUHERO bat.targ.mode = targNONE END IF '-- if it is a dead enemy's turn, cancel ai IF bat.enemy_turn = deadguy THEN bat.enemy_turn = -1 IF is_enemy(deadguy) ANDALSO bslot(deadguy).enemy.bequest_attack > 0 ANDALSO NOT already_bequested THEN 'Trigger an on-death bequest attack, and defer dying until later. DIM beq_t(11) as integer IF autotarget(deadguy, bslot(deadguy).enemy.bequest_attack - 1, bslot(), beq_t()) THEN '--Bequest attack only happens if it has a valid target WITH bslot(deadguy) .bequesting = YES .self_bequesting = is_among_targets(deadguy, beq_t()) .vis = 1 .dissolve = 0 .flee = 0 END WITH END IF EXIT SUB END IF '------PLUNDER AND EXPERIENCE AND ITEMS------ IF is_enemy(deadguy) THEN IF bslot(deadguy).death_sfx = 0 THEN IF gen(genDefaultDeathSFX) > 0 THEN playsfx gen(genDefaultDeathSFX) - 1 END IF ELSEIF bslot(deadguy).death_sfx > 0 THEN playsfx bslot(deadguy).death_sfx - 1 END IF dead_enemy deadguy, killing_attack, bat, bslot(), formdata END IF '------------END PLUNDER------------------- battle_reevaluate_dead_targets deadguy, bat, bslot() END SUB SUB dead_enemy(byval deadguy as integer, byval killing_attack as integer, byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) '--give rewards, spawn enemies, clear formdata slot, but NO other cleanup! 'killing_attack is the id of the attack that killed the target or -1 if the target died without a specific attack DIM as integer j DIM enemynum as integer = deadguy - 4 '--spawn enemies before freeing the formdata slot to avoid infinite loops. however this might need to be changed to fix morphing enemies? spawn_on_death deadguy, killing_attack, bat, formdata, bslot() IF formdata.slots(enemynum).id > -1 THEN WITH bslot(deadguy) bat.rew.plunder += .enemy.reward.gold bat.rew.exper += .enemy.reward.exper IF bat.rew.exper > 1000000 THEN bat.rew.exper = 1000000 '--this one million limit is totally arbitrary IF randint(100) < .enemy.reward.item_rate THEN '---GET ITEMS FROM FOES----- FOR j = 0 TO 16 IF bat.rew.found(j).num = 0 THEN bat.rew.found(j).id = .enemy.reward.item: bat.rew.found(j).num = 1: EXIT FOR IF bat.rew.found(j).id = .enemy.reward.item THEN bat.rew.found(j).num = bat.rew.found(j).num + 1: EXIT FOR NEXT j ELSE '------END NORMAL ITEM--------------- IF randint(100) < .enemy.reward.rare_item_rate THEN FOR j = 0 TO 16 IF bat.rew.found(j).num = 0 THEN bat.rew.found(j).id = .enemy.reward.rare_item: bat.rew.found(j).num = 1: EXIT FOR IF bat.rew.found(j).id = .enemy.reward.rare_item THEN bat.rew.found(j).num = bat.rew.found(j).num + 1: EXIT FOR NEXT j END IF '---END RARE ITEM------------- END IF '----END GET ITEMS---------------- END WITH END IF ' remove dead enemy from formation formdata.slots(enemynum).id = -1 END SUB FUNCTION enemy_is_weak(byval who as integer, bslot() as BattleSprite) as bool DIM weakhp as integer = gen(genEnemyWeakHP) IF bslot(who).stat.cur.hp < 0.01 * bslot(who).stat.max.hp * weakhp THEN RETURN YES RETURN NO END FUNCTION SUB enemy_ai (byref bat as BattleState, bslot() as BattleSprite, formdata as Formation) DIM ai as integer = 0 'if HP is less than the threshold, go into desperation mode IF enemy_is_weak(bat.enemy_turn, bslot()) THEN ai = 1 'if targetable enemy count is 1, go into alone mode IF targenemycount(bslot(), YES) = 1 THEN ai = 2 DIM slot as integer = 0 'spawn allies when alone WITH bslot(bat.enemy_turn) IF ai = 2 AND .enemy.spawn.when_alone > 0 THEN FOR j as integer = 1 TO .enemy.spawn.how_many slot = find_empty_enemy_slot(formdata) IF slot > -1 THEN formdata.slots(slot).id = .enemy.spawn.when_alone - 1 loadfoe slot, formdata, bat, bslot() END IF NEXT j END IF END WITH 'make sure that the current ai set is valid 'otherwise fall back on another IF countai(ai, bat.enemy_turn, bslot()) = 0 THEN ai = 0 IF enemy_is_weak(bat.enemy_turn, bslot()) THEN ai = 1 IF countai(ai, bat.enemy_turn, bslot()) = 0 THEN ai = 0 END IF END IF 'if no valid ai set is available, the enemy loses its turn IF countai(ai, bat.enemy_turn, bslot()) = 0 THEN bat.enemy_turn = -1 : EXIT SUB 'pick a random attack DIM atk_id as integer DIM atk as AttackData DIM safety as integer = 0 DO WITH bslot(bat.enemy_turn) SELECT CASE ai CASE 0: atk_id = .enemy.regular_ai(randint(5)) CASE 1: atk_id = .enemy.desperation_ai(randint(5)) CASE 2: atk_id = .enemy.alone_ai(randint(5)) END SELECT END WITH IF atk_id > 0 THEN 'load the data for this attack loadattackdata atk, atk_id - 1 IF atkallowed(atk, bat.enemy_turn, 0, 0, bslot()) THEN 'this attack is good, continue on to target selection EXIT DO ELSE 'this attack is unusable atk_id = 0 IF bslot(bat.enemy_turn).stat.cur.mp - atk.mp_cost < 0 THEN 'inadequate MP was the reason for the failure 'MP-idiot loses its turn IF bslot(bat.enemy_turn).mp_idiot = YES THEN bslot(bat.enemy_turn).ready = NO bslot(bat.enemy_turn).ready_meter = 0 bat.enemy_turn = -1 EXIT SUB END IF END IF 'currently, item requirements are disregarded. should they be? Maybe they should 'come out of theft items? END IF END IF safety += 1 IF safety > 99 THEN 'give up eventually debug "Enemy AI safety Warning: enemy " & bat.enemy_turn & " gave up after " & safety & " tries" bat.enemy_turn = -1 EXIT SUB END IF LOOP IF bat.turn.mode = turnACTIVE THEN update_turn_delays_in_attack_queue bat.hero_turn END IF autotarget bat.enemy_turn, atk, bslot() IF bat.turn.mode = turnACTIVE THEN bslot(bat.enemy_turn).active_turn_num += 1 'debug "Enemy " & bat.enemy_turn & " " & bslot(bat.enemy_turn).name & " turn #" & bslot(bat.enemy_turn).active_turn_num END IF 'ready for next attack bslot(bat.enemy_turn).ready = NO bslot(bat.enemy_turn).ready_meter = 0 bat.enemy_turn = -1 END SUB SUB heromenu (byref bat as BattleState, bslot() as BattleSprite, st() as herodef) WITH bslot(bat.hero_turn) IF .stat.cur.stun < .stat.max.stun THEN 'Normally a hero cannot get a turn when stunned, but this extra check is needed 'for the race condition that happens when a hero gets stunned while it is their tirn in turnACTIVE mode bat.next_hero = bat.hero_turn bat.hero_turn = -1 EXIT SUB END IF IF carray(ccMenu) > 1 THEN '--skip turn 'debug "Requested to cancel picking attacks" bat.next_hero = bat.hero_turn bat.hero_turn = -1 IF bat.turn.mode = turnTURN THEN '--wait mode switches into reverse bat.turn.reverse = YES END IF 'Don't loop the sound effect while holding down Menu to allow time to pass IF carray(ccMenu) AND 4 THEN menusound gen(genCancelSFX) EXIT SUB END IF usemenusounds usemenu .menust IF carray(ccUse) > 1 THEN '--use menu item menusound gen(genAcceptSFX) WITH *.batmenu.items[.menust.pt] IF NOT .disabled THEN SELECT CASE .t CASE batmenu_ATTACK: bslot(bat.hero_turn).attack = .sub_t + 1 bat.targ.mode = targSETUP flusharray carray(), 7, 0 EXIT SUB CASE batmenu_SPELLS: bat.listslot = .sub_t init_spell_menu bat, bslot(), st() CASE batmenu_ITEMS: bat.menu_mode = batMENUITEM bat.item.pt = 0 bat.item.top = 0 checkitemusability bat.iuse(), bslot(), bat.hero_turn bat.item_desc = "" IF inventory(bat.item.pt).used THEN bat.item_desc = readitemdescription(inventory(bat.item.pt).id) END IF END SELECT END IF END WITH END IF END WITH END SUB SUB init_spell_menu (bat as BattleState, bslot() as BattleSprite, st() as HeroDef) DIM atk as AttackData DIM spellcount as integer = 0 DIM i as integer IF st(bat.hero_turn).list_type(bat.listslot) < 2 THEN '--the type of this spell list is one that displays a menu '--init spell menu bat.menu_mode = batMENUSPELL bat.sptr = 0 FOR i = 0 TO 23 '-- loop through the spell list setting up menu items for each WITH bat.spell.slot(i) .name = "" .desc = "" .cost = "" .atk_id = -1 END WITH bat.spell.slot(i).enable = NO IF spell(bat.hero_turn, bat.listslot, i) > 0 THEN '--there is a spell in this slot spellcount += 1 bat.spell.slot(i).atk_id = spell(bat.hero_turn, bat.listslot, i) - 1 loadattackdata atk, bat.spell.slot(i).atk_id bat.spell.slot(i).name = atk.name bat.spell.slot(i).desc = atk.description bat.spell.slot(i).cost = bslot_attack_cost_info(bslot(), atk, bat.hero_turn, st(bat.hero_turn).list_type(bat.listslot), INT(i / 3) + 1) IF atkallowed(atk, bat.hero_turn, st(bat.hero_turn).list_type(bat.listslot), INT(i / 3), bslot()) THEN '-- check whether or not the spell is allowed bat.spell.slot(i).enable = YES END IF END IF bat.spell.slot(i).name = rpad(bat.spell.slot(i).name, " ", 10) '-- pad the spell menu caption NEXT i ELSEIF st(bat.hero_turn).list_type(bat.listslot) = 2 THEN '-- this is a random spell list spellcount = 0 FOR i = 0 TO 23 '-- loop through the spell list storing attack ID numbers bat.spell.slot(i).atk_id = -1 IF spell(bat.hero_turn, bat.listslot, i) > 0 THEN '--there is a spell in this slot spellcount += 1 bat.spell.slot(i).atk_id = spell(bat.hero_turn, bat.listslot, i) - 1 END IF NEXT i IF spellcount > 0 THEN '-- don't attempt to pick randomly from an empty list DIM safety as integer DIM rptr as integer rptr = randint(24) FOR i = 0 TO randint(spellcount) safety = 0 DO rptr = loopvar(rptr, 0, 23, 1) safety += 1 LOOP UNTIL bat.spell.slot(rptr).atk_id > -1 OR safety > 999 '--loop until we have found a spell (or give up after 999 tries) NEXT i bslot(bat.hero_turn).attack = bat.spell.slot(rptr).atk_id + 1 bat.targ.mode = targSETUP flusharray carray(), 7, 0 END IF END IF END SUB SUB spellmenu (byref bat as BattleState, st() as HeroDef, bslot() as BattleSprite) IF carray(ccMenu) > 1 THEN '--cancel bat.menu_mode = batMENUHERO flusharray carray(), 7, 0 menusound gen(genCancelSFX) EXIT SUB END IF WITH bat usemenusounds IF carray(ccUp) > 1 THEN IF .sptr > 2 THEN .sptr -= 3 ELSE .sptr = 24 END IF IF carray(ccDown) > 1 THEN IF .sptr < 24 THEN .sptr = small(.sptr + 3, 24) ELSE .sptr = 0 END IF IF keyval(scHome) > 1 THEN .sptr = 0 END IF IF keyval(scEnd) > 1 THEN .sptr = 24 END IF IF keyval(scPageUp) > 1 THEN .sptr = .sptr MOD 3 END IF IF .sptr < 24 THEN 'EXIT not selected IF keyval(scPageDown) > 1 THEN .sptr = (.sptr MOD 3) + 21 END IF IF carray(ccLeft) > 1 AND .sptr > 0 THEN .sptr -= 1 menusound gen(genCursorSFX) END IF IF carray(ccRight) > 1 THEN .sptr += 1 menusound gen(genCursorSFX) END IF END IF END WITH IF carray(ccUse) > 1 THEN '--use selected spell IF bat.sptr = 24 THEN '--used cancel bat.menu_mode = batMENUHERO flusharray carray(), 7, 0 menusound gen(genCancelSFX) EXIT SUB END IF DIM atk as AttackData '--can-I-use-it? checking IF bat.spell.slot(bat.sptr).atk_id = -1 THEN '--list-entry is empty menusound gen(genCancelSFX) ELSE loadattackdata atk, bat.spell.slot(bat.sptr).atk_id IF atkallowed(atk, bat.hero_turn, st(bat.hero_turn).list_type(bat.listslot), INT(bat.sptr / 3), bslot()) THEN '--attack is allowed menusound gen(genAcceptSFX) '--if lmp then set lmp consume flag IF st(bat.hero_turn).list_type(bat.listslot) = 1 THEN bslot(bat.hero_turn).consume_lmp = INT(bat.sptr / 3) + 1 '--queue attack bslot(bat.hero_turn).attack = bat.spell.slot(bat.sptr).atk_id + 1 '--exit spell menu bat.targ.mode = targSETUP bat.menu_mode = batMENUHERO flusharray carray(), 7, 0 ELSE '--not allowed menusound gen(genCancelSFX) END IF END IF END IF END SUB SUB itemmenu (byref bat as BattleState, bslot() as BattleSprite) IF carray(ccMenu) > 1 THEN bat.menu_mode = batMENUHERO flusharray carray(), 7, 0 bslot(bat.hero_turn).consume_item = -1 ' -1 here indicates that this hero will not consume any item menusound gen(genCancelSFX) EXIT SUB END IF DIM remember_pt as integer = bat.item.pt usemenusounds IF carray(ccUp) > 1 AND bat.item.pt > 2 THEN bat.item.pt = bat.item.pt - 3 IF carray(ccDown) > 1 AND bat.item.pt <= last_inv_slot() - 3 THEN bat.item.pt = bat.item.pt + 3 IF keyval(scPageUp) > 1 THEN bat.item.pt -= (bat.inv_scroll.size+1) * 3 WHILE bat.item.pt < 0: bat.item.pt += 3: WEND END IF IF keyval(scPageDown) > 1 THEN bat.item.pt += (bat.inv_scroll.size+1) * 3 WHILE bat.item.pt > last_inv_slot(): bat.item.pt -= 3: WEND END IF IF keyval(scHome) > 1 THEN bat.item.pt = 0 END IF IF keyval(scEnd) > 1 THEN bat.item.pt = last_inv_slot() END IF IF carray(ccLeft) > 1 AND bat.item.pt > 0 THEN bat.item.pt = bat.item.pt - 1 menusound gen(genCursorSFX) END IF IF carray(ccRight) > 1 AND bat.item.pt < last_inv_slot() THEN bat.item.pt = bat.item.pt + 1 menusound gen(genCursorSFX) END IF '--scroll when past top or bottom WHILE bat.item.pt < bat.item.top : bat.item.top = bat.item.top - 3 : WEND WHILE bat.item.pt >= bat.item.top + (bat.inv_scroll.size+1) * 3 : bat.item.top = bat.item.top + 3 : WEND DIM itembuf(dimbinsize(binITM)) as integer IF remember_pt <> bat.item.pt THEN IF inventory(bat.item.pt).used THEN bat.item_desc = readitemdescription(inventory(bat.item.pt).id) ELSE bat.item_desc = "" END IF END IF IF carray(ccUse) > 1 THEN IF readbit(bat.iuse(), 0, bat.item.pt) = 1 THEN menusound gen(genAcceptSFX) loaditemdata itembuf(), inventory(bat.item.pt).id bslot(bat.hero_turn).consume_item = -1 IF itembuf(73) = 1 THEN bslot(bat.hero_turn).consume_item = bat.item.pt bslot(bat.hero_turn).attack = itembuf(47) bat.targ.mode = targSETUP bat.menu_mode = batMENUHERO flusharray carray(), 7, 0 END IF END IF END SUB 'Calculate the absolute position at which an attack should be drawn at on top of 'a certain target. This position might instead be used as a waypoint for projectiles. 'Called from within generate_atkscript. FUNCTION attack_placement_over_target(attack as AttackData, targslot as integer, bat as BattleState, bslot() as BattleSprite) as XYZTriple DIM as integer attackw = 50, attackh = 50 DIM as integer xt, yt, zt IF readbit(gen(), genBits2, 20) THEN ' "Old attack positioning at bottom-left of target" ' Position attack animation aligned with bottom-left of target (?!) and down 2 pixels xt = 0 yt = (bslot(targslot).h - attackh) + 2 zt = 0 ELSE ' Visually align center of attack and target, while bottom-y position is forward several pixels of the ' bottom-y of the target to ensure the attack appears in front (with 4 pixel margin to protect against ' rounding error in anim_absmove, etc.) ' (The +4's cancel out because z increases towards top of screen) xt = (bslot(targslot).w - attackw) \ 2 yt = (bslot(targslot).h - attackh) + 4 zt = (bslot(targslot).h - attackh) \ 2 + 4 END IF ' The following case is a simple fix for the fact that bslot() contains the *initial* positions ' of everyone at the start of the attack, not the actual position at this point of the animation. IF targslot = bat.acting AND is_hero(bat.acting) THEN SELECT CASE attack.attacker_anim ' Heroes move forward 20 pixels for these attacker animations (see anim_advance) CASE atkrAnimStrike, atkrAnimCast, atkrAnimSpinStrike, atkrAnimJump xt -= 20 END SELECt END IF xt += bslot(targslot).x yt += bslot(targslot).y zt += bslot(targslot).z RETURN TYPE(xt, yt, zt) END FUNCTION ' Calculate where a projectile will start relative to the attacker (bat.acting): ' Center of attack aligned to center of left of right edge of attacker sprite. FUNCTION projectile_start_position(attack as AttackData, attacker as integer, bat as BattleState, bslot() as BattleSprite) as XYZTriple DIM ret as XYZTriple ret = attack_placement_over_target(attack, attacker, bat, bslot()) IF is_hero(attacker) THEN ret.x -= bslot(attacker).w \ 2 ELSE ret.x += bslot(attacker).w \ 2 END IF RETURN ret END FUNCTION FUNCTION check_for_unhittable_invisible_foe(byval index as integer, byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, t() as integer) as bool IF bslot(t(index)).vis <> 0 THEN RETURN NO 'Is visible, so yes, we can hit it IF attack_can_hit_dead(t(index), attack, bslot(bat.acting).stored_targs_can_be_dead) THEN RETURN NO 'Attack can hit dead targets IF bslot(bat.acting).self_bequesting THEN 'This is a self-bequest attack IF bat.acting = t(index) THEN RETURN NO 'Bequesting attacker is always allowed to hit self END IF RETURN YES END FUNCTION SUB generate_atkscript(byref attack as AttackData, byref bat as BattleState, bslot() as BattleSprite, t() as integer) DIM i as integer '--check for item consumption IF bslot(bat.acting).consume_item >= 0 THEN IF inventory(bslot(bat.acting).consume_item).used = 0 THEN '--abort if item is gone bat.atk.id = -1 EXIT SUB END IF END IF '--load attack loadattackdata attack, bat.atk.id DIM safety as integer = 0 DO WHILE spawn_chained_attack(attack.instead, attack, bat, bslot()) IF blocked_by_attack(bat, bat.acting) THEN EXIT SUB IF bat.atk.id = -1 THEN EXIT SUB loadattackdata attack, bat.atk.id safety += 1 IF safety > 100 THEN debuginfo "Endless instead-chain loop detected for " & attack.name bat.atk.id = -1 '--cancel attack EXIT SUB END IF LOOP IF attack.recheck_costs_after_delay THEN 'The "Re-check costs after attack delay" is on, so cancel the attack if we can't afford it now IF atkallowed(attack, bat.acting, 0, 0, bslot()) = NO THEN bat.atk.id = -1 EXIT SUB END IF END IF '--setup attack sprite slots FOR i = 12 TO 23 bslot(i).frame = frameSTAND bslot(i).z = 0 'load battle sprites with bslot(i) .sprite_num = 3 .frame = frameSTAND frame_unload(@.sprites) palette16_unload(@.pal) .sprites = frame_load(sprTypeAttack, attack.picture) .pal = palette16_load(attack.pal, sprTypeAttack, attack.picture) end with NEXT i DIM tcount as integer = 0 DIM pdir as integer = 0 bat.atk.has_consumed_costs = NO IF is_enemy(bat.acting) THEN pdir = 1 'CANNOT HIT INVISIBLE FOES FOR i = 0 TO 11 IF t(i) > -1 THEN IF check_for_unhittable_invisible_foe(i, attack, bat, bslot(), t()) THEN t(i) = -1 END IF END IF NEXT i 'MOVE EMPTY TARGET SLOTS TO THE BACK FOR o as integer = 0 TO UBOUND(t) - 1 FOR i = 0 TO 10 IF t(i) = -1 THEN SWAP t(i), t(i + 1) NEXT i NEXT o 'COUNT TARGETS FOR i = 0 TO 11 IF t(i) > -1 THEN tcount += 1 NEXT i bat.atk.non_elemental = YES FOR i = 0 TO gen(genNumElements) - 1 bat.atk.elemental(i) = NO IF attack.elemental_damage(i) = YES THEN bat.atk.elemental(i) = YES IF NOT gam.non_elemental_elements(i) THEN bat.atk.non_elemental = NO END IF END IF NEXT i 'ABORT IF TARGETLESS IF tcount = 0 THEN bat.atk.id = -1 EXIT SUB END IF 'Kill old target history FOR i = 0 TO 11 bslot(bat.acting).last_targs(i) = NO NEXT i 'BIG CRAZY SCRIPT CONSTRUCTION 'DEBUG debug "begin script construction" IF attack.dramatic_pause > 0 THEN anim_wait attack.dramatic_pause END IF IF is_hero(bat.acting) THEN 'load weapon sprites with bslot(24) .sprite_num = 2 DIM wpic as integer DIM wpal as integer frame_unload @.sprites palette16_unload @.pal IF attack.override_wep_pic THEN wpic = attack.wep_picture wpal = attack.wep_pal .hand(0).x = attack.wep_handle(0).x .hand(0).y = attack.wep_handle(0).y .hand(1).x = attack.wep_handle(1).x .hand(1).y = attack.wep_handle(1).y ELSE wpic = gam.hero(bat.acting).wep_pic wpal = gam.hero(bat.acting).wep_pal .hand(0).x = GetWeaponPos(eqstuf(bat.acting,0)-1,1,0) .hand(0).y = GetWeaponPos(eqstuf(bat.acting,0)-1,1,1) .hand(1).x = GetWeaponPos(eqstuf(bat.acting,0)-1,0,0) .hand(1).y = GetWeaponPos(eqstuf(bat.acting,0)-1,0,1) END IF .sprites = frame_load(sprTypeWeapon, wpic) .pal = palette16_load(wpal, sprTypeWeapon, wpic) .frame = 0 end with END IF DIM numhits as integer numhits = attack.hits ' "Attacks will ignore extra hits stat" gen bitset IF xreadbit(gen(), 13, genBits2) = NO AND attack.ignore_extra_hits = NO THEN numhits += randint(bslot(bat.acting).stat.cur.hits + 1) END IF DIM atkimgdirection as integer atkimgdirection = pdir IF attack.unreversable_picture = YES THEN atkimgdirection = 0 ' Absolute position to place/target an attack at DIM as XYZTriple targetpos '----NULL ANIMATION IF attack.attack_anim = atkAnimNull THEN anim_advance bat.acting, attack, bslot(), t() IF attack.sound_effect > 0 THEN anim_sound(attack.sound_effect - 1) FOR j as integer = 1 TO numhits IF is_hero(bat.acting) THEN anim_hero bat.acting, attack, bslot(), t() IF is_enemy(bat.acting) THEN anim_enemy bat.acting, attack, bslot(), t() FOR i = 0 TO tcount - 1 anim_inflict t(i), tcount NEXT i anim_disappear 24 anim_skip_ahead_if_targetless NEXT j anim_skip_to_here anim_retreat bat.acting, attack, bslot() anim_end END IF '----NORMAL, DROP, SPREAD-RING, and SCATTER IF attack.attack_anim = atkAnimNormal OR attack.attack_anim = atkAnimDrop OR attack.attack_anim = atkAnimScatter OR (attack.attack_anim = atkAnimRing AND tcount > 1) THEN FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) anim_setpos 12 + i, targetpos.x, targetpos.y, atkimgdirection anim_setz 12 + i, targetpos.z IF attack.attack_anim = atkAnimDrop THEN anim_setz 12 + i, targetpos.z + 180 END IF IF attack.attack_anim = atkAnimRing THEN anim_setpos 12 + i, targetpos.x, targetpos.y - bslot(t(i)).w, atkimgdirection END IF NEXT i anim_advance bat.acting, attack, bslot(), t() FOR j as integer = 1 TO numhits IF is_hero(bat.acting) THEN anim_hero bat.acting, attack, bslot(), t() IF is_enemy(bat.acting) THEN anim_enemy bat.acting, attack, bslot(), t() FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) anim_appear 12 + i IF attack.attack_anim = atkAnimRing THEN ' Appear left of the target; will circle in a "ring" anim_absmove 12 + i, targetpos.x - bslot(t(i)).w, targetpos.y, 3, 3 END IF IF attack.attack_anim = atkAnimDrop THEN anim_zmove 12 + i, -10, 20 END IF IF attack.attack_anim = atkAnimScatter THEN ' Move to a random point (FIXME for non-320*200 resolutions) anim_absmove 12 + i, randint(270), randint(150), 6, 6 END IF NEXT i IF attack.sound_effect > 0 THEN anim_sound(attack.sound_effect - 1) anim_wait 2 IF attack.attack_anim = atkAnimDrop THEN anim_wait 3 END IF anim_setframe bat.acting, frameSTAND anim_disappear 24 IF attack.attack_anim = atkAnimRing THEN ' The attack sprites follow a diamond path around their targets. ' We don't modify z position, set above. The attacks will circle behind the targets too. FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) anim_absmove 12 + i, targetpos.x, targetpos.y + bslot(t(i)).w, 3, 3 NEXT i anim_waitforall FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) anim_absmove 12 + i, targetpos.x + bslot(t(i)).w, targetpos.y, 3, 3 NEXT i anim_waitforall FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) anim_absmove 12 + i, targetpos.x, targetpos.y - bslot(t(i)).w, 3, 3 NEXT i anim_waitforall END IF FOR i = 0 TO tcount - 1 anim_inflict t(i), tcount anim_flinchstart t(i), bslot(), attack NEXT i IF attack.attack_anim <> atkAnimRing THEN anim_wait 3 END IF FOR i = 0 TO tcount - 1 anim_disappear 12 + i anim_flinchdone t(i), bslot(), attack NEXT i anim_wait 2 anim_skip_ahead_if_targetless NEXT j anim_skip_to_here anim_retreat bat.acting, attack, bslot() FOR i = 0 TO tcount - 1 anim_setframe t(i), frameSTAND NEXT i anim_end END IF '----SEQUENTIAL PROJECTILE IF attack.attack_anim = atkAnimSequentialProjectile THEN 'attacker steps forward anim_advance bat.acting, attack, bslot(), t() 'repeat the following for each attack FOR j as integer = 1 TO numhits ' Attacker animates IF is_hero(bat.acting) THEN anim_hero bat.acting, attack, bslot(), t() IF is_enemy(bat.acting) THEN anim_enemy bat.acting, attack, bslot(), t() ' Set the projectile position DIM projectile_start as XYZTriple projectile_start = projectile_start_position(attack, bat.acting, bat, bslot()) anim_setpos 12, projectile_start.x, projectile_start.y, atkimgdirection anim_setz 12 + i, projectile_start.z anim_appear 12 ' Play the sound effect IF attack.sound_effect > 0 THEN anim_sound(attack.sound_effect - 1) 'repeat the following for each target... FOR i = 0 TO tcount - 1 'find the target's position targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) 'make the projectile move to the target anim_absmove 12, targetpos.x, targetpos.y, 5, 5 anim_abszmove 12, targetpos.z, 5 anim_waitforall 'inflict damage anim_inflict t(i), tcount anim_flinchstart t(i), bslot(), attack anim_wait 3 anim_flinchdone t(i), bslot(), attack IF i = 0 THEN 'attacker's weapon picture vanishes after the first hit anim_disappear 24 END IF NEXT i 'after all hits are done, projectile flies off the side of the screen '(FIXME for non-320*200 resolutions) IF is_hero(bat.acting) THEN anim_absmove 12, -50, 100, 5, 5 END IF IF is_enemy(bat.acting) THEN anim_absmove 12, 320, 100, 5, 5 END IF anim_waitforall 'hide projectile anim_disappear 12 anim_skip_ahead_if_targetless NEXT j anim_skip_to_here 'attacker steps back anim_retreat bat.acting, attack, bslot() anim_end END IF '----PROJECTILE, REVERSE PROJECTILE and METEOR IF attack.attack_anim = atkAnimProjectile OR attack.attack_anim = atkAnimReverseProjectile OR attack.attack_anim = atkAnimMeteor THEN DIM projectile_start as XYZTriple projectile_start = projectile_start_position(attack, bat.acting, bat, bslot()) anim_advance bat.acting, attack, bslot(), t() FOR j as integer = 1 TO numhits FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) IF attack.attack_anim = atkAnimProjectile THEN anim_setpos 12 + i, projectile_start.x, projectile_start.y, atkimgdirection anim_setz 12 + i, projectile_start.z END IF IF attack.attack_anim = atkAnimReverseProjectile THEN anim_setpos 12 + i, targetpos.x, targetpos.y, atkimgdirection anim_setz 12 + i, targetpos.z END IF IF attack.attack_anim = atkAnimMeteor THEN 'FIXME for non-320*200 resolutions IF is_hero(bat.acting) THEN anim_setpos 12 + i, 320, 100, atkimgdirection END IF IF is_enemy(bat.acting) THEN anim_setpos 12 + i, -50, 100, atkimgdirection END IF anim_setz 12 + i, 180 END IF NEXT i IF is_hero(bat.acting) THEN anim_hero bat.acting, attack, bslot(), t() IF is_enemy(bat.acting) THEN anim_enemy bat.acting, attack, bslot(), t() FOR i = 0 TO tcount - 1 anim_appear 12 + i NEXT i 'Wait a tick, otherwise the initial position of the attack is never seen anim_wait 1 FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) IF attack.attack_anim = atkAnimProjectile OR attack.attack_anim = atkAnimMeteor THEN anim_absmove 12 + i, targetpos.x, targetpos.y, 6, 6 anim_abszmove 12 + i, targetpos.z, 6 END IF IF attack.attack_anim = atkAnimReverseProjectile THEN anim_absmove 12 + i, projectile_start.x, projectile_start.y, 6, 6 anim_abszmove 12 + i, projectile_start.z, 6 END IF IF attack.attack_anim = atkAnimMeteor THEN anim_zmove 12 + i, -6, 30 END IF NEXT i IF attack.sound_effect > 0 THEN anim_sound(attack.sound_effect - 1) anim_wait 8 anim_disappear 24 anim_setframe bat.acting, frameSTAND FOR i = 0 TO tcount - 1 anim_inflict t(i), tcount anim_flinchstart t(i), bslot(), attack NEXT i anim_wait 3 FOR i = 0 TO tcount - 1 anim_disappear 12 + i anim_flinchdone t(i), bslot(), attack NEXT i anim_wait 3 anim_skip_ahead_if_targetless NEXT j anim_skip_to_here anim_retreat bat.acting, attack, bslot() FOR i = 0 TO tcount - 1 anim_setframe t(i), frameSTAND NEXT i anim_end END IF '----DRIVEBY ' Assuming the hero is attacking, this creates attacks off the right side of the ' screen, sends them to intercept the targets all at the same time, and then moves them ' to the same x. So the projectiles all move at different speeds during each half. IF attack.attack_anim = atkAnimDriveby THEN anim_advance bat.acting, attack, bslot(), t() FOR j as integer = 1 TO numhits ' Create attack sprites FOR i = 0 TO tcount - 1 targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) IF is_hero(bat.acting) THEN anim_setpos 12 + i, 320, targetpos.y, atkimgdirection END IF IF is_enemy(bat.acting) THEN anim_setpos 12 + i, -50, targetpos.y, atkimgdirection END IF anim_setz 12 + i, targetpos.z NEXT i IF is_hero(bat.acting) THEN anim_hero bat.acting, attack, bslot(), t() IF is_enemy(bat.acting) THEN anim_enemy bat.acting, attack, bslot(), t() ' Intercept targets FOR i = 0 TO tcount - 1 anim_appear 12 + i targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) anim_absmove 12 + i, targetpos.x, targetpos.y, 8, 8 ' Z values are constant NEXT i IF attack.sound_effect > 0 THEN anim_sound(attack.sound_effect - 1) anim_wait 4 anim_disappear 24 anim_setframe bat.acting, frameSTAND anim_waitforall ' Drive off side of screen FOR i = 0 TO tcount - 1 anim_inflict t(i), tcount anim_flinchstart t(i), bslot(), attack targetpos = attack_placement_over_target(attack, t(i), bat, bslot()) IF is_hero(bat.acting) THEN anim_absmove 12 + i, -50, targetpos.y, 5, 7 END IF IF is_enemy(bat.acting) THEN anim_absmove 12 + i, 320, targetpos.y, 5, 7 END IF ' Z values are constant NEXT i anim_waitforall ' Destroy attacks FOR i = 0 TO tcount - 1 anim_disappear 12 + i anim_flinchdone t(i), bslot(), attack NEXT i anim_wait 3 anim_skip_ahead_if_targetless NEXT j anim_skip_to_here anim_retreat bat.acting, attack, bslot() FOR i = 0 TO tcount - 1 anim_setframe t(i), frameSTAND NEXT i anim_end END IF '----FOCUSED RING IF attack.attack_anim = atkAnimRing AND tcount = 1 THEN DIM target as integer = t(0) anim_advance bat.acting, attack, bslot(), t() FOR j as integer = 1 TO numhits targetpos = attack_placement_over_target(attack, target, bat, bslot()) ' Create 8 attacks in a circle FOR idx as integer = 0 TO 7 anim_setz 12 + idx, targetpos.z NEXT anim_setpos 12 + 0, targetpos.x + 0, targetpos.y - 50, atkimgdirection anim_setpos 12 + 1, targetpos.x + 30, targetpos.y - 30, atkimgdirection anim_setpos 12 + 2, targetpos.x + 50, targetpos.y + 0, atkimgdirection anim_setpos 12 + 3, targetpos.x + 30, targetpos.y + 30, atkimgdirection anim_setpos 12 + 4, targetpos.x - 0, targetpos.y + 50, atkimgdirection anim_setpos 12 + 5, targetpos.x - 30, targetpos.y + 30, atkimgdirection anim_setpos 12 + 6, targetpos.x - 50, targetpos.y - 0, atkimgdirection anim_setpos 12 + 7, targetpos.x - 30, targetpos.y - 30, atkimgdirection IF is_hero(bat.acting) THEN anim_hero bat.acting, attack, bslot(), t() IF is_enemy(bat.acting) THEN anim_enemy bat.acting, attack, bslot(), t() ' Move attacks into the target FOR aslot as integer = 0 TO 7 anim_appear 12 + aslot anim_absmove 12 + aslot, targetpos.x, targetpos.y, 4, 4 'Z values remain constant NEXT aslot IF attack.sound_effect > 0 THEN anim_sound(attack.sound_effect - 1) anim_wait 8 anim_disappear 24 anim_setframe bat.acting, frameSTAND FOR i = 0 TO tcount - 1 ' Note tcount = 1 anim_inflict t(i), tcount anim_flinchstart t(i), bslot(), attack NEXT i anim_wait 3 FOR aslot as integer = 0 TO 7 anim_disappear 12 + aslot NEXT aslot FOR i = 0 TO tcount - 1 ' Note tcount = 1 anim_flinchdone t(i), bslot(), attack NEXT i anim_wait 3 anim_skip_ahead_if_targetless NEXT j anim_skip_to_here anim_retreat bat.acting, attack, bslot() FOR i = 0 TO tcount - 1 ' Note tcount = 1 anim_setframe t(i), frameSTAND NEXT i anim_end END IF '----WAVE IF attack.attack_anim = atkAnimWave THEN DIM wave_start_x as integer wave_start_x = -50 IF is_hero(bat.acting) THEN wave_start_x = 320 DIM pushback_x as integer pushback_x = 24 IF is_hero(bat.acting) THEN pushback_x = -24 ' Only use targetpos.y, and then only if there's just one target. targetpos = attack_placement_over_target(attack, t(0), bat, bslot()) anim_advance bat.acting, attack, bslot(), t() FOR j as integer = 1 TO numhits FOR i = 0 TO 11 IF tcount > 1 OR attack.targ_set = 1 THEN anim_setpos 12 + i, wave_start_x, i * 15, atkimgdirection anim_setz 12 + i, 0 'Can tweak this to change the effect ELSE anim_setpos 12 + i, wave_start_x, targetpos.y, atkimgdirection anim_setz 12 + i, targetpos.z END IF NEXT i IF is_hero(bat.acting) THEN anim_hero bat.acting, attack, bslot(), t() IF is_enemy(bat.acting) THEN anim_enemy bat.acting, attack, bslot(), t() IF attack.sound_effect > 0 THEN anim_sound(attack.sound_effect - 1) FOR i = 0 TO 11 anim_appear 12 + i anim_setmove 12 + i, pushback_x, 0, 16, 0 anim_wait 1 NEXT i anim_wait 15 anim_disappear 24 anim_setframe bat.acting, frameSTAND FOR i = 0 TO tcount - 1 anim_inflict t(i), tcount anim_flinchstart t(i), bslot(), attack NEXT i anim_waitforall FOR i = 0 TO 11 anim_disappear 12 + i NEXT i FOR i = 0 TO tcount - 1 anim_flinchdone t(i), bslot(), attack NEXT i anim_wait 2 anim_skip_ahead_if_targetless NEXT j anim_skip_to_here anim_retreat bat.acting, attack, bslot() FOR i = 0 TO tcount - 1 anim_setframe t(i), frameSTAND NEXT i anim_end END IF '--setup animation pattern FOR i = 12 TO 23 '--for each attack sprite bslot(i).anim_index = 0 bslot(i).anim_pattern = attack.anim_pattern NEXT i '--if there is a caption and display time isn't "Not at all" IF attack.caption <> "" AND attack.caption_time >= 0 THEN DIM ticks as integer = attack.caption_delay IF attack.caption_time = 0 THEN ticks += 16383 '--full duration ELSE ticks += attack.caption_time '--timed END IF setbatcap bat, attack.caption, ticks, attack.caption_delay END IF 'DEBUG debug "stackpos = " & (stackpos - bstackstart) invertstack '--Remember the attack ID for later call to fulldeathcheck bat.atk.was_id = bat.atk.id '--indicates that animation is set and that we should proceed to "action" bat.anim_ready = YES END SUB SUB enforce_weak_picture(byval who as integer, bslot() as BattleSprite, byref bat as BattleState) '--Heroes only, since enemies don't currently have a weak frame IF is_hero(who) THEN '--enforce weak picture DIM weakhp as integer = 0 weakhp = gen(genHeroWeakHP) IF bslot(who).stat.cur.hp < 0.01 * bslot(who).stat.max.hp * weakhp - 1e-8 AND bat.vic.state = vicNONE THEN bslot(who).frame = frameWEAK END IF END SUB SUB setup_targetting (byref bat as BattleState, bslot() as BattleSprite) 'setuptarg (heroes only) DIM i as integer 'init bat.targ.opt_spread = 0 bat.targ.interactive = NO bat.targ.roulette = NO bat.targ.force_first = NO bat.targ.pointer = 0 FOR i = 0 TO 11 bat.targ.selected(i) = 0 ' clear list of selected targets NEXT i bat.targ.hit_dead = NO 'load attack loadattackdata bat.targ.atk, bslot(bat.hero_turn).attack - 1 get_valid_targs bat.targ.mask(), bat.hero_turn, bat.targ.atk, bslot() bat.targ.hit_dead = attack_can_hit_dead(bat.hero_turn, bat.targ.atk, bslot(bat.hero_turn).stored_targs_can_be_dead) '--attacks that can target all should default to the first enemy IF bat.targ.atk.targ_class = 3 THEN bat.targ.pointer = 4 END IF 'fail if there are no targets IF targetmaskcount(bat.targ.mask()) = 0 THEN bat.targ.mode = targNONE EXIT SUB END IF 'autoattack IF bat.targ.atk.automatic_targ THEN bat.targ.mode = targAUTO EXIT SUB END IF IF bat.targ.atk.targ_set = 0 THEN bat.targ.interactive = YES IF bat.targ.atk.targ_set = 1 THEN FOR i = 0 TO 11: bat.targ.selected(i) = bat.targ.mask(i): NEXT i IF bat.targ.atk.targ_set = 2 THEN bat.targ.interactive = YES: bat.targ.opt_spread = 1 IF bat.targ.atk.targ_set = 3 THEN bat.targ.roulette = YES IF bat.targ.atk.targ_set = 4 THEN bat.targ.force_first = YES bat.targ.pointer = find_preferred_target(bat.targ.mask(), bat.hero_turn, bat.targ.atk, bslot()) 'fail if no targets are found IF bat.targ.pointer = -1 THEN bat.targ.mode = targNONE EXIT SUB END IF 'ready to choose bat.targ.selected() from bat.targ.mask() bat.targ.mode = targMANUAL END SUB FUNCTION valid_statnum(byval statnum as integer, context as string) as integer RETURN bound_arg(statnum, 0, 15, "stat number", context, serrError) END FUNCTION FUNCTION check_attack_chain(byref ch as AttackDataChain, byref bat as BattleState, bslot() as BattleSprite) as bool 'Returns YES if the chain may proceed, or NO if it fails IF randint(100) >= ch.rate THEN RETURN NO '--random percentage failed IF ch.must_know = YES THEN IF knows_attack(bat.acting, ch.atk_id - 1, bslot()) = NO THEN RETURN NO END IF DIM targ as integer DIM tcount as integer DIM tgood as integer DIM ret as bool = NO DIM ra as double SELECT CASE ch.mode CASE 0 '--no special conditions ret = YES CASE 1 '--tag checks IF ABS(ch.val1) <= max_tag() AND ABS(ch.val2) <= max_tag() THEN ret = istag(ch.val1, YES) AND istag(ch.val2, YES) ELSE debug "chain: invalid tag check " & ch.val1 & " " & ch.val2 END IF CASE 2 '--attacker stat greater than value IF valid_statnum(ch.val1, "check_attack_chain() attacker [>n]") THEN ret = bslot(bat.acting).stat.cur.sta(ch.val1) > ch.val2 END IF CASE 3 '--attacker stat less than value IF valid_statnum(ch.val1, "check_attack_chain() attacker [n%]") THEN WITH bslot(bat.acting).stat ret = .cur.sta(ch.val1) > .max.sta(ch.val1) * (ch.val2 / 100) END WITH END IF CASE 5 '--attacker stat less than value % of max IF valid_statnum(ch.val1, "check_attack_chain() attacker [n]") THEN FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN 'debug "check stat " & ch.val1 & " any target " & targ & " > " & ch.val2 IF bslot(targ).stat.cur.sta(ch.val1) > ch.val2 THEN ret = YES END IF NEXT i END IF CASE 7 '--any target stat less than value IF valid_statnum(ch.val1, "check_attack_chain() any target [= 0 THEN 'debug "check stat " & ch.val1 & " any target " & targ & " < " & ch.val2 IF bslot(targ).stat.cur.sta(ch.val1) < ch.val2 THEN ret = YES END IF NEXT i END IF CASE 8 '--any target stat greater than % of max IF valid_statnum(ch.val1, "check_attack_chain() any target [>n%]") THEN FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN 'debug "check stat " & ch.val1 & " any target " & targ & " > " & ch.val2 & "%" WITH bslot(targ).stat IF .cur.sta(ch.val1) > .max.sta(ch.val1) * (ch.val2 / 100) THEN ret = YES END WITH END IF NEXT i END IF CASE 9 '--any target stat less than % of max IF valid_statnum(ch.val1, "check_attack_chain() any target [= 0 THEN 'debug "check stat " & ch.val1 & " any target " & targ & " < " & ch.val2 & "%" WITH bslot(targ).stat 'debug ch.val1 & " " & ch.val2 & " " & .cur.sta(ch.val1) & " " & .max.sta(ch.val1) IF .cur.sta(ch.val1) < .max.sta(ch.val1) * (ch.val2 / 100) THEN ret = YES END WITH END IF NEXT i END IF CASE 10 '--all target stat greater than value IF valid_statnum(ch.val1, "check_attack_chain() all target [>n]") THEN tcount = 0 tgood = 0 FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN tcount += 1 'debug "check stat " & ch.val1 & " all target " & targ & " > " & ch.val2 IF bslot(targ).stat.cur.sta(ch.val1) > ch.val2 THEN tgood += 1 END IF NEXT i IF tcount > 0 ANDALSO tgood = tcount THEN ret = YES END IF CASE 11 '--all target stat less than value IF valid_statnum(ch.val1, "check_attack_chain() all target [= 0 THEN tcount += 1 'debug "check stat " & ch.val1 & " all target " & targ & " < " & ch.val2 IF bslot(targ).stat.cur.sta(ch.val1) < ch.val2 THEN tgood += 1 END IF NEXT i IF tcount > 0 ANDALSO tgood = tcount THEN ret = YES END IF CASE 12 '--all target stat greater than % of max IF valid_statnum(ch.val1, "check_attack_chain() all target [>n%]") THEN tcount = 0 tgood = 0 FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN tcount += 1 'debug "check stat " & ch.val1 & " all target " & targ & " > " & ch.val2 & "%" WITH bslot(targ).stat IF .cur.sta(ch.val1) > .max.sta(ch.val1) * (ch.val2 / 100) THEN tgood += 1 END WITH END IF NEXT i IF tcount > 0 ANDALSO tgood = tcount THEN ret = YES END IF CASE 13 '--all target stat less than % of max IF valid_statnum(ch.val1, "check_attack_chain() all target [= 0 THEN tcount += 1 'debug "check stat " & ch.val1 & " all target " & targ & " < " & ch.val2 & "%" WITH bslot(targ).stat IF .cur.sta(ch.val1) < .max.sta(ch.val1) * (ch.val2 / 100) THEN tgood += 1 END WITH END IF NEXT i IF tcount > 0 ANDALSO tgood = tcount THEN ret = YES END IF CASE 14 '--all target stat greater than attacker stat IF valid_statnum(ch.val1, "check_attack_chain()") ANDALSO valid_statnum(ch.val2, "check_attack_chain()") THEN tcount = 0 tgood = 0 FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN tcount += 1 'debug "check stat " & ch.val1 & " all target " & targ & " > " & ch.val2 IF bslot(targ).stat.cur.sta(ch.val1) > bslot(bat.acting).stat.cur.sta(ch.val2) THEN tgood += 1 END IF NEXT i IF tcount > 0 ANDALSO tgood = tcount THEN ret = YES END IF CASE 15 '--all target stat less than attacker stat IF valid_statnum(ch.val1, "check_attack_chain()") ANDALSO valid_statnum(ch.val2, "check_attack_chain()") THEN tcount = 0 tgood = 0 FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN tcount += 1 'debug "check stat " & ch.val1 & " all target " & targ & " < " & ch.val2 IF bslot(targ).stat.cur.sta(ch.val1) < bslot(bat.acting).stat.cur.sta(ch.val2) THEN tgood += 1 END IF NEXT i IF tcount > 0 ANDALSO tgood = tcount THEN ret = YES END IF CASE 16 '--attacker stat less than attacker stat IF valid_statnum(ch.val1, "check_attack_chain()") ANDALSO valid_statnum(ch.val2, "check_attack_chain()") THEN ret = bslot(bat.acting).stat.cur.sta(ch.val1) < bslot(bat.acting).stat.cur.sta(ch.val2) END IF CASE 17 '--attacker stat probability IF valid_statnum(ch.val1, "check_attack_chain()") THEN ret = rando() < bslot(bat.acting).stat.cur.sta(ch.val1) / ch.val2 END IF CASE 18 '--any target stat probability IF valid_statnum(ch.val1, "check_attack_chain()") THEN ra = rando() FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN IF ra < bslot(targ).stat.cur.sta(ch.val1) / ch.val2 THEN ret = YES END IF NEXT i END IF CASE 19 '--all target stat probability IF valid_statnum(ch.val1, "check_attack_chain()") THEN ra = rando() tcount = 0 tgood = 0 FOR i as integer = 0 to 11 targ = bat.anim_t(i) IF targ >= 0 THEN tcount += 1 IF ra < bslot(targ).stat.cur.sta(ch.val1) / ch.val2 THEN tgood += 1 END IF NEXT i IF tcount > 0 ANDALSO tgood = tcount THEN ret = YES END IF 'CASE 20 '--attacker local flags 'TODO 'CASE 21 '--any target local flags 'TODO 'CASE 22 '--all target local flags 'TODO 'CASE 23 '--attacker local flag and any target local flag 'TODO 'CASE 24 '--attacker local flag and all target local flag 'TODO CASE ELSE debug "attack chain mode " & ch.mode & " unsupported" END SELECT RETURN ret XOR ch.invert_condition END FUNCTION FUNCTION spawn_chained_attack(byref ch as AttackDataChain, byref attack as AttackData, byref bat as battlestate, bslot() as BattleSprite) as integer IF ch.atk_id <= 0 THEN RETURN NO '--no chain defined IF bslot(bat.acting).stat.cur.hp <= 0 THEN RETURN NO '--attacker is dead IF attack.no_chain_on_failure = YES AND bslot(bat.acting).attack_succeeded = 0 THEN 'attack failed, and this chain configured to fail too RETURN NO END IF IF check_attack_chain(ch, bat, bslot()) THEN '--The conditions for this chain are passed bat.wait_frames = 0 bat.anim_ready = NO DIM chained_attack as AttackData loadattackdata chained_attack, ch.atk_id - 1 DIM delayed_attack_id as integer = 0 IF (chained_attack.attack_delay > 0 ORELSE chained_attack.turn_delay > 0) ANDALSO ch.no_delay = NO THEN '--chain is delayed, queue the attack bat.atk.id = -1 '--terminate the attack that lead to this chain delayed_attack_id = ch.atk_id ELSE '--chain is immediate, prep it now! bat.atk.id = ch.atk_id - 1 bat.anim_ready = NO END IF DIM blocking as integer IF bat.anim_blocking_delay = NO THEN '--chains from non-blocking attacks are always non-blocking blocking = NO ELSEIF ch.nonblocking THEN blocking = NO ELSE blocking = NOT chained_attack.nonblocking END IF IF chained_attack.targ_set <> attack.targ_set OR _ chained_attack.targ_class <> attack.targ_class OR _ chained_attack.targ_set = 3 OR chained_attack.prefer_targ > 0 THEN 'if the chained attack has a different target class/type then re-target 'also retarget if the chained attack has target setting "random roulette" 'also retarget if the chained attack's preferred target is explicitly set autotarget bat.acting, chained_attack, bslot(), , blocking, ch.dont_retarget bat.atk.id = -1 ELSEIF delayed_attack_id > 0 THEN 'if the old target info is reused, and this is not an immediate chain, copy it to the queue right away queue_attack delayed_attack_id - 1, bat.acting, bat.anim_t(), blocking, ch.dont_retarget END IF RETURN YES '--chained attack okay END IF RETURN NO '--chained attack failed END FUNCTION '.attack FUNCTION knows_attack(byval who as integer, byval atk as integer, bslot() as BattleSprite) as integer 'who is bslot index 'atk is attack id 'bslot() hero and enemy data 'spell() is a global array '--different handling for heroes and monsters IF is_hero(who) THEN FOR i as integer = 0 TO bslot(who).batmenu.numitems - 1 WITH *bslot(who).batmenu.items[i] SELECT CASE .t CASE batmenu_ATTACK: IF atk = .sub_t THEN RETURN YES CASE batmenu_SPELLS: FOR j as integer = 0 TO 23 IF spell(who, .sub_t, j) = atk THEN RETURN YES 'Knows the attack in a spell list NEXT j END SELECT END WITH NEXT i END IF IF is_enemy(who) THEN FOR i as integer = 0 TO 4 'check if enemy knows this attack for one of the three ai sets IF bslot(who).enemy.regular_ai(i) - 1 = atk THEN RETURN YES IF bslot(who).enemy.desperation_ai(i) - 1 = atk THEN RETURN YES IF bslot(who).enemy.alone_ai(i) - 1 = atk THEN RETURN YES NEXT i END IF RETURN NO END FUNCTION SUB queue_attack(byval attack as integer, byval who as integer, targs() as integer, byval override_blocking as integer=-2, byval dont_retarget as integer = NO) DIM atk as AttackData loadattackdata atk, attack DIM blocking as integer = (atk.nonblocking = NO) IF override_blocking > -2 THEN blocking = override_blocking queue_attack attack, who, atk.attack_delay, atk.turn_delay, targs(), blocking, dont_retarget END SUB SUB queue_attack(byval attack as integer, byval who as integer, byval delay as integer, byval turn_delay as integer, targs() as integer, byval blocking as integer=YES, byval dont_retarget as integer = NO) 'DIM targstr as string = "" 'FOR i as integer = 0 TO UBOUND(targs) ' IF targs(i) > -1 THEN targstr &= " " & i & "=" & targs(i) 'NEXT i 'debug "queue_attack " & readattackname(attack) & ", " & who & ", " & targstr FOR i as integer = 0 TO UBOUND(atkq) IF atkq(i).used = NO THEN 'Recycle a queue slot set_attack_queue_slot i, attack, who, delay, turn_delay, targs(), blocking, dont_retarget EXIT SUB END IF NEXT i 'No spaces to recycle, grow the queue DIM oldbound as integer = UBOUND(atkq) REDIM PRESERVE atkq(oldbound + 16) as AttackQueue FOR i as integer = oldbound + 2 TO UBOUND(atkq) clear_attack_queue_slot i NEXT i set_attack_queue_slot oldbound + 1, attack, who, delay, turn_delay, targs(), blocking END SUB SUB set_attack_queue_slot(byval slot as integer, byval attack as integer, byval who as integer, byval delay as integer, byval turn_delay as integer, targs() as integer, byval blocking as integer=YES, byval dont_retarget as integer = NO) WITH atkq(slot) .used = YES .attack = attack .attacker = who .delay = delay .turn_delay = turn_delay FOR i as integer = 0 TO UBOUND(.t) .t(i) = targs(i) NEXT i .blocking = blocking .dont_retarget = dont_retarget END WITH END SUB SUB clear_attack_queue() FOR i as integer = 0 TO UBOUND(atkq) clear_attack_queue_slot i NEXT i END SUB SUB clear_attack_queue_slot(byval slot as integer) WITH atkq(slot) .used = NO .attack = -1 .attacker = -1 .delay = 0 .turn_delay = 0 FOR i as integer = 0 TO UBOUND(.t) .t(i) = -1 NEXT i .blocking = YES .dont_retarget = NO END WITH END SUB ' This is drawn at the top of the screen, rather than the top of the compatpage SUB display_attack_queue (bslot() as BattleSprite) DIM s as string DIM targstr as string FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used THEN s = .turn_delay & "/" & .delay & " " & bslot(.attacker).name & ":" & .attacker & " " & readattackname(.attack) & ":" & .attack & " " targstr = "" FOR j as integer = 0 TO UBOUND(.t) IF .t(j) > -1 THEN targstr &= CHR(24) & .t(j) END IF NEXT j s & = targstr & " " & yesorno(.blocking, "B", "N") & yesorno(.dont_retarget, "d", "") ELSE s = "-" END IF edgeprint s, 0, i * 10, uilook(uiText), vpage END WITH NEXT i END SUB FUNCTION has_queued_attacks(byval who as integer) as integer '--Returns YES if the specified hero or enemy has at least one attack queued. '--Returns NO if they do not. '--This is intended to check for both blocking and non-blocking queued attacks. FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used THEN IF .attacker = who THEN RETURN YES END IF END WITH NEXT i RETURN NO END FUNCTION FUNCTION battle_time_can_pass(bat as BattleState) as bool ' Return YES if time is allowed to pass in active mode IF bat.atk.id <> -1 THEN RETURN NO 'an attack animation is going on right now IF bat.vic.state <> vicNONE THEN RETURN NO 'victory has already happened IF readbit(gen(), genBits2, 5) <> 0 AND bat.caption_time > 0 THEN RETURN NO ' "Attack captions pause battle meters" RETURN YES END FUNCTION FUNCTION battle_meters_can_advance(byref bat as BattleState, bslot() as BattleSprite) as bool ' Returns YES if active time ready meters for heroes and enemies are allowed to grow. ' The actual growth happens in battle_meters() DIM hero_has_a_turn as bool = bat.hero_turn >= 0 IF NOT hero_has_a_turn THEN RETURN YES DIM pause_on_all as bool = readbit(gen(), genBits, 13) '"Pause in battle on all menus" DIM pause_on_spell_and_item as bool = readbit(gen(), genBits, 0) '"Pause in battle on spell and item menus" DIM pause_on_targeting as bool = readbit(gen(), genbits2, 19) '"Pause when targeting attacks" DIM isenemytargs as bool = (targenemycount(bslot()) > 0) IF isenemytargs THEN IF pause_on_all THEN IF bat.menu_mode >= 0 ORELSE bat.targ.mode > targNONE THEN 'A menu is open or an attack is being targetted RETURN NO END IF END IF IF pause_on_spell_and_item THEN IF bat.menu_mode > 0 THEN 'Spell or item menu is open right now RETURN NO END IF END IF IF pause_on_targeting THEN IF bat.targ.mode > targNONE THEN 'Somebody is currently manually targetting an attack RETURN NO END IF END IF END IF RETURN YES END FUNCTION SUB battle_background_anim(byref bat as BattleState, formdata as Formation) IF formdata.background_frames > 1 THEN bat.bg_tick = loopvar(bat.bg_tick, 0, formdata.background_ticks, 1) IF bat.bg_tick = 0 THEN bat.curbg = loopvar(bat.curbg, formdata.background, formdata.background + formdata.background_frames - 1, 1) frame_unload @bat.backdrop bat.backdrop = frame_load(sprTypeBackdrop, bat.curbg MOD gen(genNumBackdrops)) END IF END IF END SUB FUNCTION battle_run_away(byref bat as BattleState, bslot() as BattleSprite) as bool '--this function is called every tick of battle. It returns YES if '-- a successful run has completed, thus ending battle. IF bat.turn.mode = turnACTIVE THEN battle_crappy_run_handler bat, bslot() END IF '--bat.away will be set to a positive number if running has succeeded IF bat.away > 0 THEN battle_animate_running_away bslot() bat.away += 1 IF bat.away > 10 THEN RETURN YES END IF END IF RETURN NO END FUNCTION SUB battle_animate_running_away (bslot() as BattleSprite) FOR i as integer = 0 TO 3 '--if alive, animate running away IF bslot(i).stat.cur.hp > 0 THEN WITH bslot(i) IF .vis THEN .xmov = 10 .xspeed = 6 bslot(i).walk = 1 .d = 1 END IF END WITH END IF NEXT i END SUB SUB battle_check_delays(byref bat as BattleState, bslot() as BattleSprite) 'If an attack in the attack queue is ready, start it. IF bat.atk.id >= 0 THEN EXIT SUB 'this check is redundant in active mode, but needed in wait mode '--check the attack queue delays FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used THEN IF .turn_delay = 0 ANDALSO .delay <= 0 THEN 'debug "queue trigger! " & bslot(.attacker).name & .attacker & ":" & readattackname(.attack) IF .t(0) = -1 THEN 'debuginfo "queued attack " & readattackname(.attack) & " for " & bslot(.attacker).name & .attacker & " in slot " & i & " has null target." clear_attack_queue_slot i CONTINUE FOR END IF IF bslot(.t(0)).stat.cur.hp <= 0 AND NOT attack_can_hit_dead(.attacker, .attack, bslot(.attacker).stored_targs_can_be_dead) THEN IF .t(0) = .attacker ANDALSO bslot(.attacker).bequesting THEN 'If a bequesting attacker is targetting itself, we don't care that it is dead ELSE IF .dont_retarget THEN 'debuginfo "queued attack " & readattackname(.attack) & " for " & bslot(.attacker).name & .attacker & " in slot " & i & " has dead target, and should not retarget, clearing." clear_attack_queue_slot i ELSE 'debuginfo "queued attack " & readattackname(.attack) & " for " & bslot(.attacker).name & .attacker & " in slot " & i & " has dead target, retargetting." autotarget .attacker, .attack, bslot(), .t(), NO END IF END IF END IF bat.atk.id = .attack bat.acting = .attacker FOR j as integer = 0 TO UBOUND(.t) bat.anim_t(j) = .t(j) NEXT j bat.anim_blocking_delay = .blocking bat.anim_ready = NO clear_attack_queue_slot i EXIT FOR END IF END IF END WITH NEXT i END SUB SUB battle_check_for_hero_turns(byref bat as BattleState, bslot() as BattleSprite) bat.next_hero = loopvar(bat.next_hero, 0, 3, 1) IF bat.hero_turn > -1 THEN '--somebody is already taking their turn EXIT SUB END IF IF readbit(gen(), genBits2, 7) AND bat.atk.id > -1 THEN '--an attack is currently animating, and "Pause for attack animations" tells us we must wait for it EXIT SUB END IF '--if it is not currently any hero's turn, check to see if anyone is alive and ready FOR i as integer = 0 TO 3 IF battle_check_a_hero_turn(bat, bslot(), (i + bat.next_hero) MOD 4) THEN EXIT FOR END IF NEXT i END SUB FUNCTION hero_or_enemy_can_take_a_turn (byval who as integer, bat as BattleState, bslot() as BattleSprite) as integer IF bslot(who).ready = NO THEN RETURN NO IF bslot(who).stat.cur.hp <= 0 THEN RETURN NO IF bat.death_mode <> deathNOBODY THEN RETURN NO IF has_blocking_turn_delayed_attacks(who) THEN RETURN NO IF bslot(who).no_attack_this_turn THEN RETURN NO RETURN YES END FUNCTION FUNCTION battle_check_a_hero_turn(byref bat as BattleState, bslot() as BattleSprite, byval index as integer) as integer IF hero_or_enemy_can_take_a_turn(index, bat, bslot()) THEN bat.hero_turn = index bslot(bat.hero_turn).menust.pt = 0 bat.menu_mode = batMENUHERO RETURN YES END IF RETURN NO END FUNCTION SUB battle_check_for_enemy_turns(byref bat as BattleState, bslot() as BattleSprite) bat.next_enemy = loopvar(bat.next_enemy, 4, 11, 1) IF bat.enemy_turn = -1 THEN '--if no enemy is currently taking their turn, check to find an enemy who is ready DIM slot as integer = bat.next_enemy FOR i as integer = 4 TO 11 IF battle_check_an_enemy_turn(bat, bslot(), slot) THEN EXIT FOR slot = loopvar(slot, 4, 11, 1) NEXT i END IF END SUB FUNCTION battle_check_an_enemy_turn(byref bat as BattleState, bslot() as BattleSprite, byval index as integer) as integer IF hero_or_enemy_can_take_a_turn(index, bat, bslot()) THEN bat.enemy_turn = index RETURN YES END IF RETURN NO END FUNCTION FUNCTION blocked_by_attack (bat as BattleState, byval who as integer) as integer FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used ANDALSO .attacker = who ANDALSO .blocking THEN SELECT CASE bat.turn.mode CASE turnACTIVE: IF .turn_delay = 0 ANDALSO .delay > 0 THEN RETURN YES CASE turnTURN: IF .delay > 0 ORELSE .turn_delay > 0 THEN RETURN YES END SELECT END IF END WITH NEXT i RETURN NO END FUNCTION FUNCTION ready_meter_may_grow (bat as BattleState, bslot() as BattleSprite, byval who as integer) as integer '--Only used in turnACTIVE mode WITH bslot(who) IF .attack <> 0 THEN RETURN NO IF .dissolve <> 0 THEN RETURN NO IF .stat.cur.stun < .stat.max.stun THEN RETURN NO IF .ready = YES THEN RETURN NO END WITH IF blocked_by_attack(bat, who) THEN RETURN NO RETURN YES END FUNCTION SUB battle_attack_cancel_target_attack(byval targ as integer, byref bat as BattleState, bslot() as BattleSprite, byref attack as AttackData) IF attack.cancel_targets_attack THEN '--try to cancel target's attack DIM targets_attack as AttackData FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used ANDALSO .attacker = targ THEN loadattackdata targets_attack, .attack IF targets_attack.not_cancellable_by_attacks = NO THEN 'Okay to cancel target's attack clear_attack_queue_slot i END IF END IF END WITH NEXT i END IF IF attack.cancel_targets_attack OR bslot(targ).stat.cur.stun < bslot(targ).stat.max.stun THEN '--If the currently targeting hero is the one hit, stop targetting '--note that stunning implies cancellation of untargetted attacks, '--but does not imply cancellation of already-targeted attacks. IF bat.hero_turn = targ THEN bat.targ.mode = targNONE bat.hero_turn = -1 bslot(targ).attack = 0 END IF END IF END SUB FUNCTION check_has_remaining_targets(bat as BattleState, bslot() as BattleSprite, targs() as integer) as bool 'Returns whether a multihit attack should continue: either it can hit dead targets, or 'any of the targets are alive, or dying but have not finished dying yet. IF attack_can_hit_dead(bat.acting, bat.atk.id, YES) THEN RETURN YES FOR i as integer = 0 TO UBOUND(targs) IF targs(i) > -1 THEN IF bslot(targs(i)).stat.cur.hp > 0 ORELSE bslot(targs(i)).vis THEN RETURN YES END IF NEXT i RETURN NO END FUNCTION SUB battle_reevaluate_dead_targets (byval deadguy as integer, byref bat as BattleState, bslot() as BattleSprite) '--check for queued attacks that target the dead target FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used THEN DIM attack as AttackData loadattackdata attack, .attack 'DIM s as string 's = attack.name & " of " & bslot(.attacker).name 'dim showdebug as integer=NO 'for j as integer = 0 to ubound(.t) ' if deadguy = .t(j) then s &= " was targeting " & bslot(deadguy).name & deadguy & " who died": showdebug=YES 'next j 'if showdebug then debug s IF NOT attack_can_hit_dead(.attacker, .attack, bslot(.attacker).stored_targs_can_be_dead) THEN battle_sort_away_dead_t_target deadguy, .t() END IF IF .t(0) = -1 THEN 'if no targets left, auto-re-target IF .dont_retarget THEN 'debuginfo "queued " & readattackname(.attack) & " for " & bslot(.attacker).name & " - last targ died and should not retarget" clear_attack_queue_slot i ELSE autotarget .attacker, .attack, bslot(), .t(), NO END IF END IF END IF END WITH NEXT i '--cancel current interactive targetting that points to the dead target (unless the attack is allowed to target dead) IF bat.targ.hit_dead = NO THEN IF bat.targ.mask(deadguy) = 1 THEN bat.targ.mask(deadguy) = 0 IF bat.targ.selected(deadguy) = 1 THEN bat.targ.selected(deadguy) = 0 '--if current interactive targeting points to the dead target, find a new target WITH bat.targ IF .pointer = deadguy THEN .pointer = 0 WHILE .mask(.pointer) = 0 .pointer += 1 IF .pointer > 11 THEN .mode = targNONE EXIT WHILE END IF WEND END IF END WITH END IF '----END ONLY WHEN bat.targ.hit_dead = NO END SUB SUB battle_sort_away_dead_t_target(byval deadguy as integer, t() as integer) '--FIXME: la la la! James loves Bogo-sorts! FOR i as integer = 0 TO UBOUND(t) - 1 '--crappy bogo-sort dead target away IF t(i) = deadguy THEN SWAP t(i), t(i + 1) NEXT i IF t(UBOUND(t)) = deadguy THEN t(UBOUND(t)) = -1 END SUB SUB battle_counterattacks(bat as BattleState, byval h as integer, byval targstat as integer, byval who as integer, attack as AttackData, bslot() as BattleSprite) DIM blocking as integer = NO 'counterattacks are forced non-blocking for active-mode IF bat.turn.mode = turnTURN THEN blocking = YES 'But in turn-based mode, they are blocking '--first elementals IF NOT attack.never_trigger_elemental_counterattacks THEN FOR i as integer = 0 TO gen(genNumElements) - 1 IF attack.elemental_damage(i) THEN IF bslot(who).elem_counter_attack(i) > 0 THEN autotarget who, bslot(who).elem_counter_attack(i) - 1, bslot(), YES, blocking EXIT SUB '-- only one counterattack per trigger attack END IF END IF NEXT i END IF '--then non-elemental attack IF bat.atk.non_elemental THEN IF bslot(who).non_elem_counter_attack > 0 THEN autotarget who, bslot(who).non_elem_counter_attack - 1, bslot(), YES, blocking EXIT SUB '-- only one counterattack per trigger attack END IF END IF '-then stat damage FOR i as integer = 0 TO 11 IF h > 0 AND targstat = i THEN IF bslot(who).stat_counter_attack(i) > 0 THEN 'counterattacks are forced non-blocking autotarget who, bslot(who).stat_counter_attack(i) - 1, bslot(), YES, blocking EXIT SUB '-- only one counterattack per trigger attack END IF END IF NEXT i END SUB SUB show_first_battle_timer (page as integer) '--show the timer ' Why doesn't this use the string's position, colour, or font?! IF readbit(gen(), genBits2, 22) <> 0 THEN EXIT SUB '"Never show script timer during battles" FOR i as integer = 0 to UBOUND(timers) IF timers(i).speed > 0 ANDALSO timers(i).st > 0 ANDALSO timers(i).flags AND TIMERFLAG_BATTLE THEN edgeprint plotstr(timers(i).st-1).s, pRight - 10, pBottom - 5, uilook(uiText), page EXIT FOR 'Only print the first timer if there are many of them END IF NEXT i END SUB FUNCTION pending_attacks_for_this_turn(bat as BattleState, bslot() as BattleSprite) as bool 'Returns whether this is a pending attack. Used by turnTURN mode 'Check for a currently animating attack IF bat.atk.id >= 0 THEN RETURN YES IF bat.caption_time > 0 THEN RETURN YES 'Check for attack captions 'Check for queued attacks FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used THEN '--queued attacks for stunned attackers don't count. IF .attacker >= 0 THEN WITH bslot(.attacker) IF .stat.cur.stun < .stat.max.stun THEN CONTINUE FOR END WITH END IF '--attacks with a turn delay don't count IF .turn_delay > 0 THEN CONTINUE FOR '--only blocking queued attacks are considered part of the current ' turn (although it is always perfectly possible for a nonblocking ' attack to happen in the current turn) IF .blocking THEN RETURN YES END IF END WITH NEXT i RETURN NO END FUNCTION SUB ready_all_valid_units(bslot() as BattleSprite, formdata as Formation) 'In turnTURN mode, force all valid living heroes and enemies to be ready to take their turn FOR i as integer = 0 TO 11 bslot(i).no_attack_this_turn = NO NEXT i FOR i as integer = 0 TO 3 IF gam.hero(i).id >= 0 ANDALSO bslot(i).stat.cur.hp > 0 THEN bslot(i).ready = YES bslot(i).ready_meter = 1000 'Filling the ready meter only matters for visual indication END IF NEXT i FOR i as integer = 4 TO 11 IF formdata.slots(i - 4).id >= 0 ANDALSO bslot(i).stat.cur.hp > 0 THEN bslot(i).ready = YES bslot(i).ready_meter = 1000 'Filling the ready meter only to be consistent with the heroes END IF NEXT i END SUB SUB active_mode_state_machine (bat as BattleState, bslot() as BattleSprite, formdata as Formation) IF battle_time_can_pass(bat) THEN IF battle_meters_can_advance(bat, bslot()) THEN battle_meters bat, bslot(), formdata END IF battle_check_delays bat, bslot() END IF fulldeathcheck -1, bat, bslot(), formdata battle_check_for_hero_turns bat, bslot() battle_check_for_enemy_turns bat, bslot() END SUB SUB turn_mode_state_machine (bat as BattleState, bslot() as BattleSprite, formdata as Formation) IF bat.vic.state <> vicNONE THEN EXIT SUB 'victory has already happened IF bat.death_mode <> deathNOBODY THEN EXIT SUB 'Death animation is happening IF bat.atk.id > 0 THEN EXIT SUB 'an attack is animating now, wait patiently. IF bat.hero_turn >= 0 THEN EXIT SUB 'somebody already taking a turn, so wait patiently IF bat.enemy_turn >= 0 THEN EXIT SUB IF bat.turn.choosing_attacks THEN IF bat.turn.reverse THEN 'debug "Reverse! bat.next_hero=" & bat.next_hero DO bat.next_hero = large(0, bat.next_hero - 1) WITH bslot(bat.next_hero) IF .stat.cur.hp > 0 ANDALSO .no_attack_this_turn = NO THEN cancel_blocking_attacks_for_hero_or_enemy bat.next_hero bslot(bat.next_hero).ready = YES bslot(bat.next_hero).ready_meter = 1000 EXIT DO END IF END WITH IF bat.next_hero = 0 THEN EXIT DO LOOP 'debug " END LOOP bat.next_hero=" & bat.next_hero bat.turn.reverse = NO END IF DO WHILE bat.next_hero <= 3 IF battle_check_a_hero_turn(bat, bslot(), bat.next_hero) THEN 'debug "Hero " & bat.hero_turn & " " & bslot(bat.hero_turn).name & " is picking attack" bat.next_hero += 1 EXIT SUB END IF bat.next_hero += 1 LOOP DO WHILE bat.next_enemy <= 11 IF battle_check_an_enemy_turn(bat, bslot(), bat.next_enemy) THEN 'debug "Enemy " & bat.enemy_turn & " " & bslot(bat.enemy_turn).name & " is picking attack" bat.next_enemy += 1 EXIT SUB END IF bat.next_enemy += 1 LOOP '--All attacks are chosen, update stun and mute. FOR i as integer = 0 to 11 WITH bslot(i).stat .cur.mute = small(.cur.mute + 1, .max.mute) .cur.stun = small(.cur.stun + 1, .max.stun) END WITH NEXT i apply_initiative_order bslot() '--Attack selection is finished, animate this turn! bat.turn.choosing_attacks = NO END IF '--animating/inflicting attacks IF pending_attacks_for_this_turn(bat, bslot()) = NO THEN start_next_turn bat, bslot(), formdata ELSE battle_check_delays bat, bslot() turn_mode_time_passage bat, bslot() END IF END SUB SUB turn_mode_time_passage (bat as BattleState, bslot() as battleSprite) IF bat.atk.id >= 0 THEN EXIT SUB 'Check for a currently animating attack IF bat.caption_time > 0 THEN EXIT SUB 'Check for attack captions IF bat.away > 0 THEN EXIT SUB 'no time if the heroes have already run away DIM isenemytargs as integer = (targenemycount(bslot()) > 0) IF isenemytargs THEN IF bat.menu_mode > 0 THEN EXIT SUB '--no time on spell/item menus IF bat.menu_mode >= 0 AND bat.hero_turn >= 0 THEN EXIT SUB '--no time if hero menu is open END IF decrement_attack_queue_delays bslot() END SUB SUB start_next_turn (bat as BattleState, bslot() as BattleSprite, formdata as Formation) 'A new turn starts! (turnTURN mode only!) bat.turn.number += 1 bat.turn.choosing_attacks = YES bat.next_hero = 0 bat.next_enemy = 4 ready_all_valid_units bslot(), formdata FOR i as integer = 0 to 11 '--update poison and regen WITH bslot(i).stat IF .cur.poison < .max.poison THEN do_poison i, bat, bslot(), formdata IF .cur.regen < .max.regen THEN do_regen i, bat, bslot(), formdata IF .cur.stun < .max.stun THEN '--note that stun and mute are updated after the attacks are chosen bslot(i).ready = NO bslot(i).ready_meter = 0 '--cosmetic bslot(i).no_attack_this_turn = YES END IF END WITH '--no turn for heroes with blocking turn-delayed attacks IF has_blocking_turn_delayed_attacks(i) THEN bslot(i).ready = NO bslot(i).ready_meter = 0 '--cosmetic bslot(i).no_attack_this_turn = YES END IF NEXT i '--figure out initiative_order based on speed calc_initiative_order bslot(), formdata '--update turn delays in attack queue FOR i as integer = 0 to 11 update_turn_delays_in_attack_queue i NEXT I 'debug "Turn #" & bat.turn.number & " has begun!" END SUB SUB calc_initiative_order (bslot() as BattleSprite, formdata as Formation) '--Only used for turnTURN mode '--first clear old initiative FOR i as integer = 0 to 11 bslot(i).initiative_order = 0 NEXT i '--Copy speeds into a temporary integer array DIM speeds(11) as integer FOR i as integer = 0 to 11 IF is_hero(i) THEN IF NOT (gam.hero(i).id >= 0 ANDALSO bslot(i).stat.cur.hp > 0) THEN speeds(i) = -1 CONTINUE FOR END IF END IF IF is_enemy(i) THEN IF NOT (formdata.slots(i - 4).id >= 0 ANDALSO bslot(i).stat.cur.hp > 0) THEN speeds(i) = -1 CONTINUE FOR END IF END IF speeds(i) = bslot(i).stat.cur.spd 'debug bslot(i).name & " speed = " & speeds(i) NEXT i '--Sort indexes by speed DIM order(11) as integer sort_integers_indices order(), @speeds(0) '--Randomize order when speed is a tie. FOR i as integer = 0 TO 11-1 IF speeds(order(i)) >= 0 ANDALSO speeds(order(i)) = speeds(order(i+1)) THEN 'debug "Flipping coin for " & bslot(order(i)).name & " " & order(i) & " and " & bslot(order(i+1)).name & " " & order(i+1) IF randint(100) < 50 THEN SWAP order(i), order(i+1) 'debug " Heads! (swap)" ELSE 'debug " Tails! (do nothing)" END IF END IF NEXT i DIM j as integer = 0 FOR i as integer = 11 TO 0 STEP -1 IF speeds(order(i)) = -1 THEN EXIT FOR bslot(order(i)).initiative_order = j 'debug "Initiative " & j & " " & bslot(order(i)).name j += 1 NEXT i END SUB SUB apply_initiative_order (bslot() as BattleSprite) FOR i as integer = 0 to 11 '--For each living hero and enemy... IF bslot(i).stat.cur.hp > 0 THEN '--For each blocking attack in the queue for this hero or enemy FOR j as integer = 0 to UBOUND(atkq) IF atkq(j).used ANDALSO atkq(j).attacker = i ANDALSO atkq(j).blocking ANDALSO atkq(j).turn_delay = 0 THEN 'debug "Applying initiative: adjust " & bslot(i).name & "'s attack " & atkq(j).attack & " delay " & atkq(j).delay & "+" & bslot(i).initiative_order atkq(j).delay += bslot(i).initiative_order END IF NEXT j END IF NEXT i END SUB SUB cancel_blocking_attacks_for_hero_or_enemy(byval who as integer) FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used ANDALSO .attacker = who ANDALSO .blocking THEN clear_attack_queue_slot i END IF END WITH NEXT i END SUB SUB update_turn_delays_in_attack_queue (byval who as integer) FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used ANDALSO .attacker = who ANDALSO .turn_delay > 0 THEN .turn_delay -= 1 END IF END WITH NEXT i END SUB FUNCTION has_blocking_turn_delayed_attacks(byval who as integer) as integer FOR i as integer = 0 TO UBOUND(atkq) WITH atkq(i) IF .used ANDALSO .attacker = who ANDALSO .turn_delay > 0 ANDALSO .blocking THEN RETURN YES END IF END WITH NEXT i RETURN NO END FUNCTION '========================================================================================== ' This sub does three different things, depending on the state of the DebugMenuDef: ' - Checks for debug key combos. dbg.def() returns true if that key is pressed. ' - Builds a list of available debug menu items. dbg.def() returns false. ' - Performs an action selected in the debug menu. dbg.def() returns true if selected. ' See debug_menu_functions() for more info. SUB battle_debug_menu_functions(dbg as DebugMenuDef, bat as BattleState, bslot() as BattleSprite, formdata as Formation) IF dbg.def( , scF4, "Tag debugger (F4)") THEN gam.debug_showtags = (gam.debug_showtags + 1) MOD 3 IF dbg.def(scCtrl, scF4, "Run instantly (Ctrl+F4)") THEN bat.away = 11 IF dbg.def( , scF5, "Give million experience (F5)") THEN bat.rew.exper = 1000000 DIM temp as string IF bat.turn.mode = 0 THEN temp = "Switch to turn-based battles" ELSE temp = "Switch to active-time battles" IF dbg.def( , scF6, temp & " (F6)") THEN bat.turn.mode XOR= 1 notification "bat.turn.mode=" & bat.turn.mode END IF IF dbg.def( , scF7, "Kill all targetable enemies (F7)") THEN FOR slot as integer = 4 TO 11 WITH bslot(slot) IF .hero_untargetable = NO AND .death_unneeded = NO THEN .stat.cur.hp = 0 triggerfade slot, bslot() END IF END WITH NEXT fulldeathcheck -1, bat, bslot(), formdata END IF IF dbg.def( , scF8) THEN battle_debug_menu bat, bslot(), formdata dbg.def( , , "Debug menu (F8)") 'Does nothing, but document F8. IF dbg.def( , scF10, "Show enemy meters (F10)") THEN bat.show_info_mode = IIF(bat.show_info_mode = 1, 0, 1) IF dbg.def( , scF11, "Show attack queue (F11)") THEN bat.show_info_mode = IIF(bat.show_info_mode = 2, 0, 2) 'Only for documentation, scPause is checked in main loop IF dbg.def( , , "Pause battle (Pause)") THEN battle_pause 'F12 for bat.test_view_mode handled in main loop END SUB ' Check for debug key combos. SUB check_battle_debug_keys(bat as BattleState, bslot() as BattleSprite, formdata as Formation) DIM dbg as DebugMenuDef battle_debug_menu_functions(dbg, bat, bslot(), formdata) END SUB ' Show a menu of debug functions. SUB battle_debug_menu(bat as BattleState, bslot() as BattleSprite, formdata as Formation) ' Build DIM dbg as DebugMenuDef dbg.start_building_menu() battle_debug_menu_functions(dbg, bat, bslot(), formdata) DIM menu() as string vector_to_array menu(), dbg.menu ' Show DIM result as integer STATIC default as integer = 0 result = multichoice("Battle Debug Menu", menu(), default, , "game_battle_debug_menu") IF result = -1 THEN EXIT SUB ' Enact default = result dbg.selected_item = menu(result) battle_debug_menu_functions(dbg, bat, bslot(), formdata) END SUB FUNCTION hero_attack_cost_info(byref atk as AttackData, byval hero_slot as integer, byval magic_list_type as integer=0, byval lmp_level as integer=0) as string DIM cur_lmp as integer = 0 IF magic_list_type = 1 ANDALSO lmp_level > 0 THEN cur_lmp = lmp(hero_slot, lmp_level - 1) END IF RETURN attack_cost_info(atk,_ gam.hero(hero_slot).stat.cur.focus,_ gam.hero(hero_slot).stat.cur.mp,_ gam.hero(hero_slot).stat.max.mp,_ magic_list_type,_ lmp_level,_ cur_lmp) END FUNCTION FUNCTION bslot_attack_cost_info(bslot() as BattleSprite, byref atk as AttackData, byval slot as integer, byval magic_list_type as integer=0, byval lmp_level as integer=0) as string DIM cur_lmp as integer = 0 IF slot >= 0 ANDALSO slot <= 3 THEN IF magic_list_type = 1 ANDALSO lmp_level > 0 THEN cur_lmp = lmp(slot, lmp_level - 1) END IF END IF RETURN attack_cost_info(atk,_ bslot(slot).stat.cur.focus,_ bslot(slot).stat.cur.mp,_ bslot(slot).stat.max.mp,_ magic_list_type,_ lmp_level,_ cur_lmp) END FUNCTION