From a39a68356236af3850310a64233c3a16edbb92bf Mon Sep 17 00:00:00 2001 From: dannye <33dannye@gmail.com> Date: Fri, 11 Jun 2021 18:45:36 -0500 Subject: Normalize line endings --- src/engine/ai/attacks.asm | 1442 +++++++------- src/engine/ai/boss_deck_set_up.asm | 334 ++-- src/engine/ai/damage_calculation.asm | 900 ++++----- src/engine/ai/decks/unreferenced.asm | 84 +- src/engine/ai/energy.asm | 2096 ++++++++++---------- src/engine/ai/hand_pokemon.asm | 1254 ++++++------ src/engine/ai/init.asm | 196 +- src/engine/ai/retreat.asm | 2018 +++++++++---------- src/engine/ai/special_attacks.asm | 962 ++++----- src/engine/sequences/credits_sequence_commands.asm | 928 ++++----- src/engine/sequences/opening_sequence_commands.asm | 650 +++--- 11 files changed, 5432 insertions(+), 5432 deletions(-) (limited to 'src/engine') diff --git a/src/engine/ai/attacks.asm b/src/engine/ai/attacks.asm index 9f93c33..3b331a6 100644 --- a/src/engine/ai/attacks.asm +++ b/src/engine/ai/attacks.asm @@ -1,721 +1,721 @@ -; have AI choose an attack to use, but do not execute it. -; return carry if an attack is chosen. -AIProcessButDontUseAttack: ; 169ca (5:69ca) - ld a, $01 - ld [wAIExecuteProcessedAttack], a - -; backup wPlayAreaAIScore in wTempPlayAreaAIScore. - ld de, wTempPlayAreaAIScore - ld hl, wPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - -; copies wAIScore to wTempAIScore - ld a, [wAIScore] - ld [de], a - jr AIProcessAttacks - -; copies wTempPlayAreaAIScore to wPlayAreaAIScore -; and loads wAIScore with value in wTempAIScore. -; identical to RetrievePlayAreaAIScoreFromBackup1. -RetrievePlayAreaAIScoreFromBackup2: ; 169e3 (5:69e3) - push af - ld de, wPlayAreaAIScore - ld hl, wTempPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - - ld a, [hl] - ld [wAIScore], a - pop af - ret - -; have AI choose and execute an attack. -; return carry if an attack was chosen and attempted. -AIProcessAndTryToUseAttack: ; 169f8 (5:69f8) - xor a - ld [wAIExecuteProcessedAttack], a - ; fallthrough - -; checks which of the Active card's attacks for AI to use. -; If any of the attacks has enough AI score to be used, -; AI will use it if wAIExecuteProcessedAttack is 0. -; in either case, return carry if an attack is chosen to be used. -AIProcessAttacks: ; 169fc (5:69fc) -; if AI used Pluspower, load its attack index - ld a, [wPreviousAIFlags] - and AI_FLAG_USED_PLUSPOWER - jr z, .no_pluspower - ld a, [wAIPluspowerAttack] - ld [wSelectedAttack], a - jr .attack_chosen - -.no_pluspower -; if Player is running Mewtwo1 mill deck, -; skip attack if Barrier counter is 0. - ld a, [wAIBarrierFlagCounter] - cp AI_MEWTWO_MILL + 0 - jp z, .dont_attack - -; determine AI score of both attacks. - xor a ; FIRST_ATTACK_OR_PKMN_POWER - call GetAIScoreOfAttack - ld a, [wAIScore] - ld [wFirstAttackAIScore], a - ld a, SECOND_ATTACK - call GetAIScoreOfAttack - -; compare both attack scores - ld c, SECOND_ATTACK - ld a, [wFirstAttackAIScore] - ld b, a - ld a, [wAIScore] - cp b - jr nc, .check_score - ; first attack has higher score - dec c - ld a, b - -; c holds the attack index chosen by AI, -; and a holds its AI score. -; first check if chosen attack has at least minimum score. -; then check if first attack is better than second attack -; in case the second one was chosen. -.check_score - cp $50 ; minimum score to use attack - jr c, .dont_attack - ; enough score, proceed - - ld a, c - ld [wSelectedAttack], a - or a - jr z, .attack_chosen - call CheckWhetherToSwitchToFirstAttack - -.attack_chosen -; check whether to execute the attack chosen - ld a, [wAIExecuteProcessedAttack] - or a - jr z, .execute - -; set carry and reset Play Area AI score -; to the previous values. - scf - jp RetrievePlayAreaAIScoreFromBackup2 - -.execute - ld a, AI_TRAINER_CARD_PHASE_14 - call AIProcessHandTrainerCards - -; load this attack's damage output against -; the current Defending Pokemon. - xor a ; PLAY_AREA_ARENA - ldh [hTempPlayAreaLocation_ff9d], a - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - - or a - jr z, .check_damage_bench - ; if damage is not 0, fallthrough - -.can_damage - xor a - ld [wcdb4], a - jr .use_attack - -.check_damage_bench -; check if it can otherwise damage player's bench - ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F - call CheckLoadedAttackFlag - jr c, .can_damage - -; cannot damage either Defending Pokemon or Bench - ld hl, wcdb4 - inc [hl] - -; return carry if attack is chosen -; and AI tries to use it. -.use_attack - ld a, $01 - ld [wcddb], a - call AITryUseAttack - scf - ret - -.dont_attack - ld a, [wAIExecuteProcessedAttack] - or a - jr z, .failed_to_use - -; reset Play Area AI score -; to the previous values. - jp RetrievePlayAreaAIScoreFromBackup2 - -; return no carry if no viable attack. -.failed_to_use - ld hl, wcdb4 - inc [hl] - or a - ret - -; determines the AI score of attack index in a -; of card in Play Area location hTempPlayAreaLocation_ff9d. -GetAIScoreOfAttack: ; 16a86 (5:6a86) -; initialize AI score. - ld [wSelectedAttack], a - ld a, $50 - ld [wAIScore], a - - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfSelectedAttackIsUnusable - jr nc, .usable - -; return zero AI score. -.unusable - xor a - ld [wAIScore], a - jp .done - -; load arena card IDs -.usable - xor a - ld [wAICannotDamage], a - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - ld [wTempTurnDuelistCardID], a - call SwapTurn - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - ld [wTempNonTurnDuelistCardID], a - -; handle the case where the player has No Damage substatus. -; in the case the player does, check if this attack -; has a residual effect, or if it can damage the opposing bench. -; If none of those are true, render the attack unusable. -; also if it's a PKMN power, consider it unusable as well. - bank1call HandleNoDamageOrEffectSubstatus - call SwapTurn - jr nc, .check_if_can_ko - - ; player is under No Damage substatus - ld a, $01 - ld [wAICannotDamage], a - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wLoadedAttackCategory] - cp POKEMON_POWER - jr z, .unusable - and RESIDUAL - jr nz, .check_if_can_ko - ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F - call CheckLoadedAttackFlag - jr nc, .unusable - -; calculate damage to player to check if attack can KO. -; encourage attack if it's able to KO. -.check_if_can_ko - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, DUELVARS_ARENA_CARD_HP - call GetNonTurnDuelistVariable - ld hl, wDamage - sub [hl] - jr c, .can_ko - jr z, .can_ko - jr .check_damage -.can_ko - ld a, 20 - call AddToAIScore - -; raise AI score by the number of damage counters that this attack deals. -; if no damage is dealt, subtract AI score. in case wDamage is zero -; but wMaxDamage is not, then encourage attack afterwards. -; otherwise, if wMaxDamage is also zero, check for damage against -; player's bench, and encourage attack in case there is. -.check_damage - xor a - ld [wAIAttackIsNonDamaging], a - ld a, [wDamage] - ld [wTempAI], a - or a - jr z, .no_damage - call CalculateByteTensDigit - call AddToAIScore - jr .check_recoil -.no_damage - ld a, $01 - ld [wAIAttackIsNonDamaging], a - call SubFromAIScore - ld a, [wAIMaxDamage] - or a - jr z, .no_max_damage - ld a, 2 - call AddToAIScore - xor a - ld [wAIAttackIsNonDamaging], a -.no_max_damage - ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F - call CheckLoadedAttackFlag - jr nc, .check_recoil - ld a, 2 - call AddToAIScore - -; handle recoil attacks (low and high recoil). -.check_recoil - ld a, ATTACK_FLAG1_ADDRESS | LOW_RECOIL_F - call CheckLoadedAttackFlag - jr c, .is_recoil - ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F - call CheckLoadedAttackFlag - jp nc, .check_defending_can_ko -.is_recoil - ; sub from AI score number of damage counters - ; that attack deals to itself. - ld a, [wLoadedAttackEffectParam] - or a - jp z, .check_defending_can_ko - ld [wDamage], a - call ApplyDamageModifiers_DamageToSelf - ld a, e - call CalculateByteTensDigit - call SubFromAIScore - - push de - ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F - call CheckLoadedAttackFlag - pop de - jr c, .high_recoil - - ; if LOW_RECOIL KOs self, decrease AI score - ld a, DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - cp e - jr c, .kos_self - jp nz, .check_defending_can_ko -.kos_self - ld a, 10 - call SubFromAIScore - -.high_recoil - ; dismiss this attack if no benched Pokémon - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 2 - jr c, .dismiss_high_recoil_atk - ; has benched Pokémon - -; here the AI handles high recoil attacks differently -; depending on what deck it's playing. - ld a, [wOpponentDeckID] - cp ROCK_CRUSHER_DECK_ID - jr z, .rock_crusher_deck - cp ZAPPING_SELFDESTRUCT_DECK_ID - jr z, .zapping_selfdestruct_deck - cp BOOM_BOOM_SELFDESTRUCT_DECK_ID - jr z, .encourage_high_recoil_atk - ; Boom Boom Selfdestruct deck always encourages - cp POWER_GENERATOR_DECK_ID - jr nz, .high_recoil_generic_checks - ; Power Generator deck always dismisses - -.dismiss_high_recoil_atk - xor a - ld [wAIScore], a - jp .done - -.encourage_high_recoil_atk - ld a, 20 - call AddToAIScore - jp .done - -; Zapping Selfdestruct deck only uses this attack -; if number of cards in deck >= 30 and -; HP of active card is < half max HP. -.zapping_selfdestruct_deck - ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK - call GetTurnDuelistVariable - cp 31 - jr nc, .high_recoil_generic_checks - ld e, PLAY_AREA_ARENA - call GetCardDamageAndMaxHP - sla a - cp c - jr c, .high_recoil_generic_checks - ld b, 0 - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp MAGNEMITE1 - jr z, .magnemite1 - ld b, 10 ; bench damage -.magnemite1 - ld a, 10 - add b - ld b, a ; 20 bench damage if not Magnemite1 - -; if this attack causes player to win the duel by -; knocking out own Pokémon, dismiss attack. - ld a, 1 ; count active Pokémon as KO'd - call .check_if_kos_bench - jr c, .dismiss_high_recoil_atk - jr .encourage_high_recoil_atk - -; Rock Crusher Deck only uses this attack if -; prize count is below 4 and attack wins (or potentially draws) the duel, -; (i.e. at least gets KOs equal to prize cards left). -.rock_crusher_deck - call CountPrizes - cp 4 - jr nc, .dismiss_high_recoil_atk - ; prize count < 4 - ld b, 20 ; damage dealt to bench - call SwapTurn - xor a - call .check_if_kos_bench - call SwapTurn - jr c, .encourage_high_recoil_atk - -; generic checks for all other deck IDs. -; encourage attack if it wins (or potentially draws) the duel, -; (i.e. at least gets KOs equal to prize cards left). -; dismiss it if it causes the player to win. -.high_recoil_generic_checks - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp CHANSEY - jr z, .chansey - cp MAGNEMITE1 - jr z, .magnemite1_or_weezing - cp WEEZING - jr z, .magnemite1_or_weezing - ld b, 20 ; bench damage - jr .check_bench_kos -.magnemite1_or_weezing - ld b, 10 ; bench damage - jr .check_bench_kos -.chansey - ld b, 0 ; no bench damage - -.check_bench_kos - push bc - call SwapTurn - xor a - call .check_if_kos_bench - call SwapTurn - pop bc - jr c, .wins_the_duel - push de - ld a, 1 - call .check_if_kos_bench - pop bc - jr nc, .count_own_ko_bench - -; attack causes player to draw all prize cards - xor a - ld [wAIScore], a - jp .done - -; attack causes CPU to draw all prize cards -.wins_the_duel - ld a, 20 - call AddToAIScore - jp .done - -; subtract from AI score number of own benched Pokémon KO'd -.count_own_ko_bench - push bc - ld a, d - or a - jr z, .count_player_ko_bench - dec a - call SubFromAIScore - -; add to AI score number of player benched Pokémon KO'd -.count_player_ko_bench - pop bc - ld a, b - call AddToAIScore - jr .check_defending_can_ko - -; local function that gets called to determine damage to -; benched Pokémon caused by a HIGH_RECOIL attack. -; return carry if using attack causes number of benched Pokémon KOs -; equal to or larger than remaining prize cards. -; this function is independent on duelist turn, so whatever -; turn it is when this is called, it's that duelist's -; bench/prize cards that get checked. -; input: -; a = initial number of KO's beside benched Pokémon, -; so that if the active Pokémon is KO'd by the attack, -; this counts towards the prize cards collected -; b = damage dealt to bench Pokémon -.check_if_kos_bench - ld d, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld e, PLAY_AREA_ARENA -.loop - inc e - ld a, [hli] - cp $ff - jr z, .exit_loop - ld a, e - add DUELVARS_ARENA_CARD_HP - push hl - call GetTurnDuelistVariable - pop hl - cp b - jr z, .increase_count - jr nc, .loop -.increase_count - ; increase d if damage dealt KOs - inc d - jr .loop -.exit_loop - push de - call SwapTurn - call CountPrizes - call SwapTurn - pop de - cp d - jp c, .set_carry - jp z, .set_carry - or a - ret -.set_carry - scf - ret - -; if defending card can KO, encourage attack -; unless attack is non-damaging. -.check_defending_can_ko - ld a, [wSelectedAttack] - push af - call CheckIfDefendingPokemonCanKnockOut - pop bc - ld a, b - ld [wSelectedAttack], a - jr nc, .check_discard - ld a, 5 - call AddToAIScore - ld a, [wAIAttackIsNonDamaging] - or a - jr z, .check_discard - ld a, 5 - call SubFromAIScore - -; subtract from AI score if this attack requires -; discarding any energy cards. -.check_discard - ld a, [wSelectedAttack] - ld e, a - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ld d, a - call CopyAttackDataAndDamage_FromDeckIndex - ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F - call CheckLoadedAttackFlag - jr nc, .asm_16ca6 - ld a, 1 - call SubFromAIScore - ld a, [wLoadedAttackEffectParam] - call SubFromAIScore - -.asm_16ca6 - ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_6_F - call CheckLoadedAttackFlag - jr nc, .check_nullify_flag - ld a, [wLoadedAttackEffectParam] - call AddToAIScore - -; encourage attack if it has a nullify or weaken attack effect. -.check_nullify_flag - ld a, ATTACK_FLAG2_ADDRESS | NULLIFY_OR_WEAKEN_ATTACK_F - call CheckLoadedAttackFlag - jr nc, .check_draw_flag - ld a, 1 - call AddToAIScore - -; encourage attack if it has an effect to draw a card. -.check_draw_flag - ld a, ATTACK_FLAG1_ADDRESS | DRAW_CARD_F - call CheckLoadedAttackFlag - jr nc, .check_heal_flag - ld a, 1 - call AddToAIScore - -.check_heal_flag - ld a, ATTACK_FLAG2_ADDRESS | HEAL_USER_F - call CheckLoadedAttackFlag - jr nc, .check_status_effect - ld a, [wLoadedAttackEffectParam] - cp 1 - jr z, .tally_heal_score - ld a, [wTempAI] - call CalculateByteTensDigit - ld b, a - ld a, [wLoadedAttackEffectParam] - cp 3 - jr z, .asm_16cec - srl b - jr nc, .asm_16cec - inc b -.asm_16cec - ld a, DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - call CalculateByteTensDigit - cp b - jr c, .tally_heal_score - ld a, b -.tally_heal_score - push af - ld e, PLAY_AREA_ARENA - call GetCardDamageAndMaxHP - call CalculateByteTensDigit - pop bc - cp b ; wLoadedAttackEffectParam - jr c, .add_heal_score - ld a, b -.add_heal_score - call AddToAIScore - -.check_status_effect - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - ; skip if player has Snorlax - cp SNORLAX - jp z, .handle_special_atks - - ld a, DUELVARS_ARENA_CARD_STATUS - call GetNonTurnDuelistVariable - ld [wTempAI], a - -; encourage a poison inflicting attack if opposing Pokémon -; isn't (doubly) poisoned already. -; if opposing Pokémon is only poisoned and not double poisoned, -; and this attack has FLAG_2_BIT_6 set, discourage it -; (possibly to make Nidoking's Toxic attack less likely to be chosen -; if the other Pokémon is poisoned.) - ld a, ATTACK_FLAG1_ADDRESS | INFLICT_POISON_F - call CheckLoadedAttackFlag - jr nc, .check_sleep - ld a, [wTempAI] - and DOUBLE_POISONED - jr z, .add_poison_score - and $40 ; only double poisoned? - jr z, .check_sleep - ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_6_F - call CheckLoadedAttackFlag - jr nc, .check_sleep - ld a, 2 - call SubFromAIScore - jr .check_sleep -.add_poison_score - ld a, 2 - call AddToAIScore - -; encourage sleep-inducing attack if other Pokémon isn't asleep. -.check_sleep - ld a, ATTACK_FLAG1_ADDRESS | INFLICT_SLEEP_F - call CheckLoadedAttackFlag - jr nc, .check_paralysis - ld a, [wTempAI] - and CNF_SLP_PRZ - cp ASLEEP - jr z, .check_paralysis - ld a, 1 - call AddToAIScore - -; encourage paralysis-inducing attack if other Pokémon isn't asleep. -; otherwise, if other Pokémon is asleep, discourage attack. -.check_paralysis - ld a, ATTACK_FLAG1_ADDRESS | INFLICT_PARALYSIS_F - call CheckLoadedAttackFlag - jr nc, .check_confusion - ld a, [wTempAI] - and CNF_SLP_PRZ - cp ASLEEP - jr z, .sub_prz_score - ld a, 1 - call AddToAIScore - jr .check_confusion -.sub_prz_score - ld a, 1 - call SubFromAIScore - -; encourage confuse-inducing attack if other Pokémon isn't asleep -; or confused already. -; otherwise, if other Pokémon is asleep or confused, -; discourage attack instead. -.check_confusion - ld a, ATTACK_FLAG1_ADDRESS | INFLICT_CONFUSION_F - call CheckLoadedAttackFlag - jr nc, .check_if_confused - ld a, [wTempAI] - and CNF_SLP_PRZ - cp ASLEEP - jr z, .sub_cnf_score - ld a, [wTempAI] - and CNF_SLP_PRZ - cp CONFUSED - jr z, .check_if_confused - ld a, 1 - call AddToAIScore - jr .check_if_confused -.sub_cnf_score - ld a, 1 - call SubFromAIScore - -; if this Pokémon is confused, subtract from score. -.check_if_confused - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and CNF_SLP_PRZ - cp CONFUSED - jr nz, .handle_special_atks - ld a, 1 - call SubFromAIScore - -; SPECIAL_AI_HANDLING marks attacks that the AI handles individually. -; each attack has its own checks and modifies AI score accordingly. -.handle_special_atks - ld a, ATTACK_FLAG3_ADDRESS | SPECIAL_AI_HANDLING_F - call CheckLoadedAttackFlag - jr nc, .done - call HandleSpecialAIAttacks - cp $80 - jr c, .negative_score - sub $80 - call AddToAIScore - jr .done -.negative_score - ld b, a - ld a, $80 - sub b - call SubFromAIScore - -.done - ret +; have AI choose an attack to use, but do not execute it. +; return carry if an attack is chosen. +AIProcessButDontUseAttack: ; 169ca (5:69ca) + ld a, $01 + ld [wAIExecuteProcessedAttack], a + +; backup wPlayAreaAIScore in wTempPlayAreaAIScore. + ld de, wTempPlayAreaAIScore + ld hl, wPlayAreaAIScore + ld b, MAX_PLAY_AREA_POKEMON +.loop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .loop + +; copies wAIScore to wTempAIScore + ld a, [wAIScore] + ld [de], a + jr AIProcessAttacks + +; copies wTempPlayAreaAIScore to wPlayAreaAIScore +; and loads wAIScore with value in wTempAIScore. +; identical to RetrievePlayAreaAIScoreFromBackup1. +RetrievePlayAreaAIScoreFromBackup2: ; 169e3 (5:69e3) + push af + ld de, wPlayAreaAIScore + ld hl, wTempPlayAreaAIScore + ld b, MAX_PLAY_AREA_POKEMON +.loop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .loop + + ld a, [hl] + ld [wAIScore], a + pop af + ret + +; have AI choose and execute an attack. +; return carry if an attack was chosen and attempted. +AIProcessAndTryToUseAttack: ; 169f8 (5:69f8) + xor a + ld [wAIExecuteProcessedAttack], a + ; fallthrough + +; checks which of the Active card's attacks for AI to use. +; If any of the attacks has enough AI score to be used, +; AI will use it if wAIExecuteProcessedAttack is 0. +; in either case, return carry if an attack is chosen to be used. +AIProcessAttacks: ; 169fc (5:69fc) +; if AI used Pluspower, load its attack index + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_PLUSPOWER + jr z, .no_pluspower + ld a, [wAIPluspowerAttack] + ld [wSelectedAttack], a + jr .attack_chosen + +.no_pluspower +; if Player is running Mewtwo1 mill deck, +; skip attack if Barrier counter is 0. + ld a, [wAIBarrierFlagCounter] + cp AI_MEWTWO_MILL + 0 + jp z, .dont_attack + +; determine AI score of both attacks. + xor a ; FIRST_ATTACK_OR_PKMN_POWER + call GetAIScoreOfAttack + ld a, [wAIScore] + ld [wFirstAttackAIScore], a + ld a, SECOND_ATTACK + call GetAIScoreOfAttack + +; compare both attack scores + ld c, SECOND_ATTACK + ld a, [wFirstAttackAIScore] + ld b, a + ld a, [wAIScore] + cp b + jr nc, .check_score + ; first attack has higher score + dec c + ld a, b + +; c holds the attack index chosen by AI, +; and a holds its AI score. +; first check if chosen attack has at least minimum score. +; then check if first attack is better than second attack +; in case the second one was chosen. +.check_score + cp $50 ; minimum score to use attack + jr c, .dont_attack + ; enough score, proceed + + ld a, c + ld [wSelectedAttack], a + or a + jr z, .attack_chosen + call CheckWhetherToSwitchToFirstAttack + +.attack_chosen +; check whether to execute the attack chosen + ld a, [wAIExecuteProcessedAttack] + or a + jr z, .execute + +; set carry and reset Play Area AI score +; to the previous values. + scf + jp RetrievePlayAreaAIScoreFromBackup2 + +.execute + ld a, AI_TRAINER_CARD_PHASE_14 + call AIProcessHandTrainerCards + +; load this attack's damage output against +; the current Defending Pokemon. + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + + or a + jr z, .check_damage_bench + ; if damage is not 0, fallthrough + +.can_damage + xor a + ld [wcdb4], a + jr .use_attack + +.check_damage_bench +; check if it can otherwise damage player's bench + ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F + call CheckLoadedAttackFlag + jr c, .can_damage + +; cannot damage either Defending Pokemon or Bench + ld hl, wcdb4 + inc [hl] + +; return carry if attack is chosen +; and AI tries to use it. +.use_attack + ld a, $01 + ld [wcddb], a + call AITryUseAttack + scf + ret + +.dont_attack + ld a, [wAIExecuteProcessedAttack] + or a + jr z, .failed_to_use + +; reset Play Area AI score +; to the previous values. + jp RetrievePlayAreaAIScoreFromBackup2 + +; return no carry if no viable attack. +.failed_to_use + ld hl, wcdb4 + inc [hl] + or a + ret + +; determines the AI score of attack index in a +; of card in Play Area location hTempPlayAreaLocation_ff9d. +GetAIScoreOfAttack: ; 16a86 (5:6a86) +; initialize AI score. + ld [wSelectedAttack], a + ld a, $50 + ld [wAIScore], a + + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfSelectedAttackIsUnusable + jr nc, .usable + +; return zero AI score. +.unusable + xor a + ld [wAIScore], a + jp .done + +; load arena card IDs +.usable + xor a + ld [wAICannotDamage], a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempTurnDuelistCardID], a + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempNonTurnDuelistCardID], a + +; handle the case where the player has No Damage substatus. +; in the case the player does, check if this attack +; has a residual effect, or if it can damage the opposing bench. +; If none of those are true, render the attack unusable. +; also if it's a PKMN power, consider it unusable as well. + bank1call HandleNoDamageOrEffectSubstatus + call SwapTurn + jr nc, .check_if_can_ko + + ; player is under No Damage substatus + ld a, $01 + ld [wAICannotDamage], a + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr z, .unusable + and RESIDUAL + jr nz, .check_if_can_ko + ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F + call CheckLoadedAttackFlag + jr nc, .unusable + +; calculate damage to player to check if attack can KO. +; encourage attack if it's able to KO. +.check_if_can_ko + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld hl, wDamage + sub [hl] + jr c, .can_ko + jr z, .can_ko + jr .check_damage +.can_ko + ld a, 20 + call AddToAIScore + +; raise AI score by the number of damage counters that this attack deals. +; if no damage is dealt, subtract AI score. in case wDamage is zero +; but wMaxDamage is not, then encourage attack afterwards. +; otherwise, if wMaxDamage is also zero, check for damage against +; player's bench, and encourage attack in case there is. +.check_damage + xor a + ld [wAIAttackIsNonDamaging], a + ld a, [wDamage] + ld [wTempAI], a + or a + jr z, .no_damage + call CalculateByteTensDigit + call AddToAIScore + jr .check_recoil +.no_damage + ld a, $01 + ld [wAIAttackIsNonDamaging], a + call SubFromAIScore + ld a, [wAIMaxDamage] + or a + jr z, .no_max_damage + ld a, 2 + call AddToAIScore + xor a + ld [wAIAttackIsNonDamaging], a +.no_max_damage + ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F + call CheckLoadedAttackFlag + jr nc, .check_recoil + ld a, 2 + call AddToAIScore + +; handle recoil attacks (low and high recoil). +.check_recoil + ld a, ATTACK_FLAG1_ADDRESS | LOW_RECOIL_F + call CheckLoadedAttackFlag + jr c, .is_recoil + ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F + call CheckLoadedAttackFlag + jp nc, .check_defending_can_ko +.is_recoil + ; sub from AI score number of damage counters + ; that attack deals to itself. + ld a, [wLoadedAttackEffectParam] + or a + jp z, .check_defending_can_ko + ld [wDamage], a + call ApplyDamageModifiers_DamageToSelf + ld a, e + call CalculateByteTensDigit + call SubFromAIScore + + push de + ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F + call CheckLoadedAttackFlag + pop de + jr c, .high_recoil + + ; if LOW_RECOIL KOs self, decrease AI score + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + cp e + jr c, .kos_self + jp nz, .check_defending_can_ko +.kos_self + ld a, 10 + call SubFromAIScore + +.high_recoil + ; dismiss this attack if no benched Pokémon + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + jr c, .dismiss_high_recoil_atk + ; has benched Pokémon + +; here the AI handles high recoil attacks differently +; depending on what deck it's playing. + ld a, [wOpponentDeckID] + cp ROCK_CRUSHER_DECK_ID + jr z, .rock_crusher_deck + cp ZAPPING_SELFDESTRUCT_DECK_ID + jr z, .zapping_selfdestruct_deck + cp BOOM_BOOM_SELFDESTRUCT_DECK_ID + jr z, .encourage_high_recoil_atk + ; Boom Boom Selfdestruct deck always encourages + cp POWER_GENERATOR_DECK_ID + jr nz, .high_recoil_generic_checks + ; Power Generator deck always dismisses + +.dismiss_high_recoil_atk + xor a + ld [wAIScore], a + jp .done + +.encourage_high_recoil_atk + ld a, 20 + call AddToAIScore + jp .done + +; Zapping Selfdestruct deck only uses this attack +; if number of cards in deck >= 30 and +; HP of active card is < half max HP. +.zapping_selfdestruct_deck + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp 31 + jr nc, .high_recoil_generic_checks + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + sla a + cp c + jr c, .high_recoil_generic_checks + ld b, 0 + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp MAGNEMITE1 + jr z, .magnemite1 + ld b, 10 ; bench damage +.magnemite1 + ld a, 10 + add b + ld b, a ; 20 bench damage if not Magnemite1 + +; if this attack causes player to win the duel by +; knocking out own Pokémon, dismiss attack. + ld a, 1 ; count active Pokémon as KO'd + call .check_if_kos_bench + jr c, .dismiss_high_recoil_atk + jr .encourage_high_recoil_atk + +; Rock Crusher Deck only uses this attack if +; prize count is below 4 and attack wins (or potentially draws) the duel, +; (i.e. at least gets KOs equal to prize cards left). +.rock_crusher_deck + call CountPrizes + cp 4 + jr nc, .dismiss_high_recoil_atk + ; prize count < 4 + ld b, 20 ; damage dealt to bench + call SwapTurn + xor a + call .check_if_kos_bench + call SwapTurn + jr c, .encourage_high_recoil_atk + +; generic checks for all other deck IDs. +; encourage attack if it wins (or potentially draws) the duel, +; (i.e. at least gets KOs equal to prize cards left). +; dismiss it if it causes the player to win. +.high_recoil_generic_checks + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp CHANSEY + jr z, .chansey + cp MAGNEMITE1 + jr z, .magnemite1_or_weezing + cp WEEZING + jr z, .magnemite1_or_weezing + ld b, 20 ; bench damage + jr .check_bench_kos +.magnemite1_or_weezing + ld b, 10 ; bench damage + jr .check_bench_kos +.chansey + ld b, 0 ; no bench damage + +.check_bench_kos + push bc + call SwapTurn + xor a + call .check_if_kos_bench + call SwapTurn + pop bc + jr c, .wins_the_duel + push de + ld a, 1 + call .check_if_kos_bench + pop bc + jr nc, .count_own_ko_bench + +; attack causes player to draw all prize cards + xor a + ld [wAIScore], a + jp .done + +; attack causes CPU to draw all prize cards +.wins_the_duel + ld a, 20 + call AddToAIScore + jp .done + +; subtract from AI score number of own benched Pokémon KO'd +.count_own_ko_bench + push bc + ld a, d + or a + jr z, .count_player_ko_bench + dec a + call SubFromAIScore + +; add to AI score number of player benched Pokémon KO'd +.count_player_ko_bench + pop bc + ld a, b + call AddToAIScore + jr .check_defending_can_ko + +; local function that gets called to determine damage to +; benched Pokémon caused by a HIGH_RECOIL attack. +; return carry if using attack causes number of benched Pokémon KOs +; equal to or larger than remaining prize cards. +; this function is independent on duelist turn, so whatever +; turn it is when this is called, it's that duelist's +; bench/prize cards that get checked. +; input: +; a = initial number of KO's beside benched Pokémon, +; so that if the active Pokémon is KO'd by the attack, +; this counts towards the prize cards collected +; b = damage dealt to bench Pokémon +.check_if_kos_bench + ld d, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld e, PLAY_AREA_ARENA +.loop + inc e + ld a, [hli] + cp $ff + jr z, .exit_loop + ld a, e + add DUELVARS_ARENA_CARD_HP + push hl + call GetTurnDuelistVariable + pop hl + cp b + jr z, .increase_count + jr nc, .loop +.increase_count + ; increase d if damage dealt KOs + inc d + jr .loop +.exit_loop + push de + call SwapTurn + call CountPrizes + call SwapTurn + pop de + cp d + jp c, .set_carry + jp z, .set_carry + or a + ret +.set_carry + scf + ret + +; if defending card can KO, encourage attack +; unless attack is non-damaging. +.check_defending_can_ko + ld a, [wSelectedAttack] + push af + call CheckIfDefendingPokemonCanKnockOut + pop bc + ld a, b + ld [wSelectedAttack], a + jr nc, .check_discard + ld a, 5 + call AddToAIScore + ld a, [wAIAttackIsNonDamaging] + or a + jr z, .check_discard + ld a, 5 + call SubFromAIScore + +; subtract from AI score if this attack requires +; discarding any energy cards. +.check_discard + ld a, [wSelectedAttack] + ld e, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F + call CheckLoadedAttackFlag + jr nc, .asm_16ca6 + ld a, 1 + call SubFromAIScore + ld a, [wLoadedAttackEffectParam] + call SubFromAIScore + +.asm_16ca6 + ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_6_F + call CheckLoadedAttackFlag + jr nc, .check_nullify_flag + ld a, [wLoadedAttackEffectParam] + call AddToAIScore + +; encourage attack if it has a nullify or weaken attack effect. +.check_nullify_flag + ld a, ATTACK_FLAG2_ADDRESS | NULLIFY_OR_WEAKEN_ATTACK_F + call CheckLoadedAttackFlag + jr nc, .check_draw_flag + ld a, 1 + call AddToAIScore + +; encourage attack if it has an effect to draw a card. +.check_draw_flag + ld a, ATTACK_FLAG1_ADDRESS | DRAW_CARD_F + call CheckLoadedAttackFlag + jr nc, .check_heal_flag + ld a, 1 + call AddToAIScore + +.check_heal_flag + ld a, ATTACK_FLAG2_ADDRESS | HEAL_USER_F + call CheckLoadedAttackFlag + jr nc, .check_status_effect + ld a, [wLoadedAttackEffectParam] + cp 1 + jr z, .tally_heal_score + ld a, [wTempAI] + call CalculateByteTensDigit + ld b, a + ld a, [wLoadedAttackEffectParam] + cp 3 + jr z, .asm_16cec + srl b + jr nc, .asm_16cec + inc b +.asm_16cec + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + call CalculateByteTensDigit + cp b + jr c, .tally_heal_score + ld a, b +.tally_heal_score + push af + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call CalculateByteTensDigit + pop bc + cp b ; wLoadedAttackEffectParam + jr c, .add_heal_score + ld a, b +.add_heal_score + call AddToAIScore + +.check_status_effect + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + ; skip if player has Snorlax + cp SNORLAX + jp z, .handle_special_atks + + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + ld [wTempAI], a + +; encourage a poison inflicting attack if opposing Pokémon +; isn't (doubly) poisoned already. +; if opposing Pokémon is only poisoned and not double poisoned, +; and this attack has FLAG_2_BIT_6 set, discourage it +; (possibly to make Nidoking's Toxic attack less likely to be chosen +; if the other Pokémon is poisoned.) + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_POISON_F + call CheckLoadedAttackFlag + jr nc, .check_sleep + ld a, [wTempAI] + and DOUBLE_POISONED + jr z, .add_poison_score + and $40 ; only double poisoned? + jr z, .check_sleep + ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_6_F + call CheckLoadedAttackFlag + jr nc, .check_sleep + ld a, 2 + call SubFromAIScore + jr .check_sleep +.add_poison_score + ld a, 2 + call AddToAIScore + +; encourage sleep-inducing attack if other Pokémon isn't asleep. +.check_sleep + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_SLEEP_F + call CheckLoadedAttackFlag + jr nc, .check_paralysis + ld a, [wTempAI] + and CNF_SLP_PRZ + cp ASLEEP + jr z, .check_paralysis + ld a, 1 + call AddToAIScore + +; encourage paralysis-inducing attack if other Pokémon isn't asleep. +; otherwise, if other Pokémon is asleep, discourage attack. +.check_paralysis + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_PARALYSIS_F + call CheckLoadedAttackFlag + jr nc, .check_confusion + ld a, [wTempAI] + and CNF_SLP_PRZ + cp ASLEEP + jr z, .sub_prz_score + ld a, 1 + call AddToAIScore + jr .check_confusion +.sub_prz_score + ld a, 1 + call SubFromAIScore + +; encourage confuse-inducing attack if other Pokémon isn't asleep +; or confused already. +; otherwise, if other Pokémon is asleep or confused, +; discourage attack instead. +.check_confusion + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_CONFUSION_F + call CheckLoadedAttackFlag + jr nc, .check_if_confused + ld a, [wTempAI] + and CNF_SLP_PRZ + cp ASLEEP + jr z, .sub_cnf_score + ld a, [wTempAI] + and CNF_SLP_PRZ + cp CONFUSED + jr z, .check_if_confused + ld a, 1 + call AddToAIScore + jr .check_if_confused +.sub_cnf_score + ld a, 1 + call SubFromAIScore + +; if this Pokémon is confused, subtract from score. +.check_if_confused + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + cp CONFUSED + jr nz, .handle_special_atks + ld a, 1 + call SubFromAIScore + +; SPECIAL_AI_HANDLING marks attacks that the AI handles individually. +; each attack has its own checks and modifies AI score accordingly. +.handle_special_atks + ld a, ATTACK_FLAG3_ADDRESS | SPECIAL_AI_HANDLING_F + call CheckLoadedAttackFlag + jr nc, .done + call HandleSpecialAIAttacks + cp $80 + jr c, .negative_score + sub $80 + call AddToAIScore + jr .done +.negative_score + ld b, a + ld a, $80 + sub b + call SubFromAIScore + +.done + ret diff --git a/src/engine/ai/boss_deck_set_up.asm b/src/engine/ai/boss_deck_set_up.asm index fa3a262..ebcd2ea 100644 --- a/src/engine/ai/boss_deck_set_up.asm +++ b/src/engine/ai/boss_deck_set_up.asm @@ -1,167 +1,167 @@ -; sets up the initial hand of boss deck. -; always draws at least 2 Basic Pokemon cards and 2 Energy cards. -; also sets up so that the next cards to be drawn have -; some minimum number of Basic Pokemon and Energy cards. -SetUpBossStartingHandAndDeck: ; 172af (5:72af) -; shuffle all hand cards in deck - ld a, DUELVARS_HAND - call GetTurnDuelistVariable - ld b, STARTING_HAND_SIZE -.loop_hand - ld a, [hl] - call RemoveCardFromHand - call ReturnCardToDeck - dec b - jr nz, .loop_hand - jr .count_energy_basic - -.shuffle_deck - call ShuffleDeck - -; count number of Energy and basic Pokemon cards -; in the first STARTING_HAND_SIZE in deck. -.count_energy_basic - xor a - ld [wce06], a - ld [wce08], a - - ld a, DUELVARS_DECK_CARDS - call GetTurnDuelistVariable - ld b, STARTING_HAND_SIZE -.loop_deck_1 - ld a, [hli] - push bc - call LoadCardDataToBuffer1_FromDeckIndex - pop bc - ld a, [wLoadedCard1Type] - cp TYPE_ENERGY - jr c, .pokemon_card_1 - cp TYPE_TRAINER - jr z, .next_card_deck_1 - -; energy card - ld a, [wce08] - inc a - ld [wce08], a - jr .next_card_deck_1 - -.pokemon_card_1 - ld a, [wLoadedCard1Stage] - or a - jr nz, .next_card_deck_1 ; not basic - ld a, [wce06] - inc a - ld [wce06], a - -.next_card_deck_1 - dec b - jr nz, .loop_deck_1 - -; tally the number of Energy and basic Pokemon cards -; and if any of them is smaller than 2, re-shuffle deck. - ld a, [wce06] - cp 2 - jr c, .shuffle_deck - ld a, [wce08] - cp 2 - jr c, .shuffle_deck - -; now check the following 6 cards (prize cards). -; re-shuffle deck if any of these cards is listed in wAICardListAvoidPrize. - ld b, 6 -.check_card_ids - ld a, [hli] - push bc - call .CheckIfIDIsInList - pop bc - jr c, .shuffle_deck - dec b - jr nz, .check_card_ids - -; finally, check 6 cards after that. -; if Energy or Basic Pokemon counter is below 4 -; (counting with the ones found in the initial hand) -; then re-shuffle deck. - ld b, 6 -.loop_deck_2 - ld a, [hli] - push bc - call LoadCardDataToBuffer1_FromDeckIndex - pop bc - ld a, [wLoadedCard1Type] - cp TYPE_ENERGY - jr c, .pokemon_card_2 - cp TYPE_TRAINER - jr z, .next_card_deck_2 - -; energy card - ld a, [wce08] - inc a - ld [wce08], a - jr .next_card_deck_2 - -.pokemon_card_2 - ld a, [wLoadedCard1Stage] - or a - jr nz, .next_card_deck_2 - ld a, [wce06] - inc a - ld [wce06], a - -.next_card_deck_2 - dec b - jr nz, .loop_deck_2 - - ld a, [wce06] - cp 4 - jp c, .shuffle_deck - ld a, [wce08] - cp 4 - jp c, .shuffle_deck - -; draw new set of hand cards - ld a, DUELVARS_DECK_CARDS - call GetTurnDuelistVariable - ld b, STARTING_HAND_SIZE -.draw_loop - ld a, [hli] - call SearchCardInDeckAndAddToHand - call AddCardToHand - dec b - jr nz, .draw_loop - ret - -; expectation: return carry if card ID corresponding -; to the input deck index is listed in wAICardListAvoidPrize; -; reality: always returns no carry because when checking terminating -; byte in wAICardListAvoidPrize ($00), it wrongfully uses 'cp a' instead of 'or a', -; so it always ends up returning in the first item in list. -; input: -; - a = deck index of card to check -.CheckIfIDIsInList ; 17366 (5:7366) - ld b, a - ld a, [wAICardListAvoidPrize + 1] - or a - ret z ; null - push hl - ld h, a - ld a, [wAICardListAvoidPrize] - ld l, a - - ld a, b - call GetCardIDFromDeckIndex -.loop_id_list - ld a, [hli] - cp a ; bug, should be 'or a' - jr z, .false - cp e - jr nz, .loop_id_list - -; true - pop hl - scf - ret -.false - pop hl - or a - ret +; sets up the initial hand of boss deck. +; always draws at least 2 Basic Pokemon cards and 2 Energy cards. +; also sets up so that the next cards to be drawn have +; some minimum number of Basic Pokemon and Energy cards. +SetUpBossStartingHandAndDeck: ; 172af (5:72af) +; shuffle all hand cards in deck + ld a, DUELVARS_HAND + call GetTurnDuelistVariable + ld b, STARTING_HAND_SIZE +.loop_hand + ld a, [hl] + call RemoveCardFromHand + call ReturnCardToDeck + dec b + jr nz, .loop_hand + jr .count_energy_basic + +.shuffle_deck + call ShuffleDeck + +; count number of Energy and basic Pokemon cards +; in the first STARTING_HAND_SIZE in deck. +.count_energy_basic + xor a + ld [wce06], a + ld [wce08], a + + ld a, DUELVARS_DECK_CARDS + call GetTurnDuelistVariable + ld b, STARTING_HAND_SIZE +.loop_deck_1 + ld a, [hli] + push bc + call LoadCardDataToBuffer1_FromDeckIndex + pop bc + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr c, .pokemon_card_1 + cp TYPE_TRAINER + jr z, .next_card_deck_1 + +; energy card + ld a, [wce08] + inc a + ld [wce08], a + jr .next_card_deck_1 + +.pokemon_card_1 + ld a, [wLoadedCard1Stage] + or a + jr nz, .next_card_deck_1 ; not basic + ld a, [wce06] + inc a + ld [wce06], a + +.next_card_deck_1 + dec b + jr nz, .loop_deck_1 + +; tally the number of Energy and basic Pokemon cards +; and if any of them is smaller than 2, re-shuffle deck. + ld a, [wce06] + cp 2 + jr c, .shuffle_deck + ld a, [wce08] + cp 2 + jr c, .shuffle_deck + +; now check the following 6 cards (prize cards). +; re-shuffle deck if any of these cards is listed in wAICardListAvoidPrize. + ld b, 6 +.check_card_ids + ld a, [hli] + push bc + call .CheckIfIDIsInList + pop bc + jr c, .shuffle_deck + dec b + jr nz, .check_card_ids + +; finally, check 6 cards after that. +; if Energy or Basic Pokemon counter is below 4 +; (counting with the ones found in the initial hand) +; then re-shuffle deck. + ld b, 6 +.loop_deck_2 + ld a, [hli] + push bc + call LoadCardDataToBuffer1_FromDeckIndex + pop bc + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr c, .pokemon_card_2 + cp TYPE_TRAINER + jr z, .next_card_deck_2 + +; energy card + ld a, [wce08] + inc a + ld [wce08], a + jr .next_card_deck_2 + +.pokemon_card_2 + ld a, [wLoadedCard1Stage] + or a + jr nz, .next_card_deck_2 + ld a, [wce06] + inc a + ld [wce06], a + +.next_card_deck_2 + dec b + jr nz, .loop_deck_2 + + ld a, [wce06] + cp 4 + jp c, .shuffle_deck + ld a, [wce08] + cp 4 + jp c, .shuffle_deck + +; draw new set of hand cards + ld a, DUELVARS_DECK_CARDS + call GetTurnDuelistVariable + ld b, STARTING_HAND_SIZE +.draw_loop + ld a, [hli] + call SearchCardInDeckAndAddToHand + call AddCardToHand + dec b + jr nz, .draw_loop + ret + +; expectation: return carry if card ID corresponding +; to the input deck index is listed in wAICardListAvoidPrize; +; reality: always returns no carry because when checking terminating +; byte in wAICardListAvoidPrize ($00), it wrongfully uses 'cp a' instead of 'or a', +; so it always ends up returning in the first item in list. +; input: +; - a = deck index of card to check +.CheckIfIDIsInList ; 17366 (5:7366) + ld b, a + ld a, [wAICardListAvoidPrize + 1] + or a + ret z ; null + push hl + ld h, a + ld a, [wAICardListAvoidPrize] + ld l, a + + ld a, b + call GetCardIDFromDeckIndex +.loop_id_list + ld a, [hli] + cp a ; bug, should be 'or a' + jr z, .false + cp e + jr nz, .loop_id_list + +; true + pop hl + scf + ret +.false + pop hl + or a + ret diff --git a/src/engine/ai/damage_calculation.asm b/src/engine/ai/damage_calculation.asm index a4fcd27..97c24b6 100644 --- a/src/engine/ai/damage_calculation.asm +++ b/src/engine/ai/damage_calculation.asm @@ -1,450 +1,450 @@ -; stores in wDamage, wAIMinDamage and wAIMaxDamage the calculated damage -; done to the defending Pokémon by a given card and attack -; input: -; a = attack index to take into account -; [hTempPlayAreaLocation_ff9d] = location of attacking card to consider -EstimateDamage_VersusDefendingCard: ; 143e5 (5:43e5) - ld [wSelectedAttack], a - ld e, a - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ld d, a - call CopyAttackDataAndDamage_FromDeckIndex - ld a, [wLoadedAttackCategory] - cp POKEMON_POWER - jr nz, .is_attack - -; is a Pokémon Power -; set wDamage, wAIMinDamage and wAIMaxDamage to zero - ld hl, wDamage - xor a - ld [hli], a - ld [hl], a - ld [wAIMinDamage], a - ld [wAIMaxDamage], a - ld e, a - ld d, a - ret - -.is_attack -; set wAIMinDamage and wAIMaxDamage to damage of attack -; these values take into account the range of damage -; that the attack can span (e.g. min and max number of hits) - ld a, [wDamage] - ld [wAIMinDamage], a - ld [wAIMaxDamage], a - ld a, EFFECTCMDTYPE_AI - call TryExecuteEffectCommandFunction - ld a, [wAIMinDamage] - ld hl, wAIMaxDamage - or [hl] - jr nz, .calculation - ld a, [wDamage] - ld [wAIMinDamage], a - ld [wAIMaxDamage], a - -.calculation -; if temp. location is active, damage calculation can be done directly... - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr z, CalculateDamage_VersusDefendingPokemon - -; ...otherwise substatuses need to be temporarily reset to account -; for the switching, to obtain the right damage calculation... - ; reset substatus1 - ld a, DUELVARS_ARENA_CARD_SUBSTATUS1 - call GetTurnDuelistVariable - push af - push hl - ld [hl], $00 - ; reset substatus2 - ld l, DUELVARS_ARENA_CARD_SUBSTATUS2 - ld a, [hl] - push af - push hl - ld [hl], $00 - ; reset changed resistance - ld l, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE - ld a, [hl] - push af - push hl - ld [hl], $00 - call CalculateDamage_VersusDefendingPokemon -; ...and subsequently recovered to continue the duel normally - pop hl - pop af - ld [hl], a - pop hl - pop af - ld [hl], a - pop hl - pop af - ld [hl], a - ret - -; calculates the damage that will be dealt to the player's active card -; using the card that is located in hTempPlayAreaLocation_ff9d -; taking into account weakness/resistance/pluspowers/defenders/etc -; and outputs the result capped at a max of $ff -; input: -; [wAIMinDamage] = base damage -; [wAIMaxDamage] = base damage -; [wDamage] = base damage -; [hTempPlayAreaLocation_ff9d] = turn holder's card location as the attacker -CalculateDamage_VersusDefendingPokemon: ; 14453 (5:4453) - ld hl, wAIMinDamage - call _CalculateDamage_VersusDefendingPokemon - ld hl, wAIMaxDamage - call _CalculateDamage_VersusDefendingPokemon - ld hl, wDamage -; fallthrough - -_CalculateDamage_VersusDefendingPokemon: ; 14462 (5:4462) - ld e, [hl] - ld d, $00 - push hl - - ; load this card's data - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2ID] - ld [wTempTurnDuelistCardID], a - - ; load player's arena card data - call SwapTurn - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2ID] - ld [wTempNonTurnDuelistCardID], a - call SwapTurn - - push de - call HandleNoDamageOrEffectSubstatus - pop de - jr nc, .vulnerable - ; invulnerable to damage - ld de, $0 - jr .done -.vulnerable - ldh a, [hTempPlayAreaLocation_ff9d] - or a - call z, HandleDoubleDamageSubstatus - ; skips the weak/res checks if unaffected. - bit UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d - res UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d - jr nz, .not_resistant - -; handle weakness - ldh a, [hTempPlayAreaLocation_ff9d] - call GetPlayAreaCardColor - call TranslateColorToWR - ld b, a - call SwapTurn - call GetArenaCardWeakness - call SwapTurn - and b - jr z, .not_weak - ; double de - sla e - rl d - -.not_weak -; handle resistance - call SwapTurn - call GetArenaCardResistance - call SwapTurn - and b - jr z, .not_resistant - ld hl, -30 - add hl, de - ld e, l - ld d, h - -.not_resistant - ; apply pluspower and defender boosts - ldh a, [hTempPlayAreaLocation_ff9d] - add CARD_LOCATION_ARENA - ld b, a - call ApplyAttachedPluspower - call SwapTurn - ld b, CARD_LOCATION_ARENA - call ApplyAttachedDefender - call HandleDamageReduction - ; test if de underflowed - bit 7, d - jr z, .no_underflow - ld de, $0 - -.no_underflow - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and DOUBLE_POISONED - jr z, .not_poisoned - ld c, 20 - and DOUBLE_POISONED & (POISONED ^ $ff) - jr nz, .add_poison - ld c, 10 -.add_poison - ld a, c - add e - ld e, a - ld a, $00 - adc d - ld d, a -.not_poisoned - call SwapTurn - -.done - pop hl - ld [hl], e - ld a, d - or a - ret z - ; cap damage - ld a, $ff - ld [hl], a - ret - -; stores in wDamage, wAIMinDamage and wAIMaxDamage the calculated damage -; done to the Pokémon at hTempPlayAreaLocation_ff9d -; by the defending Pokémon, using the attack index at a -; input: -; a = attack index -; [hTempPlayAreaLocation_ff9d] = location of card to calculate -; damage as the receiver -EstimateDamage_FromDefendingPokemon: ; 1450b (5:450b) - call SwapTurn - ld [wSelectedAttack], a - ld e, a - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ld d, a - call CopyAttackDataAndDamage_FromDeckIndex - call SwapTurn - ld a, [wLoadedAttackCategory] - cp POKEMON_POWER - jr nz, .is_attack - -; is a Pokémon Power -; set wDamage, wAIMinDamage and wAIMaxDamage to zero - ld hl, wDamage - xor a - ld [hli], a - ld [hl], a - ld [wAIMinDamage], a - ld [wAIMaxDamage], a - ld e, a - ld d, a - ret - -.is_attack -; set wAIMinDamage and wAIMaxDamage to damage of attack -; these values take into account the range of damage -; that the attack can span (e.g. min and max number of hits) - ld a, [wDamage] - ld [wAIMinDamage], a - ld [wAIMaxDamage], a - call SwapTurn - ldh a, [hTempPlayAreaLocation_ff9d] - push af - xor a - ldh [hTempPlayAreaLocation_ff9d], a - ld a, EFFECTCMDTYPE_AI - call TryExecuteEffectCommandFunction - pop af - ldh [hTempPlayAreaLocation_ff9d], a - call SwapTurn - ld a, [wAIMinDamage] - ld hl, wAIMaxDamage - or [hl] - jr nz, .calculation - ld a, [wDamage] - ld [wAIMinDamage], a - ld [wAIMaxDamage], a - -.calculation -; if temp. location is active, damage calculation can be done directly... - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr z, CalculateDamage_FromDefendingPokemon - -; ...otherwise substatuses need to be temporarily reset to account -; for the switching, to obtain the right damage calculation... - ld a, DUELVARS_ARENA_CARD_SUBSTATUS1 - call GetTurnDuelistVariable - push af - push hl - ld [hl], $00 - ; reset substatus2 - ld l, DUELVARS_ARENA_CARD_SUBSTATUS2 - ld a, [hl] - push af - push hl - ld [hl], $00 - ; reset changed resistance - ld l, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE - ld a, [hl] - push af - push hl - ld [hl], $00 - call CalculateDamage_FromDefendingPokemon -; ...and subsequently recovered to continue the duel normally - pop hl - pop af - ld [hl], a - pop hl - pop af - ld [hl], a - pop hl - pop af - ld [hl], a - ret - -; similar to CalculateDamage_VersusDefendingPokemon but reversed, -; calculating damage of the defending Pokémon versus -; the card located in hTempPlayAreaLocation_ff9d -; taking into account weakness/resistance/pluspowers/defenders/etc -; and poison damage for two turns -; and outputs the result capped at a max of $ff -; input: -; [wAIMinDamage] = base damage -; [wAIMaxDamage] = base damage -; [wDamage] = base damage -; [hTempPlayAreaLocation_ff9d] = location of card to calculate -; damage as the receiver -CalculateDamage_FromDefendingPokemon: ; 1458c (5:458c) - ld hl, wAIMinDamage - call .CalculateDamage - ld hl, wAIMaxDamage - call .CalculateDamage - ld hl, wDamage - ; fallthrough - -.CalculateDamage ; 1459b (5:459b) - ld e, [hl] - ld d, $00 - push hl - - ; load player active card's data - call SwapTurn - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2ID] - ld [wTempTurnDuelistCardID], a - call SwapTurn - - ; load opponent's card data - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2ID] - ld [wTempNonTurnDuelistCardID], a - - call SwapTurn - call HandleDoubleDamageSubstatus - bit UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d - res UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d - jr nz, .not_resistant - -; handle weakness - call GetArenaCardColor - call TranslateColorToWR - ld b, a - call SwapTurn - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .bench_weak - ld a, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS - call GetTurnDuelistVariable - or a - jr nz, .unchanged_weak - -.bench_weak - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2Weakness] -.unchanged_weak - and b - jr z, .not_weak - ; double de - sla e - rl d - -.not_weak -; handle resistance - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .bench_res - ld a, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE - call GetTurnDuelistVariable - or a - jr nz, .unchanged_res - -.bench_res - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2Resistance] -.unchanged_res - and b - jr z, .not_resistant - ld hl, -30 - add hl, de - ld e, l - ld d, h - -.not_resistant - ; apply pluspower and defender boosts - call SwapTurn - ld b, CARD_LOCATION_ARENA - call ApplyAttachedPluspower - call SwapTurn - ldh a, [hTempPlayAreaLocation_ff9d] - add CARD_LOCATION_ARENA - ld b, a - call ApplyAttachedDefender - ldh a, [hTempPlayAreaLocation_ff9d] - or a - call z, HandleDamageReduction - bit 7, d - jr z, .no_underflow - ld de, $0 - -.no_underflow - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .done - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and DOUBLE_POISONED - jr z, .done - ld c, 40 - and DOUBLE_POISONED & (POISONED ^ $ff) - jr nz, .add_poison - ld c, 20 -.add_poison - ld a, c - add e - ld e, a - ld a, $00 - adc d - ld d, a - -.done - pop hl - ld [hl], e - ld a, d - or a - ret z - ld a, $ff - ld [hl], a - ret +; stores in wDamage, wAIMinDamage and wAIMaxDamage the calculated damage +; done to the defending Pokémon by a given card and attack +; input: +; a = attack index to take into account +; [hTempPlayAreaLocation_ff9d] = location of attacking card to consider +EstimateDamage_VersusDefendingCard: ; 143e5 (5:43e5) + ld [wSelectedAttack], a + ld e, a + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr nz, .is_attack + +; is a Pokémon Power +; set wDamage, wAIMinDamage and wAIMaxDamage to zero + ld hl, wDamage + xor a + ld [hli], a + ld [hl], a + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + ld e, a + ld d, a + ret + +.is_attack +; set wAIMinDamage and wAIMaxDamage to damage of attack +; these values take into account the range of damage +; that the attack can span (e.g. min and max number of hits) + ld a, [wDamage] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + ld a, EFFECTCMDTYPE_AI + call TryExecuteEffectCommandFunction + ld a, [wAIMinDamage] + ld hl, wAIMaxDamage + or [hl] + jr nz, .calculation + ld a, [wDamage] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + +.calculation +; if temp. location is active, damage calculation can be done directly... + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr z, CalculateDamage_VersusDefendingPokemon + +; ...otherwise substatuses need to be temporarily reset to account +; for the switching, to obtain the right damage calculation... + ; reset substatus1 + ld a, DUELVARS_ARENA_CARD_SUBSTATUS1 + call GetTurnDuelistVariable + push af + push hl + ld [hl], $00 + ; reset substatus2 + ld l, DUELVARS_ARENA_CARD_SUBSTATUS2 + ld a, [hl] + push af + push hl + ld [hl], $00 + ; reset changed resistance + ld l, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE + ld a, [hl] + push af + push hl + ld [hl], $00 + call CalculateDamage_VersusDefendingPokemon +; ...and subsequently recovered to continue the duel normally + pop hl + pop af + ld [hl], a + pop hl + pop af + ld [hl], a + pop hl + pop af + ld [hl], a + ret + +; calculates the damage that will be dealt to the player's active card +; using the card that is located in hTempPlayAreaLocation_ff9d +; taking into account weakness/resistance/pluspowers/defenders/etc +; and outputs the result capped at a max of $ff +; input: +; [wAIMinDamage] = base damage +; [wAIMaxDamage] = base damage +; [wDamage] = base damage +; [hTempPlayAreaLocation_ff9d] = turn holder's card location as the attacker +CalculateDamage_VersusDefendingPokemon: ; 14453 (5:4453) + ld hl, wAIMinDamage + call _CalculateDamage_VersusDefendingPokemon + ld hl, wAIMaxDamage + call _CalculateDamage_VersusDefendingPokemon + ld hl, wDamage +; fallthrough + +_CalculateDamage_VersusDefendingPokemon: ; 14462 (5:4462) + ld e, [hl] + ld d, $00 + push hl + + ; load this card's data + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2ID] + ld [wTempTurnDuelistCardID], a + + ; load player's arena card data + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2ID] + ld [wTempNonTurnDuelistCardID], a + call SwapTurn + + push de + call HandleNoDamageOrEffectSubstatus + pop de + jr nc, .vulnerable + ; invulnerable to damage + ld de, $0 + jr .done +.vulnerable + ldh a, [hTempPlayAreaLocation_ff9d] + or a + call z, HandleDoubleDamageSubstatus + ; skips the weak/res checks if unaffected. + bit UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d + res UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d + jr nz, .not_resistant + +; handle weakness + ldh a, [hTempPlayAreaLocation_ff9d] + call GetPlayAreaCardColor + call TranslateColorToWR + ld b, a + call SwapTurn + call GetArenaCardWeakness + call SwapTurn + and b + jr z, .not_weak + ; double de + sla e + rl d + +.not_weak +; handle resistance + call SwapTurn + call GetArenaCardResistance + call SwapTurn + and b + jr z, .not_resistant + ld hl, -30 + add hl, de + ld e, l + ld d, h + +.not_resistant + ; apply pluspower and defender boosts + ldh a, [hTempPlayAreaLocation_ff9d] + add CARD_LOCATION_ARENA + ld b, a + call ApplyAttachedPluspower + call SwapTurn + ld b, CARD_LOCATION_ARENA + call ApplyAttachedDefender + call HandleDamageReduction + ; test if de underflowed + bit 7, d + jr z, .no_underflow + ld de, $0 + +.no_underflow + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and DOUBLE_POISONED + jr z, .not_poisoned + ld c, 20 + and DOUBLE_POISONED & (POISONED ^ $ff) + jr nz, .add_poison + ld c, 10 +.add_poison + ld a, c + add e + ld e, a + ld a, $00 + adc d + ld d, a +.not_poisoned + call SwapTurn + +.done + pop hl + ld [hl], e + ld a, d + or a + ret z + ; cap damage + ld a, $ff + ld [hl], a + ret + +; stores in wDamage, wAIMinDamage and wAIMaxDamage the calculated damage +; done to the Pokémon at hTempPlayAreaLocation_ff9d +; by the defending Pokémon, using the attack index at a +; input: +; a = attack index +; [hTempPlayAreaLocation_ff9d] = location of card to calculate +; damage as the receiver +EstimateDamage_FromDefendingPokemon: ; 1450b (5:450b) + call SwapTurn + ld [wSelectedAttack], a + ld e, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + call SwapTurn + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr nz, .is_attack + +; is a Pokémon Power +; set wDamage, wAIMinDamage and wAIMaxDamage to zero + ld hl, wDamage + xor a + ld [hli], a + ld [hl], a + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + ld e, a + ld d, a + ret + +.is_attack +; set wAIMinDamage and wAIMaxDamage to damage of attack +; these values take into account the range of damage +; that the attack can span (e.g. min and max number of hits) + ld a, [wDamage] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + call SwapTurn + ldh a, [hTempPlayAreaLocation_ff9d] + push af + xor a + ldh [hTempPlayAreaLocation_ff9d], a + ld a, EFFECTCMDTYPE_AI + call TryExecuteEffectCommandFunction + pop af + ldh [hTempPlayAreaLocation_ff9d], a + call SwapTurn + ld a, [wAIMinDamage] + ld hl, wAIMaxDamage + or [hl] + jr nz, .calculation + ld a, [wDamage] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + +.calculation +; if temp. location is active, damage calculation can be done directly... + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr z, CalculateDamage_FromDefendingPokemon + +; ...otherwise substatuses need to be temporarily reset to account +; for the switching, to obtain the right damage calculation... + ld a, DUELVARS_ARENA_CARD_SUBSTATUS1 + call GetTurnDuelistVariable + push af + push hl + ld [hl], $00 + ; reset substatus2 + ld l, DUELVARS_ARENA_CARD_SUBSTATUS2 + ld a, [hl] + push af + push hl + ld [hl], $00 + ; reset changed resistance + ld l, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE + ld a, [hl] + push af + push hl + ld [hl], $00 + call CalculateDamage_FromDefendingPokemon +; ...and subsequently recovered to continue the duel normally + pop hl + pop af + ld [hl], a + pop hl + pop af + ld [hl], a + pop hl + pop af + ld [hl], a + ret + +; similar to CalculateDamage_VersusDefendingPokemon but reversed, +; calculating damage of the defending Pokémon versus +; the card located in hTempPlayAreaLocation_ff9d +; taking into account weakness/resistance/pluspowers/defenders/etc +; and poison damage for two turns +; and outputs the result capped at a max of $ff +; input: +; [wAIMinDamage] = base damage +; [wAIMaxDamage] = base damage +; [wDamage] = base damage +; [hTempPlayAreaLocation_ff9d] = location of card to calculate +; damage as the receiver +CalculateDamage_FromDefendingPokemon: ; 1458c (5:458c) + ld hl, wAIMinDamage + call .CalculateDamage + ld hl, wAIMaxDamage + call .CalculateDamage + ld hl, wDamage + ; fallthrough + +.CalculateDamage ; 1459b (5:459b) + ld e, [hl] + ld d, $00 + push hl + + ; load player active card's data + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2ID] + ld [wTempTurnDuelistCardID], a + call SwapTurn + + ; load opponent's card data + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2ID] + ld [wTempNonTurnDuelistCardID], a + + call SwapTurn + call HandleDoubleDamageSubstatus + bit UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d + res UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, d + jr nz, .not_resistant + +; handle weakness + call GetArenaCardColor + call TranslateColorToWR + ld b, a + call SwapTurn + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .bench_weak + ld a, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS + call GetTurnDuelistVariable + or a + jr nz, .unchanged_weak + +.bench_weak + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Weakness] +.unchanged_weak + and b + jr z, .not_weak + ; double de + sla e + rl d + +.not_weak +; handle resistance + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .bench_res + ld a, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE + call GetTurnDuelistVariable + or a + jr nz, .unchanged_res + +.bench_res + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Resistance] +.unchanged_res + and b + jr z, .not_resistant + ld hl, -30 + add hl, de + ld e, l + ld d, h + +.not_resistant + ; apply pluspower and defender boosts + call SwapTurn + ld b, CARD_LOCATION_ARENA + call ApplyAttachedPluspower + call SwapTurn + ldh a, [hTempPlayAreaLocation_ff9d] + add CARD_LOCATION_ARENA + ld b, a + call ApplyAttachedDefender + ldh a, [hTempPlayAreaLocation_ff9d] + or a + call z, HandleDamageReduction + bit 7, d + jr z, .no_underflow + ld de, $0 + +.no_underflow + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .done + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and DOUBLE_POISONED + jr z, .done + ld c, 40 + and DOUBLE_POISONED & (POISONED ^ $ff) + jr nz, .add_poison + ld c, 20 +.add_poison + ld a, c + add e + ld e, a + ld a, $00 + adc d + ld d, a + +.done + pop hl + ld [hl], e + ld a, d + or a + ret z + ld a, $ff + ld [hl], a + ret diff --git a/src/engine/ai/decks/unreferenced.asm b/src/engine/ai/decks/unreferenced.asm index 3cd56c3..8722a27 100644 --- a/src/engine/ai/decks/unreferenced.asm +++ b/src/engine/ai/decks/unreferenced.asm @@ -1,42 +1,42 @@ -AIActionTable_Unreferenced: ; 1406a (5:406a) - dw $406c - dw .do_turn - dw .do_turn - dw .star_duel - dw .forced_switch - dw .ko_switch - dw .take_prize - -.do_turn - call AIDecidePlayPokemonCard - call AIDecideWhetherToRetreat - jr nc, .try_attack - call AIDecideBenchPokemonToSwitchTo - call AITryToRetreat - call AIDecideWhetherToRetreat - jr nc, .try_attack - call AIDecideBenchPokemonToSwitchTo - call AITryToRetreat -.try_attack - call AIProcessAndTryToPlayEnergy - call AIProcessAndTryToUseAttack - ret c - ld a, OPPACTION_FINISH_NO_ATTACK - bank1call AIMakeDecision - ret - -.star_duel - call AIPlayInitialBasicCards - ret - -.forced_switch - call AIDecideBenchPokemonToSwitchTo - ret - -.ko_switch - call AIDecideBenchPokemonToSwitchTo - ret - -.take_prize - call AIPickPrizeCards - ret +AIActionTable_Unreferenced: ; 1406a (5:406a) + dw $406c + dw .do_turn + dw .do_turn + dw .star_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn + call AIDecidePlayPokemonCard + call AIDecideWhetherToRetreat + jr nc, .try_attack + call AIDecideBenchPokemonToSwitchTo + call AITryToRetreat + call AIDecideWhetherToRetreat + jr nc, .try_attack + call AIDecideBenchPokemonToSwitchTo + call AITryToRetreat +.try_attack + call AIProcessAndTryToPlayEnergy + call AIProcessAndTryToUseAttack + ret c + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret + +.star_duel + call AIPlayInitialBasicCards + ret + +.forced_switch + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize + call AIPickPrizeCards + ret diff --git a/src/engine/ai/energy.asm b/src/engine/ai/energy.asm index af1aa32..ce8c037 100644 --- a/src/engine/ai/energy.asm +++ b/src/engine/ai/energy.asm @@ -1,1048 +1,1048 @@ -; processes AI energy card playing logic -; with AI_ENERGY_FLAG_DONT_PLAY flag on -; unreferenced -Func_16488: ; 16488 (5:6488) - ld a, AI_ENERGY_FLAG_DONT_PLAY - ld [wAIEnergyAttachLogicFlags], a - ld de, wTempPlayAreaAIScore - ld hl, wPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - ld a, [wAIScore] - ld [de], a - jr AIProcessAndTryToPlayEnergy.has_logic_flags - -; have AI choose an energy card to play, but do not play it. -; does not consider whether the cards have evolutions to be played. -; return carry if an energy card is chosen to use in any Play Area card, -; and if so, return its Play Area location in hTempPlayAreaLocation_ff9d. -AIProcessButDontPlayEnergy_SkipEvolution: ; 164a1 (5:64a1) - ld a, AI_ENERGY_FLAG_DONT_PLAY | AI_ENERGY_FLAG_SKIP_EVOLUTION - ld [wAIEnergyAttachLogicFlags], a - -; backup wPlayAreaAIScore in wTempPlayAreaAIScore. - ld de, wTempPlayAreaAIScore - ld hl, wPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - - ld a, [wAIScore] - ld [de], a - - jr AIProcessEnergyCards - -; have AI choose an energy card to play, but do not play it. -; does not consider whether the cards have evolutions to be played. -; return carry if an energy card is chosen to use in any Bench card, -; and if so, return its Play Area location in hTempPlayAreaLocation_ff9d. -AIProcessButDontPlayEnergy_SkipEvolutionAndArena: ; 164ba (5:64ba) - ld a, AI_ENERGY_FLAG_DONT_PLAY | AI_ENERGY_FLAG_SKIP_EVOLUTION | AI_ENERGY_FLAG_SKIP_ARENA_CARD - ld [wAIEnergyAttachLogicFlags], a - -; backup wPlayAreaAIScore in wTempPlayAreaAIScore. - ld de, wTempPlayAreaAIScore - ld hl, wPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - - ld a, [wAIScore] - ld [de], a - - jr AIProcessEnergyCards - -; copies wTempPlayAreaAIScore to wPlayAreaAIScore -; and loads wAIScore with value in wTempAIScore. -; identical to RetrievePlayAreaAIScoreFromBackup2. -RetrievePlayAreaAIScoreFromBackup1: ; 164d3 (5:64d3) - push af - ld de, wPlayAreaAIScore - ld hl, wTempPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - ld a, [hl] - ld [wAIScore], a - pop af - ret - -; have AI decide whether to play energy card from hand -; and determine which card is best to attach it. -AIProcessAndTryToPlayEnergy: ; 164e8 (5:64e8) - xor a - ld [wAIEnergyAttachLogicFlags], a - -.has_logic_flags - call CreateEnergyCardListFromHand - jr nc, AIProcessEnergyCards - -; no energy - ld a, [wAIEnergyAttachLogicFlags] - or a - jr z, .exit - jp RetrievePlayAreaAIScoreFromBackup1 -.exit - or a - ret - -; have AI decide whether to play energy card -; and determine which card is best to attach it. -AIProcessEnergyCards: ; 164fc (5:64fc) -; initialize Play Area AI score - ld a, $80 - ld b, MAX_PLAY_AREA_POKEMON - ld hl, wPlayAreaEnergyAIScore -.loop - ld [hli], a - dec b - jr nz, .loop - -; Legendary Articuno Deck has its own energy card logic - call HandleLegendaryArticunoEnergyScoring - -; start the main Play Area loop - ld b, PLAY_AREA_ARENA - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - ld c, a - -.loop_play_area - push bc - ld a, b - ldh [hTempPlayAreaLocation_ff9d], a - ld a, $80 - ld [wAIScore], a - ld a, $ff - ld [wTempAI], a - ld a, [wAIEnergyAttachLogicFlags] - and AI_ENERGY_FLAG_SKIP_EVOLUTION - jr nz, .check_venusaur - -; check if energy needed is found in hand -; and if there's an evolution in hand or deck -; and if so, add to AI score - call CreateHandCardList - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ld [wCurCardCanAttack], a - call GetAttacksEnergyCostBits - ld hl, wDuelTempList - call CheckEnergyFlagsNeededInList - jp nc, .store_score - ld a, [wCurCardCanAttack] - call CheckForEvolutionInList - jr nc, .no_evolution_in_hand - ld [wTempAI], a ; store evolution card found - ld a, 2 - call AddToAIScore - jr .check_venusaur - -.no_evolution_in_hand - ld a, [wCurCardCanAttack] - call CheckForEvolutionInDeck - jr nc, .check_venusaur - ld a, 1 - call AddToAIScore - -; if there's no Muk in any Play Area -; and there's Venusaur2 in own Play Area, -; add to AI score -.check_venusaur - ld a, MUK - call CountPokemonIDInBothPlayAreas - jr c, .check_if_active - ld a, VENUSAUR2 - call CountPokemonIDInPlayArea - jr nc, .check_if_active - ld a, 1 - call AddToAIScore - -.check_if_active - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .bench - -; arena - ld a, [wAIBarrierFlagCounter] - bit AI_MEWTWO_MILL_F, a - jr z, .add_to_score - -; subtract from score instead -; if Player is running Mewtwo1 mill deck. - ld a, 5 - call SubFromAIScore - jr .check_defending_can_ko - -.add_to_score - ld a, 4 - call AddToAIScore - -; lower AI score if poison/double poison -; will KO Pokémon between turns -; or if the defending Pokémon can KO - ld a, DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - call CalculateByteTensDigit - cp 3 - jr nc, .check_defending_can_ko - ; hp < 30 - cp 2 - jr z, .has_20_hp - ; hp = 10 - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and POISONED - jr z, .check_defending_can_ko - jr .poison_will_ko -.has_20_hp - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and DOUBLE_POISONED - jr z, .check_defending_can_ko -.poison_will_ko - ld a, 10 - call SubFromAIScore - jr .check_bench -.check_defending_can_ko - call CheckIfDefendingPokemonCanKnockOut - jr nc, .ai_score_bonus - ld a, 10 - call SubFromAIScore - -; if either poison will KO or defending Pokémon can KO, -; check if there are bench Pokémon, -; if there are not, add AI score -.check_bench - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - dec a - jr nz, .ai_score_bonus - ld a, 6 - call AddToAIScore - jr .ai_score_bonus - -; lower AI score by 3 - (bench HP)/10 -; if bench HP < 30 -.bench - add DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - call CalculateByteTensDigit - cp 3 - jr nc, .ai_score_bonus -; hp < 30 - ld b, a - ld a, 3 - sub b - call SubFromAIScore - -; check list in wAICardListEnergyBonus -.ai_score_bonus - ld a, [wAICardListEnergyBonus + 1] - or a - jr z, .check_boss_deck ; is null - ld h, a - ld a, [wAICardListEnergyBonus] - ld l, a - - push hl - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - pop hl - -.loop_id_list - ld a, [hli] - or a - jr z, .check_boss_deck - cp e - jr nz, .next_id - - ; number of attached energy cards - ld a, [hli] - ld d, a - push de - ldh a, [hTempPlayAreaLocation_ff9d] - ld e, a - call GetPlayAreaCardAttachedEnergies - ld a, [wTotalAttachedEnergies] - pop de - cp d - jr c, .check_id_score - ; already reached target number of energy cards - ld a, 10 - call SubFromAIScore - jr .store_score - -.check_id_score - ld a, [hli] - cp $80 - jr c, .decrease_score_1 - sub $80 - call AddToAIScore - jr .check_boss_deck - -.decrease_score_1 - ld d, a - ld a, $80 - sub d - call SubFromAIScore - jr .check_boss_deck - -.next_id - inc hl - inc hl - jr .loop_id_list - -; if it's a boss deck, call Func_174f2 -; and apply to the AI score the values -; determined for this card -.check_boss_deck - call CheckIfNotABossDeckID - jr c, .skip_boss_deck - - call Func_174f2 - ldh a, [hTempPlayAreaLocation_ff9d] - ld c, a - ld b, $00 - ld hl, wPlayAreaEnergyAIScore - add hl, bc - ld a, [hl] - cp $80 - jr c, .decrease_score_2 - sub $80 - call AddToAIScore - jr .skip_boss_deck - -.decrease_score_2 - ld b, a - ld a, $80 - sub b - call SubFromAIScore - -.skip_boss_deck - ld a, 1 - call AddToAIScore - -; add AI score for both attacks, -; according to their energy requirements. - xor a ; first attack - call DetermineAIScoreOfAttackEnergyRequirement - ld a, SECOND_ATTACK - call DetermineAIScoreOfAttackEnergyRequirement - -; store bench score for this card. -.store_score - ldh a, [hTempPlayAreaLocation_ff9d] - ld c, a - ld b, $00 - ld hl, wPlayAreaAIScore - add hl, bc - ld a, [wAIScore] - ld [hl], a - pop bc - inc b - dec c - jp nz, .loop_play_area - -; the Play Area loop is over and the score -; for each card has been calculated. -; now to determine the highest score. - call FindPlayAreaCardWithHighestAIScore - jp nc, .not_found - - ld a, [wAIEnergyAttachLogicFlags] - or a - jr z, .play_card - scf - jp RetrievePlayAreaAIScoreFromBackup1 - -.play_card - call CreateEnergyCardListFromHand - jp AITryToPlayEnergyCard - -.not_found: ; 1668a (5:668a) - ld a, [wAIEnergyAttachLogicFlags] - or a - jr z, .no_carry - jp RetrievePlayAreaAIScoreFromBackup1 -.no_carry - or a - ret - -; checks score related to selected attack, -; in order to determine whether to play energy card. -; the AI score is increased/decreased accordingly. -; input: -; [wSelectedAttack] = attack to check. -DetermineAIScoreOfAttackEnergyRequirement: ; 16695 (5:6695) - ld [wSelectedAttack], a - call CheckEnergyNeededForAttack - jp c, .not_enough_energy - ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F - call CheckLoadedAttackFlag - jr c, .attached_energy_boost - ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F - call CheckLoadedAttackFlag - jr c, .discard_energy - jp .check_evolution - -.attached_energy_boost - ld a, [wLoadedAttackEffectParam] - cp MAX_ENERGY_BOOST_IS_LIMITED - jr z, .check_surplus_energy - - ; is MAX_ENERGY_BOOST_IS_NOT_LIMITED, - ; which is equal to 3, add to score. - call AddToAIScore - jp .check_evolution - -.check_surplus_energy - call CheckIfNoSurplusEnergyForAttack - jr c, .asm_166cd - cp 3 ; check how much surplus energy - jr c, .asm_166cd - -.asm_166c5 - ld a, 5 - call SubFromAIScore - jp .check_evolution - -.asm_166cd - ld a, 2 - call AddToAIScore - -; check whether attack has ATTACHED_ENERGY_BOOST flag -; and add to AI score if attaching another energy -; will KO defending Pokémon. -; add more to score if this is currently active Pokémon. - ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F - call CheckLoadedAttackFlag - jp nc, .check_evolution - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, DUELVARS_ARENA_CARD_HP - call GetNonTurnDuelistVariable - ld hl, wDamage - sub [hl] - jp c, .check_evolution - jp z, .check_evolution - ld a, [wDamage] - add 10 ; boost gained by attaching another energy card - ld b, a - ld a, DUELVARS_ARENA_CARD_HP - call GetNonTurnDuelistVariable - sub b - jr c, .attaching_kos_player - jr nz, .check_evolution - -.attaching_kos_player - ld a, 20 - call AddToAIScore - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .check_evolution - ld a, 10 - call AddToAIScore - jr .check_evolution - -; checks if there is surplus energy for attack -; that discards attached energy card. -; if current card is Zapdos2, don't add to score. -; if there is no surplus energy, encourage playing energy. -.discard_energy - ld a, [wLoadedCard1ID] - cp ZAPDOS2 - jr z, .check_evolution - call CheckIfNoSurplusEnergyForAttack - jr c, .asm_166cd - jr .asm_166c5 - -.not_enough_energy - ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_5_F - call CheckLoadedAttackFlag - jr nc, .check_color_needed - ld a, 5 - call SubFromAIScore - -; if the energy card color needed is in hand, increase AI score. -; if a colorless card is needed, increase AI score. -.check_color_needed - ld a, b - or a - jr z, .check_colorless_needed - ld a, e - call LookForCardIDInHand - jr c, .check_colorless_needed - ld a, 4 - call AddToAIScore - jr .check_total_needed -.check_colorless_needed - ld a, c - or a - jr z, .check_evolution - ld a, 3 - call AddToAIScore - -; if only one energy card is needed for attack, -; encourage playing energy card. -.check_total_needed - ld a, b - add c - dec a - jr nz, .check_evolution - ld a, 3 - call AddToAIScore - -; if the attack KOs player and this is the active card, add to AI score. - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .check_evolution - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, DUELVARS_ARENA_CARD_HP - call GetNonTurnDuelistVariable - ld hl, wDamage - sub [hl] - jr z, .atk_kos_defending - jr nc, .check_evolution -.atk_kos_defending - ld a, 20 - call AddToAIScore - -; this is possibly a bug. -; this is an identical check as above to test whether this card is active. -; in case it is active, the score gets added 10 more points, -; in addition to the 20 points already added above. -; what was probably intended was to add 20 points -; plus 10 in case it is the Arena card. - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .check_evolution - ld a, 10 - call AddToAIScore - -.check_evolution - ld a, [wTempAI] ; evolution in hand - cp $ff - ret z - -; temporarily replace this card with evolution in hand. - ld b, a - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - push af - ld [hl], b - -; check for energy still needed for evolution to attack. -; if FLAG_2_BIT_5 is not set, check what color is needed. -; if the energy card color needed is in hand, increase AI score. -; if a colorless card is needed, increase AI score. - call CheckEnergyNeededForAttack - jr nc, .done - ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_5_F - call CheckLoadedAttackFlag - jr c, .done - ld a, b - or a - jr z, .check_colorless_needed_evo - ld a, e - call LookForCardIDInHand - jr c, .check_colorless_needed_evo - ld a, 2 - call AddToAIScore - jr .done -.check_colorless_needed_evo - ld a, c - or a - jr z, .done - ld a, 1 - call AddToAIScore - -; recover the original card in the Play Area location. -.done - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - pop af - ld [hl], a - ret - -; returns in hTempPlayAreaLocation_ff9d the Play Area location -; of the card with the highest Play Area AI score, unless -; the highest score is below $85. -; if it succeeds in return a card location, set carry. -; if AI_ENERGY_FLAG_SKIP_ARENA_CARD is set in wAIEnergyAttachLogicFlags -; doesn't include the Arena card and there's no minimum score. -FindPlayAreaCardWithHighestAIScore: ; 167b5 (5:67b5) - ld a, [wAIEnergyAttachLogicFlags] - and AI_ENERGY_FLAG_SKIP_ARENA_CARD - jr nz, .only_bench - - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - ld b, a - ld c, PLAY_AREA_ARENA - ld e, c - ld d, c - ld hl, wPlayAreaAIScore -; find highest Play Area AI score. -.loop_1 - ld a, [hli] - cp e - jr c, .next_1 - jr z, .next_1 - ld e, a ; overwrite highest score found - ld d, c ; overwrite Play Area of highest score -.next_1 - inc c - dec b - jr nz, .loop_1 - -; if highest AI score is below $85, return no carry. -; else, store Play Area location and return carry. - ld a, e - cp $85 - jr c, .not_enough_score - ld a, d - ldh [hTempPlayAreaLocation_ff9d], a - scf - ret -.not_enough_score - or a - ret - -; same as above but only check bench Pokémon scores. -.only_bench - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - dec a - jr z, .no_carry - - ld b, a - ld e, 0 - ld c, PLAY_AREA_BENCH_1 - ld d, c - ld hl, wPlayAreaAIScore + 1 -.loop_2 - ld a, [hli] - cp e - jr c, .next_2 - jr z, .next_2 - ld e, a ; overwrite highest score found - ld d, c ; overwrite Play Area of highest score -.next_2 - inc c - dec b - jr nz, .loop_2 - -; in this case, there is no minimum threshold AI score. - ld a, d - ldh [hTempPlayAreaLocation_ff9d], a - scf - ret -.no_carry - or a - ret - -; returns carry if there's an evolution card -; that can evolve card in hTempPlayAreaLocation_ff9d, -; and that card needs energy to use wSelectedAttack. -CheckIfEvolutionNeedsEnergyForAttack: ; 16805 (5:6805) - call CreateHandCardList - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call CheckCardEvolutionInHandOrDeck - jr c, .has_evolution - or a - ret - -.has_evolution - ld b, a - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - push af - ld [hl], b - call CheckEnergyNeededForAttack - jr c, .not_enough_energy - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - pop af - ld [hl], a - or a - ret - -.not_enough_energy - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - pop af - ld [hl], a - scf - ret - -; returns in e the card ID of the energy required for -; the Discard/Energy Boost attack loaded in wSelectedAttack. -; if it's Zapdos2's Thunderbolt attack, return no carry. -; if it's Charizard's Fire Spin or Exeggutor's Big Eggsplosion -; attack, don't return energy card ID, but set carry. -; output: -; b = 1 if needs color energy, 0 otherwise; -; c = 1 if only needs colorless energy, 0 otherwise; -; carry set if not Zapdos2's Thunderbolt attack. -GetEnergyCardForDiscardOrEnergyBoostAttack: ; 1683b (5:683b) -; load card ID and check selected attack index. - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld b, a - ld a, [wSelectedAttack] - or a - jr z, .first_attack - -; check if second attack is Zapdos2's Thunderbolt, -; Charizard's Fire Spin or Exeggutor's Big Eggsplosion, -; for these to be treated differently. -; for both attacks, load its energy cost. - ld a, b - cp ZAPDOS2 - jr z, .zapdos2 - cp CHARIZARD - jr z, .charizard_or_exeggutor - cp EXEGGUTOR - jr z, .charizard_or_exeggutor - ld hl, wLoadedCard2Atk2EnergyCost - jr .fire -.first_attack - ld hl, wLoadedCard2Atk1EnergyCost - -; check which energy color the attack requires, -; and load in e the card ID of corresponding energy card, -; then return carry flag set. -.fire - ld a, [hli] - ld b, a - and $f0 - jr z, .grass - ld e, FIRE_ENERGY - jr .set_carry -.grass - ld a, b - and $0f - jr z, .lightning - ld e, GRASS_ENERGY - jr .set_carry -.lightning - ld a, [hli] - ld b, a - and $f0 - jr z, .water - ld e, LIGHTNING_ENERGY - jr .set_carry -.water - ld a, b - and $0f - jr z, .fighting - ld e, WATER_ENERGY - jr .set_carry -.fighting - ld a, [hli] - ld b, a - and $f0 - jr z, .psychic - ld e, FIGHTING_ENERGY - jr .set_carry -.psychic - ld e, PSYCHIC_ENERGY - -.set_carry - lb bc, $01, $00 - scf - ret - -; for Zapdos2's Thunderbolt attack, return with no carry. -.zapdos2 - or a - ret - -; Charizard's Fire Spin and Exeggutor's Big Eggsplosion, -; return carry. -.charizard_or_exeggutor - lb bc, $00, $01 - scf - ret - -; called after the AI has decided which card to attach -; energy from hand. AI does checks to determine whether -; this card needs more energy or not, and chooses the -; right energy card to play. If the card is played, -; return with carry flag set. -AITryToPlayEnergyCard: ; 1689f (5:689f) -; check if energy cards are still needed for attacks. -; if first attack doesn't need, test for the second attack. - xor a - ld [wTempAI], a - ld [wSelectedAttack], a - call CheckEnergyNeededForAttack - jr nc, .second_attack - ld a, b - or a - jr nz, .check_deck - ld a, c - or a - jr nz, .check_deck - -.second_attack - ld a, SECOND_ATTACK - ld [wSelectedAttack], a - call CheckEnergyNeededForAttack - jr nc, .check_discard_or_energy_boost - ld a, b - or a - jr nz, .check_deck - ld a, c - or a - jr nz, .check_deck - -; neither attack needs energy cards to be used. -; check whether these attacks can be given -; extra energy cards for their effects. -.check_discard_or_energy_boost - ld a, $01 - ld [wTempAI], a - -; for both attacks, check if it has the effect of -; discarding energy cards or attached energy boost. - xor a ; FIRST_ATTACK_OR_PKMN_POWER - ld [wSelectedAttack], a - call CheckEnergyNeededForAttack - ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F - call CheckLoadedAttackFlag - jr c, .energy_boost_or_discard_energy - ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F - call CheckLoadedAttackFlag - jr c, .energy_boost_or_discard_energy - - ld a, SECOND_ATTACK - ld [wSelectedAttack], a - call CheckEnergyNeededForAttack - ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F - call CheckLoadedAttackFlag - jr c, .energy_boost_or_discard_energy - ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F - call CheckLoadedAttackFlag - jr c, .energy_boost_or_discard_energy - -; if none of the attacks have those flags, do an additional -; check to ascertain whether evolution card needs energy -; to use second attack. Return if all these checks fail. - call CheckIfEvolutionNeedsEnergyForAttack - ret nc - call CreateEnergyCardListFromHand - jr .check_deck - -; for attacks that discard energy or get boost for -; additional energy cards, get the energy card ID required by attack. -; if it's Zapdos2's Thunderbolt attack, return. -.energy_boost_or_discard_energy - call GetEnergyCardForDiscardOrEnergyBoostAttack - ret nc - -; some decks allow basic Pokémon to be given double colorless -; in anticipation for evolution, so play card if that is the case. -.check_deck - call CheckSpecificDecksToAttachDoubleColorless - jr c, .play_energy_card - - ld a, b - or a - jr z, .colorless_energy - -; in this case, Pokémon needs a specific basic energy card. -; look for basic energy card needed in hand and play it. - ld a, e - call LookForCardIDInHand - ldh [hTemp_ffa0], a - jr nc, .play_energy_card - -; in this case Pokémon just needs colorless (any basic energy card). -; if active card, check if it needs 2 colorless. -; if it does (and also doesn't additionally need a color energy), -; look for double colorless card in hand and play it if found. -.colorless_energy - ldh a, [hTempPlayAreaLocation_ff9d] - or a - jr nz, .look_for_any_energy - ld a, c - or a - jr z, .check_if_done - cp 2 - jr nz, .look_for_any_energy - - ; needs two colorless - ld hl, wDuelTempList -.loop_1 - ld a, [hli] - cp $ff - jr z, .look_for_any_energy - ldh [hTemp_ffa0], a - call GetCardIDFromDeckIndex - ld a, e - cp DOUBLE_COLORLESS_ENERGY - jr nz, .loop_1 - jr .play_energy_card - -; otherwise, look for any card and play it. -; if it's a boss deck, only play double colorless in this situation. -.look_for_any_energy - ld hl, wDuelTempList - call CountCardsInDuelTempList - call ShuffleCards -.loop_2 - ld a, [hli] - cp $ff - jr z, .check_if_done - call CheckIfOpponentHasBossDeckID - jr nc, .load_card - push af - call GetCardIDFromDeckIndex - ld a, e - cp DOUBLE_COLORLESS_ENERGY - pop bc - jr z, .loop_2 - ld a, b -.load_card - ldh [hTemp_ffa0], a - -; plays energy card loaded in hTemp_ffa0 and sets carry flag. -.play_energy_card - ldh a, [hTempPlayAreaLocation_ff9d] - ldh [hTempPlayAreaLocation_ffa1], a - ld a, OPPACTION_PLAY_ENERGY - bank1call AIMakeDecision - scf - ret - -; wTempAI is 1 if the attack had a Discard/Energy Boost effect, -; and 0 otherwise. If 1, then return. If not one, check if -; there is still a second attack to check. -.check_if_done - ld a, [wTempAI] - or a - jr z, .check_first_attack - ret -.check_first_attack - ld a, [wSelectedAttack] - or a - jp z, .second_attack - ret - -; check if playing certain decks so that AI can decide whether to play -; double colorless to some specific cards. -; these are cards that do not need double colorless to any of their attacks -; but are required by their evolutions. -; return carry if there's a double colorless in hand to attach -; and it's one of the card IDs from these decks. -; output: -; [hTemp_ffa0] = card index of double colorless in hand; -; carry set if can play energy card. -CheckSpecificDecksToAttachDoubleColorless: ; 1696e (5:696e) - push bc - push de - push hl - -; check if AI is playing any of the applicable decks. - ld a, [wOpponentDeckID] - cp LEGENDARY_DRAGONITE_DECK_ID - jr z, .legendary_dragonite_deck - cp FIRE_CHARGE_DECK_ID - jr z, .fire_charge_deck - cp LEGENDARY_RONALD_DECK_ID - jr z, .legendary_ronald_deck - -.no_carry - pop hl - pop de - pop bc - or a - ret - -; if playing Legendary Dragonite deck, -; check for Charmander and Dratini. -.legendary_dragonite_deck - call .get_id - cp CHARMANDER - jr z, .check_colorless_attached - cp DRATINI - jr z, .check_colorless_attached - jr .no_carry - -; if playing Fire Charge deck, -; check for Growlithe. -.fire_charge_deck - call .get_id - cp GROWLITHE - jr z, .check_colorless_attached - jr .no_carry - -; if playing Legendary Ronald deck, -; check for Dratini. -.legendary_ronald_deck - call .get_id - cp DRATINI - jr z, .check_colorless_attached - jr .no_carry - -; check if card has any colorless energy cards attached, -; and if there are any, return no carry. -.check_colorless_attached - ldh a, [hTempPlayAreaLocation_ff9d] - ld e, a - call GetPlayAreaCardAttachedEnergies - ld a, [wAttachedEnergies + COLORLESS] - or a - jr nz, .no_carry - -; card has no colorless energy, so look for double colorless -; in hand and if found, return carry and its card index. - ld a, DOUBLE_COLORLESS_ENERGY - call LookForCardIDInHand - jr c, .no_carry - ldh [hTemp_ffa0], a - pop hl - pop de - pop bc - scf - ret - -.get_id: - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - ret +; processes AI energy card playing logic +; with AI_ENERGY_FLAG_DONT_PLAY flag on +; unreferenced +Func_16488: ; 16488 (5:6488) + ld a, AI_ENERGY_FLAG_DONT_PLAY + ld [wAIEnergyAttachLogicFlags], a + ld de, wTempPlayAreaAIScore + ld hl, wPlayAreaAIScore + ld b, MAX_PLAY_AREA_POKEMON +.loop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .loop + ld a, [wAIScore] + ld [de], a + jr AIProcessAndTryToPlayEnergy.has_logic_flags + +; have AI choose an energy card to play, but do not play it. +; does not consider whether the cards have evolutions to be played. +; return carry if an energy card is chosen to use in any Play Area card, +; and if so, return its Play Area location in hTempPlayAreaLocation_ff9d. +AIProcessButDontPlayEnergy_SkipEvolution: ; 164a1 (5:64a1) + ld a, AI_ENERGY_FLAG_DONT_PLAY | AI_ENERGY_FLAG_SKIP_EVOLUTION + ld [wAIEnergyAttachLogicFlags], a + +; backup wPlayAreaAIScore in wTempPlayAreaAIScore. + ld de, wTempPlayAreaAIScore + ld hl, wPlayAreaAIScore + ld b, MAX_PLAY_AREA_POKEMON +.loop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .loop + + ld a, [wAIScore] + ld [de], a + + jr AIProcessEnergyCards + +; have AI choose an energy card to play, but do not play it. +; does not consider whether the cards have evolutions to be played. +; return carry if an energy card is chosen to use in any Bench card, +; and if so, return its Play Area location in hTempPlayAreaLocation_ff9d. +AIProcessButDontPlayEnergy_SkipEvolutionAndArena: ; 164ba (5:64ba) + ld a, AI_ENERGY_FLAG_DONT_PLAY | AI_ENERGY_FLAG_SKIP_EVOLUTION | AI_ENERGY_FLAG_SKIP_ARENA_CARD + ld [wAIEnergyAttachLogicFlags], a + +; backup wPlayAreaAIScore in wTempPlayAreaAIScore. + ld de, wTempPlayAreaAIScore + ld hl, wPlayAreaAIScore + ld b, MAX_PLAY_AREA_POKEMON +.loop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .loop + + ld a, [wAIScore] + ld [de], a + + jr AIProcessEnergyCards + +; copies wTempPlayAreaAIScore to wPlayAreaAIScore +; and loads wAIScore with value in wTempAIScore. +; identical to RetrievePlayAreaAIScoreFromBackup2. +RetrievePlayAreaAIScoreFromBackup1: ; 164d3 (5:64d3) + push af + ld de, wPlayAreaAIScore + ld hl, wTempPlayAreaAIScore + ld b, MAX_PLAY_AREA_POKEMON +.loop + ld a, [hli] + ld [de], a + inc de + dec b + jr nz, .loop + ld a, [hl] + ld [wAIScore], a + pop af + ret + +; have AI decide whether to play energy card from hand +; and determine which card is best to attach it. +AIProcessAndTryToPlayEnergy: ; 164e8 (5:64e8) + xor a + ld [wAIEnergyAttachLogicFlags], a + +.has_logic_flags + call CreateEnergyCardListFromHand + jr nc, AIProcessEnergyCards + +; no energy + ld a, [wAIEnergyAttachLogicFlags] + or a + jr z, .exit + jp RetrievePlayAreaAIScoreFromBackup1 +.exit + or a + ret + +; have AI decide whether to play energy card +; and determine which card is best to attach it. +AIProcessEnergyCards: ; 164fc (5:64fc) +; initialize Play Area AI score + ld a, $80 + ld b, MAX_PLAY_AREA_POKEMON + ld hl, wPlayAreaEnergyAIScore +.loop + ld [hli], a + dec b + jr nz, .loop + +; Legendary Articuno Deck has its own energy card logic + call HandleLegendaryArticunoEnergyScoring + +; start the main Play Area loop + ld b, PLAY_AREA_ARENA + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + +.loop_play_area + push bc + ld a, b + ldh [hTempPlayAreaLocation_ff9d], a + ld a, $80 + ld [wAIScore], a + ld a, $ff + ld [wTempAI], a + ld a, [wAIEnergyAttachLogicFlags] + and AI_ENERGY_FLAG_SKIP_EVOLUTION + jr nz, .check_venusaur + +; check if energy needed is found in hand +; and if there's an evolution in hand or deck +; and if so, add to AI score + call CreateHandCardList + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld [wCurCardCanAttack], a + call GetAttacksEnergyCostBits + ld hl, wDuelTempList + call CheckEnergyFlagsNeededInList + jp nc, .store_score + ld a, [wCurCardCanAttack] + call CheckForEvolutionInList + jr nc, .no_evolution_in_hand + ld [wTempAI], a ; store evolution card found + ld a, 2 + call AddToAIScore + jr .check_venusaur + +.no_evolution_in_hand + ld a, [wCurCardCanAttack] + call CheckForEvolutionInDeck + jr nc, .check_venusaur + ld a, 1 + call AddToAIScore + +; if there's no Muk in any Play Area +; and there's Venusaur2 in own Play Area, +; add to AI score +.check_venusaur + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .check_if_active + ld a, VENUSAUR2 + call CountPokemonIDInPlayArea + jr nc, .check_if_active + ld a, 1 + call AddToAIScore + +.check_if_active + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .bench + +; arena + ld a, [wAIBarrierFlagCounter] + bit AI_MEWTWO_MILL_F, a + jr z, .add_to_score + +; subtract from score instead +; if Player is running Mewtwo1 mill deck. + ld a, 5 + call SubFromAIScore + jr .check_defending_can_ko + +.add_to_score + ld a, 4 + call AddToAIScore + +; lower AI score if poison/double poison +; will KO Pokémon between turns +; or if the defending Pokémon can KO + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + call CalculateByteTensDigit + cp 3 + jr nc, .check_defending_can_ko + ; hp < 30 + cp 2 + jr z, .has_20_hp + ; hp = 10 + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and POISONED + jr z, .check_defending_can_ko + jr .poison_will_ko +.has_20_hp + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and DOUBLE_POISONED + jr z, .check_defending_can_ko +.poison_will_ko + ld a, 10 + call SubFromAIScore + jr .check_bench +.check_defending_can_ko + call CheckIfDefendingPokemonCanKnockOut + jr nc, .ai_score_bonus + ld a, 10 + call SubFromAIScore + +; if either poison will KO or defending Pokémon can KO, +; check if there are bench Pokémon, +; if there are not, add AI score +.check_bench + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + jr nz, .ai_score_bonus + ld a, 6 + call AddToAIScore + jr .ai_score_bonus + +; lower AI score by 3 - (bench HP)/10 +; if bench HP < 30 +.bench + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + call CalculateByteTensDigit + cp 3 + jr nc, .ai_score_bonus +; hp < 30 + ld b, a + ld a, 3 + sub b + call SubFromAIScore + +; check list in wAICardListEnergyBonus +.ai_score_bonus + ld a, [wAICardListEnergyBonus + 1] + or a + jr z, .check_boss_deck ; is null + ld h, a + ld a, [wAICardListEnergyBonus] + ld l, a + + push hl + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + pop hl + +.loop_id_list + ld a, [hli] + or a + jr z, .check_boss_deck + cp e + jr nz, .next_id + + ; number of attached energy cards + ld a, [hli] + ld d, a + push de + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + pop de + cp d + jr c, .check_id_score + ; already reached target number of energy cards + ld a, 10 + call SubFromAIScore + jr .store_score + +.check_id_score + ld a, [hli] + cp $80 + jr c, .decrease_score_1 + sub $80 + call AddToAIScore + jr .check_boss_deck + +.decrease_score_1 + ld d, a + ld a, $80 + sub d + call SubFromAIScore + jr .check_boss_deck + +.next_id + inc hl + inc hl + jr .loop_id_list + +; if it's a boss deck, call Func_174f2 +; and apply to the AI score the values +; determined for this card +.check_boss_deck + call CheckIfNotABossDeckID + jr c, .skip_boss_deck + + call Func_174f2 + ldh a, [hTempPlayAreaLocation_ff9d] + ld c, a + ld b, $00 + ld hl, wPlayAreaEnergyAIScore + add hl, bc + ld a, [hl] + cp $80 + jr c, .decrease_score_2 + sub $80 + call AddToAIScore + jr .skip_boss_deck + +.decrease_score_2 + ld b, a + ld a, $80 + sub b + call SubFromAIScore + +.skip_boss_deck + ld a, 1 + call AddToAIScore + +; add AI score for both attacks, +; according to their energy requirements. + xor a ; first attack + call DetermineAIScoreOfAttackEnergyRequirement + ld a, SECOND_ATTACK + call DetermineAIScoreOfAttackEnergyRequirement + +; store bench score for this card. +.store_score + ldh a, [hTempPlayAreaLocation_ff9d] + ld c, a + ld b, $00 + ld hl, wPlayAreaAIScore + add hl, bc + ld a, [wAIScore] + ld [hl], a + pop bc + inc b + dec c + jp nz, .loop_play_area + +; the Play Area loop is over and the score +; for each card has been calculated. +; now to determine the highest score. + call FindPlayAreaCardWithHighestAIScore + jp nc, .not_found + + ld a, [wAIEnergyAttachLogicFlags] + or a + jr z, .play_card + scf + jp RetrievePlayAreaAIScoreFromBackup1 + +.play_card + call CreateEnergyCardListFromHand + jp AITryToPlayEnergyCard + +.not_found: ; 1668a (5:668a) + ld a, [wAIEnergyAttachLogicFlags] + or a + jr z, .no_carry + jp RetrievePlayAreaAIScoreFromBackup1 +.no_carry + or a + ret + +; checks score related to selected attack, +; in order to determine whether to play energy card. +; the AI score is increased/decreased accordingly. +; input: +; [wSelectedAttack] = attack to check. +DetermineAIScoreOfAttackEnergyRequirement: ; 16695 (5:6695) + ld [wSelectedAttack], a + call CheckEnergyNeededForAttack + jp c, .not_enough_energy + ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F + call CheckLoadedAttackFlag + jr c, .attached_energy_boost + ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F + call CheckLoadedAttackFlag + jr c, .discard_energy + jp .check_evolution + +.attached_energy_boost + ld a, [wLoadedAttackEffectParam] + cp MAX_ENERGY_BOOST_IS_LIMITED + jr z, .check_surplus_energy + + ; is MAX_ENERGY_BOOST_IS_NOT_LIMITED, + ; which is equal to 3, add to score. + call AddToAIScore + jp .check_evolution + +.check_surplus_energy + call CheckIfNoSurplusEnergyForAttack + jr c, .asm_166cd + cp 3 ; check how much surplus energy + jr c, .asm_166cd + +.asm_166c5 + ld a, 5 + call SubFromAIScore + jp .check_evolution + +.asm_166cd + ld a, 2 + call AddToAIScore + +; check whether attack has ATTACHED_ENERGY_BOOST flag +; and add to AI score if attaching another energy +; will KO defending Pokémon. +; add more to score if this is currently active Pokémon. + ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F + call CheckLoadedAttackFlag + jp nc, .check_evolution + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld hl, wDamage + sub [hl] + jp c, .check_evolution + jp z, .check_evolution + ld a, [wDamage] + add 10 ; boost gained by attaching another energy card + ld b, a + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + sub b + jr c, .attaching_kos_player + jr nz, .check_evolution + +.attaching_kos_player + ld a, 20 + call AddToAIScore + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .check_evolution + ld a, 10 + call AddToAIScore + jr .check_evolution + +; checks if there is surplus energy for attack +; that discards attached energy card. +; if current card is Zapdos2, don't add to score. +; if there is no surplus energy, encourage playing energy. +.discard_energy + ld a, [wLoadedCard1ID] + cp ZAPDOS2 + jr z, .check_evolution + call CheckIfNoSurplusEnergyForAttack + jr c, .asm_166cd + jr .asm_166c5 + +.not_enough_energy + ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_5_F + call CheckLoadedAttackFlag + jr nc, .check_color_needed + ld a, 5 + call SubFromAIScore + +; if the energy card color needed is in hand, increase AI score. +; if a colorless card is needed, increase AI score. +.check_color_needed + ld a, b + or a + jr z, .check_colorless_needed + ld a, e + call LookForCardIDInHand + jr c, .check_colorless_needed + ld a, 4 + call AddToAIScore + jr .check_total_needed +.check_colorless_needed + ld a, c + or a + jr z, .check_evolution + ld a, 3 + call AddToAIScore + +; if only one energy card is needed for attack, +; encourage playing energy card. +.check_total_needed + ld a, b + add c + dec a + jr nz, .check_evolution + ld a, 3 + call AddToAIScore + +; if the attack KOs player and this is the active card, add to AI score. + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .check_evolution + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld hl, wDamage + sub [hl] + jr z, .atk_kos_defending + jr nc, .check_evolution +.atk_kos_defending + ld a, 20 + call AddToAIScore + +; this is possibly a bug. +; this is an identical check as above to test whether this card is active. +; in case it is active, the score gets added 10 more points, +; in addition to the 20 points already added above. +; what was probably intended was to add 20 points +; plus 10 in case it is the Arena card. + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .check_evolution + ld a, 10 + call AddToAIScore + +.check_evolution + ld a, [wTempAI] ; evolution in hand + cp $ff + ret z + +; temporarily replace this card with evolution in hand. + ld b, a + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + push af + ld [hl], b + +; check for energy still needed for evolution to attack. +; if FLAG_2_BIT_5 is not set, check what color is needed. +; if the energy card color needed is in hand, increase AI score. +; if a colorless card is needed, increase AI score. + call CheckEnergyNeededForAttack + jr nc, .done + ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_5_F + call CheckLoadedAttackFlag + jr c, .done + ld a, b + or a + jr z, .check_colorless_needed_evo + ld a, e + call LookForCardIDInHand + jr c, .check_colorless_needed_evo + ld a, 2 + call AddToAIScore + jr .done +.check_colorless_needed_evo + ld a, c + or a + jr z, .done + ld a, 1 + call AddToAIScore + +; recover the original card in the Play Area location. +.done + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + ret + +; returns in hTempPlayAreaLocation_ff9d the Play Area location +; of the card with the highest Play Area AI score, unless +; the highest score is below $85. +; if it succeeds in return a card location, set carry. +; if AI_ENERGY_FLAG_SKIP_ARENA_CARD is set in wAIEnergyAttachLogicFlags +; doesn't include the Arena card and there's no minimum score. +FindPlayAreaCardWithHighestAIScore: ; 167b5 (5:67b5) + ld a, [wAIEnergyAttachLogicFlags] + and AI_ENERGY_FLAG_SKIP_ARENA_CARD + jr nz, .only_bench + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, PLAY_AREA_ARENA + ld e, c + ld d, c + ld hl, wPlayAreaAIScore +; find highest Play Area AI score. +.loop_1 + ld a, [hli] + cp e + jr c, .next_1 + jr z, .next_1 + ld e, a ; overwrite highest score found + ld d, c ; overwrite Play Area of highest score +.next_1 + inc c + dec b + jr nz, .loop_1 + +; if highest AI score is below $85, return no carry. +; else, store Play Area location and return carry. + ld a, e + cp $85 + jr c, .not_enough_score + ld a, d + ldh [hTempPlayAreaLocation_ff9d], a + scf + ret +.not_enough_score + or a + ret + +; same as above but only check bench Pokémon scores. +.only_bench + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + jr z, .no_carry + + ld b, a + ld e, 0 + ld c, PLAY_AREA_BENCH_1 + ld d, c + ld hl, wPlayAreaAIScore + 1 +.loop_2 + ld a, [hli] + cp e + jr c, .next_2 + jr z, .next_2 + ld e, a ; overwrite highest score found + ld d, c ; overwrite Play Area of highest score +.next_2 + inc c + dec b + jr nz, .loop_2 + +; in this case, there is no minimum threshold AI score. + ld a, d + ldh [hTempPlayAreaLocation_ff9d], a + scf + ret +.no_carry + or a + ret + +; returns carry if there's an evolution card +; that can evolve card in hTempPlayAreaLocation_ff9d, +; and that card needs energy to use wSelectedAttack. +CheckIfEvolutionNeedsEnergyForAttack: ; 16805 (5:6805) + call CreateHandCardList + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call CheckCardEvolutionInHandOrDeck + jr c, .has_evolution + or a + ret + +.has_evolution + ld b, a + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + push af + ld [hl], b + call CheckEnergyNeededForAttack + jr c, .not_enough_energy + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + or a + ret + +.not_enough_energy + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + scf + ret + +; returns in e the card ID of the energy required for +; the Discard/Energy Boost attack loaded in wSelectedAttack. +; if it's Zapdos2's Thunderbolt attack, return no carry. +; if it's Charizard's Fire Spin or Exeggutor's Big Eggsplosion +; attack, don't return energy card ID, but set carry. +; output: +; b = 1 if needs color energy, 0 otherwise; +; c = 1 if only needs colorless energy, 0 otherwise; +; carry set if not Zapdos2's Thunderbolt attack. +GetEnergyCardForDiscardOrEnergyBoostAttack: ; 1683b (5:683b) +; load card ID and check selected attack index. + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld b, a + ld a, [wSelectedAttack] + or a + jr z, .first_attack + +; check if second attack is Zapdos2's Thunderbolt, +; Charizard's Fire Spin or Exeggutor's Big Eggsplosion, +; for these to be treated differently. +; for both attacks, load its energy cost. + ld a, b + cp ZAPDOS2 + jr z, .zapdos2 + cp CHARIZARD + jr z, .charizard_or_exeggutor + cp EXEGGUTOR + jr z, .charizard_or_exeggutor + ld hl, wLoadedCard2Atk2EnergyCost + jr .fire +.first_attack + ld hl, wLoadedCard2Atk1EnergyCost + +; check which energy color the attack requires, +; and load in e the card ID of corresponding energy card, +; then return carry flag set. +.fire + ld a, [hli] + ld b, a + and $f0 + jr z, .grass + ld e, FIRE_ENERGY + jr .set_carry +.grass + ld a, b + and $0f + jr z, .lightning + ld e, GRASS_ENERGY + jr .set_carry +.lightning + ld a, [hli] + ld b, a + and $f0 + jr z, .water + ld e, LIGHTNING_ENERGY + jr .set_carry +.water + ld a, b + and $0f + jr z, .fighting + ld e, WATER_ENERGY + jr .set_carry +.fighting + ld a, [hli] + ld b, a + and $f0 + jr z, .psychic + ld e, FIGHTING_ENERGY + jr .set_carry +.psychic + ld e, PSYCHIC_ENERGY + +.set_carry + lb bc, $01, $00 + scf + ret + +; for Zapdos2's Thunderbolt attack, return with no carry. +.zapdos2 + or a + ret + +; Charizard's Fire Spin and Exeggutor's Big Eggsplosion, +; return carry. +.charizard_or_exeggutor + lb bc, $00, $01 + scf + ret + +; called after the AI has decided which card to attach +; energy from hand. AI does checks to determine whether +; this card needs more energy or not, and chooses the +; right energy card to play. If the card is played, +; return with carry flag set. +AITryToPlayEnergyCard: ; 1689f (5:689f) +; check if energy cards are still needed for attacks. +; if first attack doesn't need, test for the second attack. + xor a + ld [wTempAI], a + ld [wSelectedAttack], a + call CheckEnergyNeededForAttack + jr nc, .second_attack + ld a, b + or a + jr nz, .check_deck + ld a, c + or a + jr nz, .check_deck + +.second_attack + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + call CheckEnergyNeededForAttack + jr nc, .check_discard_or_energy_boost + ld a, b + or a + jr nz, .check_deck + ld a, c + or a + jr nz, .check_deck + +; neither attack needs energy cards to be used. +; check whether these attacks can be given +; extra energy cards for their effects. +.check_discard_or_energy_boost + ld a, $01 + ld [wTempAI], a + +; for both attacks, check if it has the effect of +; discarding energy cards or attached energy boost. + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + call CheckEnergyNeededForAttack + ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F + call CheckLoadedAttackFlag + jr c, .energy_boost_or_discard_energy + ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F + call CheckLoadedAttackFlag + jr c, .energy_boost_or_discard_energy + + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + call CheckEnergyNeededForAttack + ld a, ATTACK_FLAG2_ADDRESS | ATTACHED_ENERGY_BOOST_F + call CheckLoadedAttackFlag + jr c, .energy_boost_or_discard_energy + ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F + call CheckLoadedAttackFlag + jr c, .energy_boost_or_discard_energy + +; if none of the attacks have those flags, do an additional +; check to ascertain whether evolution card needs energy +; to use second attack. Return if all these checks fail. + call CheckIfEvolutionNeedsEnergyForAttack + ret nc + call CreateEnergyCardListFromHand + jr .check_deck + +; for attacks that discard energy or get boost for +; additional energy cards, get the energy card ID required by attack. +; if it's Zapdos2's Thunderbolt attack, return. +.energy_boost_or_discard_energy + call GetEnergyCardForDiscardOrEnergyBoostAttack + ret nc + +; some decks allow basic Pokémon to be given double colorless +; in anticipation for evolution, so play card if that is the case. +.check_deck + call CheckSpecificDecksToAttachDoubleColorless + jr c, .play_energy_card + + ld a, b + or a + jr z, .colorless_energy + +; in this case, Pokémon needs a specific basic energy card. +; look for basic energy card needed in hand and play it. + ld a, e + call LookForCardIDInHand + ldh [hTemp_ffa0], a + jr nc, .play_energy_card + +; in this case Pokémon just needs colorless (any basic energy card). +; if active card, check if it needs 2 colorless. +; if it does (and also doesn't additionally need a color energy), +; look for double colorless card in hand and play it if found. +.colorless_energy + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .look_for_any_energy + ld a, c + or a + jr z, .check_if_done + cp 2 + jr nz, .look_for_any_energy + + ; needs two colorless + ld hl, wDuelTempList +.loop_1 + ld a, [hli] + cp $ff + jr z, .look_for_any_energy + ldh [hTemp_ffa0], a + call GetCardIDFromDeckIndex + ld a, e + cp DOUBLE_COLORLESS_ENERGY + jr nz, .loop_1 + jr .play_energy_card + +; otherwise, look for any card and play it. +; if it's a boss deck, only play double colorless in this situation. +.look_for_any_energy + ld hl, wDuelTempList + call CountCardsInDuelTempList + call ShuffleCards +.loop_2 + ld a, [hli] + cp $ff + jr z, .check_if_done + call CheckIfOpponentHasBossDeckID + jr nc, .load_card + push af + call GetCardIDFromDeckIndex + ld a, e + cp DOUBLE_COLORLESS_ENERGY + pop bc + jr z, .loop_2 + ld a, b +.load_card + ldh [hTemp_ffa0], a + +; plays energy card loaded in hTemp_ffa0 and sets carry flag. +.play_energy_card + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_PLAY_ENERGY + bank1call AIMakeDecision + scf + ret + +; wTempAI is 1 if the attack had a Discard/Energy Boost effect, +; and 0 otherwise. If 1, then return. If not one, check if +; there is still a second attack to check. +.check_if_done + ld a, [wTempAI] + or a + jr z, .check_first_attack + ret +.check_first_attack + ld a, [wSelectedAttack] + or a + jp z, .second_attack + ret + +; check if playing certain decks so that AI can decide whether to play +; double colorless to some specific cards. +; these are cards that do not need double colorless to any of their attacks +; but are required by their evolutions. +; return carry if there's a double colorless in hand to attach +; and it's one of the card IDs from these decks. +; output: +; [hTemp_ffa0] = card index of double colorless in hand; +; carry set if can play energy card. +CheckSpecificDecksToAttachDoubleColorless: ; 1696e (5:696e) + push bc + push de + push hl + +; check if AI is playing any of the applicable decks. + ld a, [wOpponentDeckID] + cp LEGENDARY_DRAGONITE_DECK_ID + jr z, .legendary_dragonite_deck + cp FIRE_CHARGE_DECK_ID + jr z, .fire_charge_deck + cp LEGENDARY_RONALD_DECK_ID + jr z, .legendary_ronald_deck + +.no_carry + pop hl + pop de + pop bc + or a + ret + +; if playing Legendary Dragonite deck, +; check for Charmander and Dratini. +.legendary_dragonite_deck + call .get_id + cp CHARMANDER + jr z, .check_colorless_attached + cp DRATINI + jr z, .check_colorless_attached + jr .no_carry + +; if playing Fire Charge deck, +; check for Growlithe. +.fire_charge_deck + call .get_id + cp GROWLITHE + jr z, .check_colorless_attached + jr .no_carry + +; if playing Legendary Ronald deck, +; check for Dratini. +.legendary_ronald_deck + call .get_id + cp DRATINI + jr z, .check_colorless_attached + jr .no_carry + +; check if card has any colorless energy cards attached, +; and if there are any, return no carry. +.check_colorless_attached + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + COLORLESS] + or a + jr nz, .no_carry + +; card has no colorless energy, so look for double colorless +; in hand and if found, return carry and its card index. + ld a, DOUBLE_COLORLESS_ENERGY + call LookForCardIDInHand + jr c, .no_carry + ldh [hTemp_ffa0], a + pop hl + pop de + pop bc + scf + ret + +.get_id: + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ret diff --git a/src/engine/ai/hand_pokemon.asm b/src/engine/ai/hand_pokemon.asm index 28ff6b1..27a4176 100644 --- a/src/engine/ai/hand_pokemon.asm +++ b/src/engine/ai/hand_pokemon.asm @@ -1,627 +1,627 @@ -; determine whether AI plays -; basic cards from hand -AIDecidePlayPokemonCard: ; 15eae (5:5eae) - call CreateHandCardList - call SortTempHandByIDList - ld hl, wDuelTempList - ld de, wHandTempList - call CopyHandCardList - ld hl, wHandTempList - -.next_hand_card - ld a, [hli] - cp $ff - jp z, AIDecideEvolution - - ld [wTempAIPokemonCard], a - push hl - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - cp TYPE_ENERGY - jr nc, .skip - ; skip non-pokemon cards - - ld a, [wLoadedCard1Stage] - or a - jr nz, .skip - ; skip non-basic pokemon - - ld a, 130 - ld [wAIScore], a - call AIDecidePlayLegendaryBirds - -; if Play Area has more than 4 Pokémon, decrease AI score -; else, increase AI score - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 4 - jr c, .has_4_or_fewer - ld a, 20 - call SubFromAIScore - jr .check_defending_can_ko -.has_4_or_fewer - ld a, 50 - call AddToAIScore - -; if defending Pokémon can KO active card, increase AI score -.check_defending_can_ko - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfDefendingPokemonCanKnockOut - jr nc, .check_energy_cards - ld a, 20 - call AddToAIScore - -; if energy cards are found in hand -; for this card's attacks, raise AI score -.check_energy_cards - ld a, [wTempAIPokemonCard] - call GetAttacksEnergyCostBits - call CheckEnergyFlagsNeededInList - jr nc, .check_evolution_hand - ld a, 20 - call AddToAIScore - -; if evolution card is found in hand -; for this card, raise AI score -.check_evolution_hand - ld a, [wTempAIPokemonCard] - call CheckForEvolutionInList - jr nc, .check_evolution_deck - ld a, 20 - call AddToAIScore - -; if evolution card is found in deck -; for this card, raise AI score -.check_evolution_deck - ld a, [wTempAIPokemonCard] - call CheckForEvolutionInDeck - jr nc, .check_score - ld a, 10 - call AddToAIScore - -; if AI score is >= 180, play card from hand -.check_score - ld a, [wAIScore] - cp 180 - jr c, .skip - ld a, [wTempAIPokemonCard] - ldh [hTemp_ffa0], a - call CheckIfCardCanBePlayed - jr c, .skip - ld a, OPPACTION_PLAY_BASIC_PKMN - bank1call AIMakeDecision - jr c, .done -.skip - pop hl - jp .next_hand_card -.done - pop hl - ret - -; determine whether AI evolves -; Pokémon in the Play Area -AIDecideEvolution: ; 15f4c (5:5f4c) - call CreateHandCardList - ld hl, wDuelTempList - ld de, wHandTempList - call CopyHandCardList - ld hl, wHandTempList - -.next_hand_card - ld a, [hli] - cp $ff - jp z, .done - ld [wTempAIPokemonCard], a - -; check if Prehistoric Power is active -; and if so, skip to next card in hand - push hl - call IsPrehistoricPowerActive - jp c, .done_hand_card - -; load evolution data to buffer1 -; skip if it's not a Pokémon card -; and if it's a basic stage card - ld a, [wTempAIPokemonCard] - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - cp TYPE_ENERGY - jp nc, .done_hand_card - ld a, [wLoadedCard1Stage] - or a - jp z, .done_hand_card - -; start looping Pokémon in Play Area -; to find a card to evolve - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - ld c, a - ld b, 0 -.next_bench_pokemon - push bc - ld e, b - ld a, [wTempAIPokemonCard] - ld d, a - call CheckIfCanEvolveInto - pop bc - push bc - jp c, .done_bench_pokemon - -; store this Play Area location in wTempAI -; and initialize the AI score - ld a, b - ld [wTempAI], a - ldh [hTempPlayAreaLocation_ff9d], a - ld a, $80 - ld [wAIScore], a - call AIDecideSpecialEvolutions - -; check if the card can use any attacks -; and if any of those attacks can KO - xor a - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - jr nc, .can_attack - ld a, $01 - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - jr c, .cant_attack_or_ko -.can_attack - ld a, $01 - ld [wCurCardCanAttack], a - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .check_evolution_attacks - call CheckIfSelectedAttackIsUnusable - jr c, .check_evolution_attacks - ld a, $01 - ld [wCurCardCanKO], a - jr .check_evolution_attacks -.cant_attack_or_ko - xor a - ld [wCurCardCanAttack], a - ld [wCurCardCanKO], a - -; check evolution to see if it can use any of its attacks: -; if it can, raise AI score; -; if it can't, decrease AI score and if an energy card that is needed -; can be played from the hand, raise AI score. -.check_evolution_attacks - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - push af - ld a, [wTempAIPokemonCard] - ld [hl], a - xor a - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - jr nc, .evolution_can_attack - ld a, $01 - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - jr c, .evolution_cant_attack -.evolution_can_attack - ld a, 5 - call AddToAIScore - jr .check_evolution_ko -.evolution_cant_attack - ld a, [wCurCardCanAttack] - or a - jr z, .check_evolution_ko - ld a, 2 - call SubFromAIScore - ld a, [wAlreadyPlayedEnergy] - or a - jr nz, .check_evolution_ko - call LookForEnergyNeededInHand - jr nc, .check_evolution_ko - ld a, 7 - call AddToAIScore - -; if it's an active card: -; if evolution can't KO but the current card can, lower AI score; -; if evolution can KO as well, raise AI score. -.check_evolution_ko - ld a, [wCurCardCanAttack] - or a - jr z, .check_defending_can_ko_evolution - ld a, [wTempAI] - or a - jr nz, .check_defending_can_ko_evolution - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .evolution_cant_ko - call CheckIfSelectedAttackIsUnusable - jr c, .evolution_cant_ko - ld a, 5 - call AddToAIScore - jr .check_defending_can_ko_evolution -.evolution_cant_ko - ld a, [wCurCardCanKO] - or a - jr z, .check_defending_can_ko_evolution - ld a, 20 - call SubFromAIScore - -; if defending Pokémon can KO evolution, lower AI score -.check_defending_can_ko_evolution - ld a, [wTempAI] - or a - jr nz, .check_mr_mime - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfDefendingPokemonCanKnockOut - jr nc, .check_mr_mime - ld a, 5 - call SubFromAIScore - -; if evolution can't damage player's Mr Mime, lower AI score -.check_mr_mime - ld a, [wTempAI] - call CheckDamageToMrMime - jr c, .check_defending_can_ko - ld a, 20 - call SubFromAIScore - -; if defending Pokémon can KO current card, raise AI score -.check_defending_can_ko - ld a, [wTempAI] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - pop af - ld [hl], a - ld a, [wTempAI] - or a - jr nz, .check_2nd_stage_hand - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfDefendingPokemonCanKnockOut - jr nc, .check_status - ld a, 5 - call AddToAIScore - -; if current card has a status condition, raise AI score -.check_status - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - or a - jr z, .check_2nd_stage_hand - ld a, 4 - call AddToAIScore - -; if hand has 2nd stage card to evolve evolution card, raise AI score -.check_2nd_stage_hand - ld a, [wTempAIPokemonCard] - call CheckForEvolutionInList - jr nc, .check_2nd_stage_deck - ld a, 2 - call AddToAIScore - jr .check_damage - -; if deck has 2nd stage card to evolve evolution card, raise AI score -.check_2nd_stage_deck - ld a, [wTempAIPokemonCard] - call CheckForEvolutionInDeck - jr nc, .check_damage - ld a, 1 - call AddToAIScore - -; decrease AI score proportional to damage -; AI score -= floor(Damage / 40) -.check_damage - ld a, [wTempAI] - ld e, a - call GetCardDamageAndMaxHP - or a - jr z, .check_mysterious_fossil - srl a - srl a - call CalculateByteTensDigit - call SubFromAIScore - -; if is Mysterious Fossil or -; wLoadedCard1Unknown2 is set to $02, -; raise AI score -.check_mysterious_fossil - ld a, [wTempAI] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1ID] - cp MYSTERIOUS_FOSSIL - jr z, .mysterious_fossil - ld a, [wLoadedCard1Unknown2] - cp $02 - jr nz, .pikachu_deck - ld a, 2 - call AddToAIScore - jr .pikachu_deck - -.mysterious_fossil - ld a, 5 - call AddToAIScore - -; in Pikachu Deck, decrease AI score for evolving Pikachu -.pikachu_deck - ld a, [wOpponentDeckID] - cp PIKACHU_DECK_ID - jr nz, .check_score - ld a, [wLoadedCard1ID] - cp PIKACHU1 - jr z, .pikachu - cp PIKACHU2 - jr z, .pikachu - cp PIKACHU3 - jr z, .pikachu - cp PIKACHU4 - jr nz, .check_score -.pikachu - ld a, 3 - call SubFromAIScore - -; if AI score >= 133, go through with the evolution -.check_score - ld a, [wAIScore] - cp 133 - jr c, .done_bench_pokemon - ld a, [wTempAI] - ldh [hTempPlayAreaLocation_ffa1], a - ld a, [wTempAIPokemonCard] - ldh [hTemp_ffa0], a - ld a, OPPACTION_EVOLVE_PKMN - bank1call AIMakeDecision - pop bc - jr .done_hand_card - -.done_bench_pokemon - pop bc - inc b - dec c - jp nz, .next_bench_pokemon -.done_hand_card - pop hl - jp .next_hand_card -.done - or a - ret - -; determine AI score for evolving -; Charmeleon, Magikarp, Dragonair and Grimer -; in certain decks -AIDecideSpecialEvolutions: ; 16120 (5:6120) -; check if deck applies - ld a, [wOpponentDeckID] - cp LEGENDARY_DRAGONITE_DECK_ID - jr z, .legendary_dragonite - cp INVINCIBLE_RONALD_DECK_ID - jr z, .invincible_ronald - cp LEGENDARY_RONALD_DECK_ID - jr z, .legendary_ronald - ret - -.legendary_dragonite - ld a, [wLoadedCard2ID] - cp CHARMELEON - jr z, .charmeleon - cp MAGIKARP - jr z, .magikarp - cp DRAGONAIR - jr z, .dragonair - ret - -; check if number of energy cards attached to Charmeleon are at least 3 -; and if adding the energy cards in hand makes at least 6 energy cards -.charmeleon - ldh a, [hTempPlayAreaLocation_ff9d] - ld e, a - call CountNumberOfEnergyCardsAttached - cp 3 - jr c, .not_enough_energy - push af - farcall CountOppEnergyCardsInHand - pop bc - add b - cp 6 - jr c, .not_enough_energy - ld a, 3 - call AddToAIScore - ret -.not_enough_energy - ld a, 10 - call SubFromAIScore - ret - -; check if Magikarp is not the active card -; and has at least 2 energy cards attached -.magikarp - ldh a, [hTempPlayAreaLocation_ff9d] - or a ; active card - ret z - ld e, a - call CountNumberOfEnergyCardsAttached - cp 2 - ret c - ld a, 3 - call AddToAIScore - ret - -.invincible_ronald - ld a, [wLoadedCard2ID] - cp GRIMER - jr z, .grimer - ret - -; check if Grimer is not active card -.grimer - ldh a, [hTempPlayAreaLocation_ff9d] - or a ; active card - ret z - ld a, 10 - call AddToAIScore - ret - -.legendary_ronald - ld a, [wLoadedCard2ID] - cp DRAGONAIR - jr z, .dragonair - ret - -.dragonair - ldh a, [hTempPlayAreaLocation_ff9d] - or a ; active card - jr z, .is_active - -; if Dragonair is benched, check all Pokémon in Play Area -; and sum all the damage in HP of all cards -; if this result is >= 70, check if there's -; a Muk in any duelist's Play Area - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - ld b, a - ld c, 0 -.loop - dec b - ld e, b - push bc - call GetCardDamageAndMaxHP - pop bc - add c - ld c, a - ld a, b - or a - jr nz, .loop - ld a, 70 - cp c - jr c, .check_muk -.lower_score - ld a, 10 - call SubFromAIScore - ret - -; if there's no Muk, raise score -.check_muk - ld a, MUK - call CountPokemonIDInBothPlayAreas - jr c, .lower_score - ld a, 10 - call AddToAIScore - ret - -; if Dragonair is active, check its damage in HP -; if this result is >= 50, -; and if at least 3 energy cards attached, -; check if there's a Muk in any duelist's Play Area -.is_active - ld e, 0 - call GetCardDamageAndMaxHP - cp 50 - jr c, .lower_score - ld e, PLAY_AREA_ARENA - call GetPlayAreaCardAttachedEnergies - ld a, [wTotalAttachedEnergies] - cp 3 - jr c, .lower_score - jr .check_muk - -; determine AI score for the legendary cards -; Moltres, Zapdos and Articuno -AIDecidePlayLegendaryBirds: ; 161d5 (5:61d5) -; check if deck applies - ld a, [wOpponentDeckID] - cp LEGENDARY_ZAPDOS_DECK_ID - jr z, .begin - cp LEGENDARY_ARTICUNO_DECK_ID - jr z, .begin - cp LEGENDARY_RONALD_DECK_ID - jr z, .begin - ret - -; check if card applies -.begin - ld a, [wLoadedCard1ID] - cp ARTICUNO2 - jr z, .articuno - cp MOLTRES2 - jr z, .moltres - cp ZAPDOS3 - jr z, .zapdos - ret - -.articuno - ; exit if not enough Pokemon in Play Area - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 2 - ret c - - call CheckIfActiveCardCanKnockOut - jr c, .subtract - call CheckIfActivePokemonCanUseAnyNonResidualAttack - jr nc, .subtract - call AIDecideWhetherToRetreat - jr c, .subtract - - ; checks for player's active card status - ld a, DUELVARS_ARENA_CARD_STATUS - call GetNonTurnDuelistVariable - and CNF_SLP_PRZ - or a - jr nz, .subtract - - ; checks for player's Pokemon Power - call SwapTurn - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ld d, a - ld e, $00 - call CopyAttackDataAndDamage_FromDeckIndex - call SwapTurn - ld a, [wLoadedAttackCategory] - cp POKEMON_POWER - jr z, .check_muk_and_snorlax - - ; return if no space on the bench - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_BENCH_POKEMON - jr c, .check_muk_and_snorlax - ret - -.check_muk_and_snorlax - ; checks for Muk in both Play Areas - ld a, MUK - call CountPokemonIDInBothPlayAreas - jr c, .subtract - ; checks if player's active card is Snorlax - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - cp SNORLAX - jr z, .subtract - -; add - ld a, 70 - call AddToAIScore - ret -.subtract - ld a, 100 - call SubFromAIScore - ret - -.moltres - ; checks if there's enough cards in deck - ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK - call GetTurnDuelistVariable - cp 56 ; max number of cards not in deck to activate - jr nc, .subtract - ret - -.zapdos - ; checks for Muk in both Play Areas - ld a, MUK - call CountPokemonIDInBothPlayAreas - jr c, .subtract - ret +; determine whether AI plays +; basic cards from hand +AIDecidePlayPokemonCard: ; 15eae (5:5eae) + call CreateHandCardList + call SortTempHandByIDList + ld hl, wDuelTempList + ld de, wHandTempList + call CopyHandCardList + ld hl, wHandTempList + +.next_hand_card + ld a, [hli] + cp $ff + jp z, AIDecideEvolution + + ld [wTempAIPokemonCard], a + push hl + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr nc, .skip + ; skip non-pokemon cards + + ld a, [wLoadedCard1Stage] + or a + jr nz, .skip + ; skip non-basic pokemon + + ld a, 130 + ld [wAIScore], a + call AIDecidePlayLegendaryBirds + +; if Play Area has more than 4 Pokémon, decrease AI score +; else, increase AI score + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 4 + jr c, .has_4_or_fewer + ld a, 20 + call SubFromAIScore + jr .check_defending_can_ko +.has_4_or_fewer + ld a, 50 + call AddToAIScore + +; if defending Pokémon can KO active card, increase AI score +.check_defending_can_ko + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfDefendingPokemonCanKnockOut + jr nc, .check_energy_cards + ld a, 20 + call AddToAIScore + +; if energy cards are found in hand +; for this card's attacks, raise AI score +.check_energy_cards + ld a, [wTempAIPokemonCard] + call GetAttacksEnergyCostBits + call CheckEnergyFlagsNeededInList + jr nc, .check_evolution_hand + ld a, 20 + call AddToAIScore + +; if evolution card is found in hand +; for this card, raise AI score +.check_evolution_hand + ld a, [wTempAIPokemonCard] + call CheckForEvolutionInList + jr nc, .check_evolution_deck + ld a, 20 + call AddToAIScore + +; if evolution card is found in deck +; for this card, raise AI score +.check_evolution_deck + ld a, [wTempAIPokemonCard] + call CheckForEvolutionInDeck + jr nc, .check_score + ld a, 10 + call AddToAIScore + +; if AI score is >= 180, play card from hand +.check_score + ld a, [wAIScore] + cp 180 + jr c, .skip + ld a, [wTempAIPokemonCard] + ldh [hTemp_ffa0], a + call CheckIfCardCanBePlayed + jr c, .skip + ld a, OPPACTION_PLAY_BASIC_PKMN + bank1call AIMakeDecision + jr c, .done +.skip + pop hl + jp .next_hand_card +.done + pop hl + ret + +; determine whether AI evolves +; Pokémon in the Play Area +AIDecideEvolution: ; 15f4c (5:5f4c) + call CreateHandCardList + ld hl, wDuelTempList + ld de, wHandTempList + call CopyHandCardList + ld hl, wHandTempList + +.next_hand_card + ld a, [hli] + cp $ff + jp z, .done + ld [wTempAIPokemonCard], a + +; check if Prehistoric Power is active +; and if so, skip to next card in hand + push hl + call IsPrehistoricPowerActive + jp c, .done_hand_card + +; load evolution data to buffer1 +; skip if it's not a Pokémon card +; and if it's a basic stage card + ld a, [wTempAIPokemonCard] + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jp nc, .done_hand_card + ld a, [wLoadedCard1Stage] + or a + jp z, .done_hand_card + +; start looping Pokémon in Play Area +; to find a card to evolve + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld b, 0 +.next_bench_pokemon + push bc + ld e, b + ld a, [wTempAIPokemonCard] + ld d, a + call CheckIfCanEvolveInto + pop bc + push bc + jp c, .done_bench_pokemon + +; store this Play Area location in wTempAI +; and initialize the AI score + ld a, b + ld [wTempAI], a + ldh [hTempPlayAreaLocation_ff9d], a + ld a, $80 + ld [wAIScore], a + call AIDecideSpecialEvolutions + +; check if the card can use any attacks +; and if any of those attacks can KO + xor a + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr nc, .can_attack + ld a, $01 + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .cant_attack_or_ko +.can_attack + ld a, $01 + ld [wCurCardCanAttack], a + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .check_evolution_attacks + call CheckIfSelectedAttackIsUnusable + jr c, .check_evolution_attacks + ld a, $01 + ld [wCurCardCanKO], a + jr .check_evolution_attacks +.cant_attack_or_ko + xor a + ld [wCurCardCanAttack], a + ld [wCurCardCanKO], a + +; check evolution to see if it can use any of its attacks: +; if it can, raise AI score; +; if it can't, decrease AI score and if an energy card that is needed +; can be played from the hand, raise AI score. +.check_evolution_attacks + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + push af + ld a, [wTempAIPokemonCard] + ld [hl], a + xor a + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr nc, .evolution_can_attack + ld a, $01 + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .evolution_cant_attack +.evolution_can_attack + ld a, 5 + call AddToAIScore + jr .check_evolution_ko +.evolution_cant_attack + ld a, [wCurCardCanAttack] + or a + jr z, .check_evolution_ko + ld a, 2 + call SubFromAIScore + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .check_evolution_ko + call LookForEnergyNeededInHand + jr nc, .check_evolution_ko + ld a, 7 + call AddToAIScore + +; if it's an active card: +; if evolution can't KO but the current card can, lower AI score; +; if evolution can KO as well, raise AI score. +.check_evolution_ko + ld a, [wCurCardCanAttack] + or a + jr z, .check_defending_can_ko_evolution + ld a, [wTempAI] + or a + jr nz, .check_defending_can_ko_evolution + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .evolution_cant_ko + call CheckIfSelectedAttackIsUnusable + jr c, .evolution_cant_ko + ld a, 5 + call AddToAIScore + jr .check_defending_can_ko_evolution +.evolution_cant_ko + ld a, [wCurCardCanKO] + or a + jr z, .check_defending_can_ko_evolution + ld a, 20 + call SubFromAIScore + +; if defending Pokémon can KO evolution, lower AI score +.check_defending_can_ko_evolution + ld a, [wTempAI] + or a + jr nz, .check_mr_mime + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfDefendingPokemonCanKnockOut + jr nc, .check_mr_mime + ld a, 5 + call SubFromAIScore + +; if evolution can't damage player's Mr Mime, lower AI score +.check_mr_mime + ld a, [wTempAI] + call CheckDamageToMrMime + jr c, .check_defending_can_ko + ld a, 20 + call SubFromAIScore + +; if defending Pokémon can KO current card, raise AI score +.check_defending_can_ko + ld a, [wTempAI] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + ld a, [wTempAI] + or a + jr nz, .check_2nd_stage_hand + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfDefendingPokemonCanKnockOut + jr nc, .check_status + ld a, 5 + call AddToAIScore + +; if current card has a status condition, raise AI score +.check_status + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + or a + jr z, .check_2nd_stage_hand + ld a, 4 + call AddToAIScore + +; if hand has 2nd stage card to evolve evolution card, raise AI score +.check_2nd_stage_hand + ld a, [wTempAIPokemonCard] + call CheckForEvolutionInList + jr nc, .check_2nd_stage_deck + ld a, 2 + call AddToAIScore + jr .check_damage + +; if deck has 2nd stage card to evolve evolution card, raise AI score +.check_2nd_stage_deck + ld a, [wTempAIPokemonCard] + call CheckForEvolutionInDeck + jr nc, .check_damage + ld a, 1 + call AddToAIScore + +; decrease AI score proportional to damage +; AI score -= floor(Damage / 40) +.check_damage + ld a, [wTempAI] + ld e, a + call GetCardDamageAndMaxHP + or a + jr z, .check_mysterious_fossil + srl a + srl a + call CalculateByteTensDigit + call SubFromAIScore + +; if is Mysterious Fossil or +; wLoadedCard1Unknown2 is set to $02, +; raise AI score +.check_mysterious_fossil + ld a, [wTempAI] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1ID] + cp MYSTERIOUS_FOSSIL + jr z, .mysterious_fossil + ld a, [wLoadedCard1Unknown2] + cp $02 + jr nz, .pikachu_deck + ld a, 2 + call AddToAIScore + jr .pikachu_deck + +.mysterious_fossil + ld a, 5 + call AddToAIScore + +; in Pikachu Deck, decrease AI score for evolving Pikachu +.pikachu_deck + ld a, [wOpponentDeckID] + cp PIKACHU_DECK_ID + jr nz, .check_score + ld a, [wLoadedCard1ID] + cp PIKACHU1 + jr z, .pikachu + cp PIKACHU2 + jr z, .pikachu + cp PIKACHU3 + jr z, .pikachu + cp PIKACHU4 + jr nz, .check_score +.pikachu + ld a, 3 + call SubFromAIScore + +; if AI score >= 133, go through with the evolution +.check_score + ld a, [wAIScore] + cp 133 + jr c, .done_bench_pokemon + ld a, [wTempAI] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wTempAIPokemonCard] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EVOLVE_PKMN + bank1call AIMakeDecision + pop bc + jr .done_hand_card + +.done_bench_pokemon + pop bc + inc b + dec c + jp nz, .next_bench_pokemon +.done_hand_card + pop hl + jp .next_hand_card +.done + or a + ret + +; determine AI score for evolving +; Charmeleon, Magikarp, Dragonair and Grimer +; in certain decks +AIDecideSpecialEvolutions: ; 16120 (5:6120) +; check if deck applies + ld a, [wOpponentDeckID] + cp LEGENDARY_DRAGONITE_DECK_ID + jr z, .legendary_dragonite + cp INVINCIBLE_RONALD_DECK_ID + jr z, .invincible_ronald + cp LEGENDARY_RONALD_DECK_ID + jr z, .legendary_ronald + ret + +.legendary_dragonite + ld a, [wLoadedCard2ID] + cp CHARMELEON + jr z, .charmeleon + cp MAGIKARP + jr z, .magikarp + cp DRAGONAIR + jr z, .dragonair + ret + +; check if number of energy cards attached to Charmeleon are at least 3 +; and if adding the energy cards in hand makes at least 6 energy cards +.charmeleon + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call CountNumberOfEnergyCardsAttached + cp 3 + jr c, .not_enough_energy + push af + farcall CountOppEnergyCardsInHand + pop bc + add b + cp 6 + jr c, .not_enough_energy + ld a, 3 + call AddToAIScore + ret +.not_enough_energy + ld a, 10 + call SubFromAIScore + ret + +; check if Magikarp is not the active card +; and has at least 2 energy cards attached +.magikarp + ldh a, [hTempPlayAreaLocation_ff9d] + or a ; active card + ret z + ld e, a + call CountNumberOfEnergyCardsAttached + cp 2 + ret c + ld a, 3 + call AddToAIScore + ret + +.invincible_ronald + ld a, [wLoadedCard2ID] + cp GRIMER + jr z, .grimer + ret + +; check if Grimer is not active card +.grimer + ldh a, [hTempPlayAreaLocation_ff9d] + or a ; active card + ret z + ld a, 10 + call AddToAIScore + ret + +.legendary_ronald + ld a, [wLoadedCard2ID] + cp DRAGONAIR + jr z, .dragonair + ret + +.dragonair + ldh a, [hTempPlayAreaLocation_ff9d] + or a ; active card + jr z, .is_active + +; if Dragonair is benched, check all Pokémon in Play Area +; and sum all the damage in HP of all cards +; if this result is >= 70, check if there's +; a Muk in any duelist's Play Area + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, 0 +.loop + dec b + ld e, b + push bc + call GetCardDamageAndMaxHP + pop bc + add c + ld c, a + ld a, b + or a + jr nz, .loop + ld a, 70 + cp c + jr c, .check_muk +.lower_score + ld a, 10 + call SubFromAIScore + ret + +; if there's no Muk, raise score +.check_muk + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .lower_score + ld a, 10 + call AddToAIScore + ret + +; if Dragonair is active, check its damage in HP +; if this result is >= 50, +; and if at least 3 energy cards attached, +; check if there's a Muk in any duelist's Play Area +.is_active + ld e, 0 + call GetCardDamageAndMaxHP + cp 50 + jr c, .lower_score + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + cp 3 + jr c, .lower_score + jr .check_muk + +; determine AI score for the legendary cards +; Moltres, Zapdos and Articuno +AIDecidePlayLegendaryBirds: ; 161d5 (5:61d5) +; check if deck applies + ld a, [wOpponentDeckID] + cp LEGENDARY_ZAPDOS_DECK_ID + jr z, .begin + cp LEGENDARY_ARTICUNO_DECK_ID + jr z, .begin + cp LEGENDARY_RONALD_DECK_ID + jr z, .begin + ret + +; check if card applies +.begin + ld a, [wLoadedCard1ID] + cp ARTICUNO2 + jr z, .articuno + cp MOLTRES2 + jr z, .moltres + cp ZAPDOS3 + jr z, .zapdos + ret + +.articuno + ; exit if not enough Pokemon in Play Area + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + ret c + + call CheckIfActiveCardCanKnockOut + jr c, .subtract + call CheckIfActivePokemonCanUseAnyNonResidualAttack + jr nc, .subtract + call AIDecideWhetherToRetreat + jr c, .subtract + + ; checks for player's active card status + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + and CNF_SLP_PRZ + or a + jr nz, .subtract + + ; checks for player's Pokemon Power + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + ld e, $00 + call CopyAttackDataAndDamage_FromDeckIndex + call SwapTurn + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr z, .check_muk_and_snorlax + + ; return if no space on the bench + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_BENCH_POKEMON + jr c, .check_muk_and_snorlax + ret + +.check_muk_and_snorlax + ; checks for Muk in both Play Areas + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .subtract + ; checks if player's active card is Snorlax + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp SNORLAX + jr z, .subtract + +; add + ld a, 70 + call AddToAIScore + ret +.subtract + ld a, 100 + call SubFromAIScore + ret + +.moltres + ; checks if there's enough cards in deck + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp 56 ; max number of cards not in deck to activate + jr nc, .subtract + ret + +.zapdos + ; checks for Muk in both Play Areas + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .subtract + ret diff --git a/src/engine/ai/init.asm b/src/engine/ai/init.asm index cda2387..9f252d6 100644 --- a/src/engine/ai/init.asm +++ b/src/engine/ai/init.asm @@ -1,98 +1,98 @@ -InitAIDuelVars: ; 15636 (5:5636) - ld a, $10 - ld hl, wcda5 - call ClearMemory_Bank5 - ld a, 5 - ld [wAIPokedexCounter], a - ld a, $ff - ld [wcda5], a - ret - -; initializes some variables and sets value of wAIBarrierFlagCounter. -; if Player uses Barrier 3 times in a row, AI checks if Player's deck -; has only Mewtwo1 Pokemon cards (running a Mewtwo1 mill deck). -InitAITurnVars: ; 15649 (5:5649) -; increase Pokedex counter by 1 - ld a, [wAIPokedexCounter] - inc a - ld [wAIPokedexCounter], a - - xor a - ld [wPreviousAIFlags], a - ld [wcddb], a - ld [wcddc], a - ld [wAIRetreatedThisTurn], a - -; checks if the Player used an attack last turn -; and if it was the second attack of their card. - ld a, [wPlayerAttackingAttackIndex] - cp $ff - jr z, .check_flag - or a - jr z, .check_flag - ld a, [wPlayerAttackingCardIndex] - cp $ff - jr z, .check_flag - -; if the card is Mewtwo1, it means the Player -; used its second attack, Barrier. - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - cp MEWTWO1 - jr nz, .check_flag - ; Player used Barrier last turn - -; check if flag was already set, if so, -; reset wAIBarrierFlagCounter to $80. - ld a, [wAIBarrierFlagCounter] - bit AI_MEWTWO_MILL_F, a - jr nz, .set_flag - -; if not, increase it by 1 and check if it exceeds 2. - inc a - ld [wAIBarrierFlagCounter], a - cp 3 - jr c, .done - -; this means that the Player used Barrier -; at least 3 turns in a row. -; check if Player is running Mewtwo1-only deck, -; if so, set wAIBarrierFlagCounter flag. - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - cp MEWTWO1 - jr nz, .reset_1 - farcall CheckIfPlayerHasPokemonOtherThanMewtwo1 - jr nc, .set_flag -.reset_1 -; reset wAIBarrierFlagCounter - xor a - ld [wAIBarrierFlagCounter], a - jr .done - -.set_flag - ld a, AI_MEWTWO_MILL - ld [wAIBarrierFlagCounter], a - jr .done - -.check_flag -; increase counter by 1 if flag is set - ld a, [wAIBarrierFlagCounter] - bit AI_MEWTWO_MILL_F, a - jr z, .reset_2 - inc a - ld [wAIBarrierFlagCounter], a - jr .done - -.reset_2 -; reset wAIBarrierFlagCounter - xor a - ld [wAIBarrierFlagCounter], a -.done - ret +InitAIDuelVars: ; 15636 (5:5636) + ld a, $10 + ld hl, wcda5 + call ClearMemory_Bank5 + ld a, 5 + ld [wAIPokedexCounter], a + ld a, $ff + ld [wcda5], a + ret + +; initializes some variables and sets value of wAIBarrierFlagCounter. +; if Player uses Barrier 3 times in a row, AI checks if Player's deck +; has only Mewtwo1 Pokemon cards (running a Mewtwo1 mill deck). +InitAITurnVars: ; 15649 (5:5649) +; increase Pokedex counter by 1 + ld a, [wAIPokedexCounter] + inc a + ld [wAIPokedexCounter], a + + xor a + ld [wPreviousAIFlags], a + ld [wcddb], a + ld [wcddc], a + ld [wAIRetreatedThisTurn], a + +; checks if the Player used an attack last turn +; and if it was the second attack of their card. + ld a, [wPlayerAttackingAttackIndex] + cp $ff + jr z, .check_flag + or a + jr z, .check_flag + ld a, [wPlayerAttackingCardIndex] + cp $ff + jr z, .check_flag + +; if the card is Mewtwo1, it means the Player +; used its second attack, Barrier. + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MEWTWO1 + jr nz, .check_flag + ; Player used Barrier last turn + +; check if flag was already set, if so, +; reset wAIBarrierFlagCounter to $80. + ld a, [wAIBarrierFlagCounter] + bit AI_MEWTWO_MILL_F, a + jr nz, .set_flag + +; if not, increase it by 1 and check if it exceeds 2. + inc a + ld [wAIBarrierFlagCounter], a + cp 3 + jr c, .done + +; this means that the Player used Barrier +; at least 3 turns in a row. +; check if Player is running Mewtwo1-only deck, +; if so, set wAIBarrierFlagCounter flag. + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MEWTWO1 + jr nz, .reset_1 + farcall CheckIfPlayerHasPokemonOtherThanMewtwo1 + jr nc, .set_flag +.reset_1 +; reset wAIBarrierFlagCounter + xor a + ld [wAIBarrierFlagCounter], a + jr .done + +.set_flag + ld a, AI_MEWTWO_MILL + ld [wAIBarrierFlagCounter], a + jr .done + +.check_flag +; increase counter by 1 if flag is set + ld a, [wAIBarrierFlagCounter] + bit AI_MEWTWO_MILL_F, a + jr z, .reset_2 + inc a + ld [wAIBarrierFlagCounter], a + jr .done + +.reset_2 +; reset wAIBarrierFlagCounter + xor a + ld [wAIBarrierFlagCounter], a +.done + ret diff --git a/src/engine/ai/retreat.asm b/src/engine/ai/retreat.asm index 618e859..04fc415 100644 --- a/src/engine/ai/retreat.asm +++ b/src/engine/ai/retreat.asm @@ -1,1009 +1,1009 @@ -; determine AI score for retreating -; return carry if AI decides to retreat -AIDecideWhetherToRetreat: ; 158b2 (5:58b2) - ld a, [wGotHeadsFromConfusionCheckDuringRetreat] - or a - jp nz, .no_carry - xor a - ld [wAIPlayEnergyCardForRetreat], a - call LoadDefendingPokemonColorWRAndPrizeCards - ld a, $80 ; initial retreat score - ld [wAIScore], a - ld a, [wcdb4] - or a - jr z, .check_status - srl a - srl a - sla a - call AddToAIScore - -.check_status - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - or a - jr z, .check_ko_1 ; no status - and DOUBLE_POISONED - jr z, .check_cnf ; no poison - ld a, 2 - call AddToAIScore -.check_cnf - ld a, [hl] - and CNF_SLP_PRZ - cp CONFUSED - jr nz, .check_ko_1 - ld a, 1 - call AddToAIScore - -.check_ko_1 - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .active_cant_ko_1 - call CheckIfSelectedAttackIsUnusable - jp nc, .active_cant_use_atk - call LookForEnergyNeededForAttackInHand - jr nc, .active_cant_ko_1 - -.active_cant_use_atk - ld a, 5 - call SubFromAIScore - ld a, [wAIOpponentPrizeCount] - cp 2 - jr nc, .active_cant_ko_1 - ld a, 35 - call SubFromAIScore - -.active_cant_ko_1 - call CheckIfDefendingPokemonCanKnockOut - jr nc, .defending_cant_ko - ld a, 2 - call AddToAIScore - - call CheckIfNotABossDeckID - jr c, .check_resistance_1 - ld a, [wAIPlayerPrizeCount] - cp 2 - jr nc, .check_prize_count - ld a, $01 - ld [wAIPlayEnergyCardForRetreat], a - -.defending_cant_ko - call CheckIfNotABossDeckID - jr c, .check_resistance_1 - ld a, [wAIPlayerPrizeCount] - cp 2 - jr nc, .check_prize_count - ld a, 2 - call AddToAIScore - -.check_prize_count - ld a, [wAIOpponentPrizeCount] - cp 2 - jr nc, .check_resistance_1 - ld a, 2 - call SubFromAIScore - -.check_resistance_1 - call GetArenaCardColor - call TranslateColorToWR - ld b, a - ld a, [wAIPlayerResistance] - and b - jr z, .check_weakness_1 - ld a, 1 - call AddToAIScore - -; check bench for Pokémon that -; the defending card is not resistant to -; if one is found, skip SubFromAIScore - ld a, [wAIPlayerResistance] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop_resistance_1 - ld a, [hli] - cp $ff - jr z, .exit_loop_resistance_1 - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - call TranslateColorToWR - and b - jr nz, .loop_resistance_1 - jr .check_weakness_1 -.exit_loop_resistance_1 - ld a, 2 - call SubFromAIScore - -.check_weakness_1 - ld a, [wAIPlayerColor] - ld b, a - call GetArenaCardWeakness - and b - jr z, .check_resistance_2 - ld a, 2 - call AddToAIScore - -; check bench for Pokémon that -; is not weak to defending Pokémon -; if one is found, skip SubFromAIScore - ld a, [wAIPlayerColor] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop_weakness_1 - ld a, [hli] - cp $ff - jr z, .exit_loop_weakness_1 - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Weakness] - and b - jr nz, .loop_weakness_1 - jr .check_resistance_2 -.exit_loop_weakness_1 - ld a, 3 - call SubFromAIScore - -.check_resistance_2 - ld a, [wAIPlayerColor] - ld b, a - call GetArenaCardResistance - and b - jr z, .check_weakness_2 - ld a, 3 - call SubFromAIScore - -; check bench for Pokémon that -; is the defending Pokémon's weakness -; if none is found, skip AddToAIScore -.check_weakness_2 - ld a, [wAIPlayerWeakness] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld e, $00 -.loop_weakness_2 - inc e - ld a, [hli] - cp $ff - jr z, .check_resistance_3 - push de - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - call TranslateColorToWR - pop de - and b - jr z, .loop_weakness_2 - ld a, 2 - call AddToAIScore - - push de - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - pop de - cp PORYGON - jr nz, .check_weakness_3 - -; handle Porygon - ld a, e - call CheckIfCanDamageDefendingPokemon - jr nc, .check_weakness_3 - ld a, 10 - call AddToAIScore - jr .check_resistance_3 - -.check_weakness_3 - call GetArenaCardColor - call TranslateColorToWR - ld b, a - ld a, [wAIPlayerWeakness] - and b - jr z, .check_resistance_3 - ld a, 3 - call SubFromAIScore - -; check bench for Pokémon that -; is resistant to defending Pokémon -; if none is found, skip AddToAIScore -.check_resistance_3 - ld a, [wAIPlayerColor] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop_resistance_2 - ld a, [hli] - cp $ff - jr z, .check_ko_2 - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Resistance] - and b - jr z, .loop_resistance_2 - ld a, 1 - call AddToAIScore - -; check bench for Pokémon that -; can KO defending Pokémon -; if none is found, skip AddToAIScore -.check_ko_2 - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld c, 0 -.loop_ko_1 - inc c - ld a, [hli] - cp $ff - jr z, .check_defending_id - ld a, c - ldh [hTempPlayAreaLocation_ff9d], a - push hl - push bc - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .no_ko - call CheckIfSelectedAttackIsUnusable - jr nc, .success - call LookForEnergyNeededForAttackInHand - jr c, .success -.no_ko - pop bc - pop hl - jr .loop_ko_1 -.success - pop bc - pop hl - ld a, 2 - call AddToAIScore - -; a bench Pokémon was found that can KO -; if this is a boss deck and it's at last prize card -; if arena Pokémon cannot KO, add to AI score -; and set wAIPlayEnergyCardForRetreat to $01 - - ld a, [wAIOpponentPrizeCount] - cp 2 - jr nc, .check_defending_id - call CheckIfNotABossDeckID - jr c, .check_defending_id - - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .active_cant_ko_2 - call CheckIfSelectedAttackIsUnusable - jp nc, .check_defending_id -.active_cant_ko_2 - ld a, 40 - call AddToAIScore - ld a, $01 - ld [wAIPlayEnergyCardForRetreat], a - -.check_defending_id - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - cp MR_MIME - jr z, .mr_mime_or_hitmonlee - cp HITMONLEE ; ?? - jr nz, .check_retreat_cost - -; check bench if there's any Pokémon -; that can damage defending Pokémon -; this is done because of Mr. Mime's PKMN PWR -; but why Hitmonlee ($87) as well? -.mr_mime_or_hitmonlee - xor a - call CheckIfCanDamageDefendingPokemon - jr c, .check_retreat_cost - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld c, 0 -.loop_damage - inc c - ld a, [hli] - cp $ff - jr z, .check_retreat_cost - ld a, c - push hl - push bc - call CheckIfCanDamageDefendingPokemon - jr c, .can_damage - pop bc - pop hl - jr .loop_damage -.can_damage - pop bc - pop hl - ld a, 5 - call AddToAIScore - ld a, $01 - ld [wAIPlayEnergyCardForRetreat], a - -; subtract from wAIScore if retreat cost is larger than 1 -; then check if any cards have at least half HP, -; are final evolutions and can use second attack in the bench -; and adds to wAIScore if the active Pokémon doesn't meet -; these conditions -.check_retreat_cost - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call GetPlayAreaCardRetreatCost - cp 2 - jr c, .one_or_none - cp 3 - jr nc, .three_or_more - ; exactly two - ld a, 1 - call SubFromAIScore - jr .one_or_none - -.three_or_more - ld a, 2 - call SubFromAIScore - -.one_or_none - call CheckIfArenaCardIsAtHalfHPCanEvolveAndUseSecondAttack - jr c, .check_defending_can_ko - call CountNumberOfSetUpBenchPokemon - cp 2 - jr c, .check_defending_can_ko - call AddToAIScore - -; check bench for Pokémon that -; the defending Pokémon can't knock out -; if none is found, skip SubFromAIScore -.check_defending_can_ko - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld e, 0 -.loop_ko_2 - inc e - ld a, [hli] - cp $ff - jr z, .exit_loop_ko - push de - push hl - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2ID] - pop hl - pop de - cp MYSTERIOUS_FOSSIL - jr z, .loop_ko_2 - cp CLEFAIRY_DOLL - jr z, .loop_ko_2 - ld a, e - ldh [hTempPlayAreaLocation_ff9d], a - push de - push hl - call CheckIfDefendingPokemonCanKnockOut - pop hl - pop de - jr c, .loop_ko_2 - jr .check_active_id -.exit_loop_ko - ld a, 20 - call SubFromAIScore - -.check_active_id - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp MYSTERIOUS_FOSSIL - jr z, .mysterious_fossil_or_clefairy_doll - cp CLEFAIRY_DOLL - jr z, .mysterious_fossil_or_clefairy_doll - -; if wAIScore is at least 131, set carry - ld a, [wAIScore] - cp 131 - jr nc, .set_carry -.no_carry - or a - ret -.set_carry - scf - ret - -; set carry regardless if active card is -; either Mysterious Fossil or Clefairy Doll -; and there's a bench Pokémon who is not KO'd -; by defending Pokémon and can damage it -.mysterious_fossil_or_clefairy_doll - ld e, 0 -.loop_ko_3 - inc e - ld a, e - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - cp $ff - jr z, .no_carry - ld a, e - ldh [hTempPlayAreaLocation_ff9d], a - push de - call CheckIfDefendingPokemonCanKnockOut - pop de - jr c, .loop_ko_3 - ld a, e - push de - call CheckIfCanDamageDefendingPokemon - pop de - jr nc, .loop_ko_3 - jr .set_carry - -; if player's turn and loaded attack is not a Pokémon Power OR -; if opponent's turn and wcddb == 0 -; set wcdda's bit 7 flag -Func_15b54: ; 15b54 (5:5b54) - xor a - ld [wcdda], a - ld a, [wWhoseTurn] - cp OPPONENT_TURN - jr z, .opponent - -; player - ld a, [wLoadedAttackCategory] - cp POKEMON_POWER - ret z - jr .set_flag - -.opponent - ld a, [wcddb] - or a - ret nz - -.set_flag - ld a, %10000000 - ld [wcdda], a - ret - -; calculates AI score for bench Pokémon -; returns in a and [hTempPlayAreaLocation_ff9d] the -; Play Area location of best card to switch to. -; returns carry if no Bench Pokemon. -AIDecideBenchPokemonToSwitchTo: ; 15b72 (5:5b72) - xor a - ldh [hTempPlayAreaLocation_ff9d], a - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 2 - ret c - -; has at least 2 Pokémon in Play Area - call Func_15b54 - call LoadDefendingPokemonColorWRAndPrizeCards - ld a, 50 - ld [wAIScore], a - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - ld b, a - ld c, PLAY_AREA_ARENA - push bc - jp .store_score - -.next_bench - push bc - ld a, c - ldh [hTempPlayAreaLocation_ff9d], a - ld a, 50 - ld [wAIScore], a - -; check if card can KO defending Pokémon -; if it can, raise AI score -; if on last prize card, raise AI score again - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .check_can_use_atks - call CheckIfSelectedAttackIsUnusable - jr c, .check_can_use_atks - ld a, 10 - call AddToAIScore - ld a, [wcdda] - or %00000001 - ld [wcdda], a - call CountPrizes - cp 2 - jp nc, .check_defending_weak - ld a, 10 - call AddToAIScore - -; calculates damage of both attacks -; to raise AI score accordingly -.check_can_use_atks - xor a - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - call nc, .HandleAttackDamageScore - ld a, $01 - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - call nc, .HandleAttackDamageScore - jr .check_energy_card - -; adds to AI score depending on amount of damage -; it can inflict to the defending Pokémon -; AI score += floor(Damage / 10) + 1 -.HandleAttackDamageScore - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - call CalculateByteTensDigit - inc a - call AddToAIScore - ret - -; if an energy card that is needed is found in hand -; calculate damage of the move and raise AI score -; AI score += floor(Damage / 20) -.check_energy_card - call LookForEnergyNeededInHand - jr nc, .check_attached_energy - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - call CalculateByteTensDigit - srl a - call AddToAIScore - -; if no energies attached to card, lower AI score -.check_attached_energy - ldh a, [hTempPlayAreaLocation_ff9d] - ld e, a - call GetPlayAreaCardAttachedEnergies - ld a, [wTotalAttachedEnergies] - or a - jr nz, .check_mr_mime - ld a, 1 - call SubFromAIScore - -; if can damage Mr Mime, raise AI score -.check_mr_mime - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call LoadCardDataToBuffer2_FromDeckIndex - call SwapTurn - cp MR_MIME - jr nz, .check_defending_weak - xor a - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - or a - jr nz, .can_damage - ld a, $01 - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - or a - jr z, .check_defending_weak -.can_damage - ld a, 5 - call AddToAIScore - -; if defending card is weak to this card, raise AI score -.check_defending_weak - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - call TranslateColorToWR - ld c, a - ld hl, wAIPlayerWeakness - and [hl] - jr z, .check_defending_resist - ld a, 3 - call AddToAIScore - -; if defending card is resistant to this card, lower AI score -.check_defending_resist - ld a, c - ld hl, wAIPlayerResistance - and [hl] - jr z, .check_resistance - ld a, 2 - call SubFromAIScore - -; if this card is resistant to defending Pokémon, raise AI score -.check_resistance - ld a, [wAIPlayerColor] - ld hl, wLoadedCard1Resistance - and [hl] - jr z, .check_weakness - ld a, 2 - call AddToAIScore - -; if this card is weak to defending Pokémon, lower AI score -.check_weakness - ld a, [wAIPlayerColor] - ld hl, wLoadedCard1Weakness - and [hl] - jr z, .check_retreat_cost - ld a, 3 - call SubFromAIScore - -; if this card's retreat cost < 2, raise AI score -; if this card's retreat cost > 2, lower AI score -.check_retreat_cost - call GetPlayAreaCardRetreatCost - cp 2 - jr c, .one_or_none - jr z, .check_player_prize_count - ld a, 1 - call SubFromAIScore - jr .check_player_prize_count -.one_or_none - ld a, 1 - call AddToAIScore - -; if wcdda != $81 -; if defending Pokémon can KO this card -; if player is not at last prize card, lower 3 from AI score -; if player is at last prize card, lower 10 from AI score -.check_player_prize_count - ld a, [wcdda] - cp %10000000 | %00000001 - jr z, .check_hp - call CheckIfDefendingPokemonCanKnockOut - jr nc, .check_hp - ld e, 3 - ld a, [wAIPlayerPrizeCount] - cp 1 - jr nz, .lower_score_1 - ld e, 10 -.lower_score_1 - ld a, e - call SubFromAIScore - -; if this card's HP is 0, make AI score 0 -.check_hp - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - or a - jr nz, .add_hp_score - ld [wAIScore], a - jr .store_score - -; AI score += floor(HP/40) -.add_hp_score - ld b, a - ld a, 4 - call CalculateBDividedByA_Bank5 - call CalculateByteTensDigit - call AddToAIScore - -; raise AI score if -; - is a Mr Mime OR -; - is a Mew1 and defending card is not basic stage - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer1_FromDeckIndex - cp MR_MIME - jr z, .raise_score - cp MEW1 - jr nz, .asm_15cf0 - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2Stage] - or a - jr z, .asm_15cf0 -.raise_score - ld a, 5 - call AddToAIScore - -; if wLoadedCard1Unknown2 == $01, lower AI score -.asm_15cf0 - ld a, [wLoadedCard1Unknown2] - cp $01 - jr nz, .mysterious_fossil_or_clefairy_doll - ld a, 2 - call SubFromAIScore - -; if card is Mysterious Fossil or Clefairy Doll, -; lower AI score -.mysterious_fossil_or_clefairy_doll - ld a, [wLoadedCard1ID] - cp MYSTERIOUS_FOSSIL - jr z, .lower_score_2 - cp CLEFAIRY_DOLL - jr nz, .ai_score_bonus -.lower_score_2 - ld a, 10 - call SubFromAIScore - -.ai_score_bonus - ld b, a - ld a, [wAICardListRetreatBonus + 1] - or a - jr z, .store_score - ld h, a - ld a, [wAICardListRetreatBonus] - ld l, a - -.loop_ids - ld a, [hli] - or a - jr z, .store_score ; list is over - cp b - jr nz, .next_id - ld a, [hl] - cp $80 - jr c, .subtract_score - sub $80 - call AddToAIScore - jr .next_id -.subtract_score - ld c, a - ld a, $80 - sub c - call SubFromAIScore -.next_id - inc hl - jr .loop_ids - -.store_score - ldh a, [hTempPlayAreaLocation_ff9d] - ld c, a - ld b, $00 - ld hl, wPlayAreaAIScore - add hl, bc - ld a, [wAIScore] - ld [hl], a - pop bc - inc c - dec b - jp nz, .next_bench - -; done - xor a - ld [wcdb4], a - jp FindHighestBenchScore - -; handles AI action of retreating Arena Pokémon -; and chooses which energy cards to discard. -; if card can't discard, return carry. -; in case it's Clefairy Doll or Mysterious Fossil, -; handle its effect to discard itself instead of retreating. -; input: -; - a = Play Area location (PLAY_AREA_*) of card to retreat to. -AITryToRetreat: ; 15d4f (5:5d4f) - push af - ld a, [wAIPlayEnergyCardForRetreat] - or a - jr z, .check_id - -; AI is allowed to play an energy card -; from the hand in order to provide -; the necessary energy for retreat cost - -; check status - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and CNF_SLP_PRZ - cp ASLEEP - jp z, .check_id - cp PARALYZED - jp z, .check_id - -; if an energy card hasn't been played yet, -; checks if the Pokémon needs just one more energy to retreat -; if it does, check if there are any energy cards in hand -; and if there are, play that energy card - ld a, [wAlreadyPlayedEnergy] - or a - jr nz, .check_id - ld e, PLAY_AREA_ARENA - call CountNumberOfEnergyCardsAttached - push af - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call GetPlayAreaCardRetreatCost - pop bc - cp b - jr c, .check_id - jr z, .check_id - ; energy attached < retreat cost - sub b - cp 1 - jr nz, .check_id - call CreateEnergyCardListFromHand - jr c, .check_id - ld a, [wDuelTempList] - ldh [hTemp_ffa0], a - xor a - ldh [hTempPlayAreaLocation_ffa1], a - ld a, OPPACTION_PLAY_ENERGY - bank1call AIMakeDecision - -.check_id - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp MYSTERIOUS_FOSSIL - jp z, .mysterious_fossil_or_clefairy_doll - cp CLEFAIRY_DOLL - jp z, .mysterious_fossil_or_clefairy_doll - -; if card is Asleep or Paralyzed, set carry and exit -; else, load the status in hTemp_ffa0 - pop af - ldh [hTempPlayAreaLocation_ffa1], a - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - ld b, a - and CNF_SLP_PRZ - cp ASLEEP - jp z, .set_carry - cp PARALYZED - jp z, .set_carry - ld a, b - ldh [hTemp_ffa0], a - ld a, $ff - ldh [hTempRetreatCostCards], a - -; check energy required to retreat -; if the cost is 0, retreat right away - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call GetPlayAreaCardRetreatCost - ld [wTempCardRetreatCost], a - or a - jp z, .retreat - -; if cost > 0 and number of energy cards attached == cost -; discard them all - xor a - call CreateArenaOrBenchEnergyCardList - ld e, PLAY_AREA_ARENA - call GetPlayAreaCardAttachedEnergies - ld a, [wTotalAttachedEnergies] - ld c, a - ld a, [wTempCardRetreatCost] - cp c - jr nz, .choose_energy_discard - - ld hl, hTempRetreatCostCards - ld de, wDuelTempList -.loop_1 - ld a, [de] - inc de - ld [hli], a - cp $ff - jr nz, .loop_1 - jp .retreat - -; if cost > 0 and number of energy cards attached > cost -; choose energy cards to discard according to color -.choose_energy_discard - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - ld [wTempCardID], a - call LoadCardDataToBuffer1_FromCardID - ld a, [wLoadedCard1Type] - or TYPE_ENERGY - ld [wTempCardType], a - ld a, [wTempCardRetreatCost] - ld c, a - -; first, look for and discard double colorless energy -; if retreat cost is >= 2 - ld hl, wDuelTempList - ld de, hTempRetreatCostCards -.loop_2 - ld a, c - cp 2 - jr c, .energy_not_same_color - ld a, [hli] - cp $ff - jr z, .energy_not_same_color - ld [de], a - push de - call GetCardIDFromDeckIndex - ld a, e - pop de - cp DOUBLE_COLORLESS_ENERGY - jr nz, .loop_2 - ld a, [de] - call RemoveCardFromDuelTempList - dec hl - inc de - dec c - dec c - jr nz, .loop_2 - jr .end_retreat_list - -; second, shuffle attached cards and discard energy cards -; that are not of the same type as the Pokémon -; the exception for this are cards that are needed for -; some attacks but are not of the same color as the Pokémon -; (i.e. Psyduck's Headache attack) -; and energy cards attached to Eevee corresponding to a -; color of any of its evolutions (water, fire, lightning) -.energy_not_same_color - ld hl, wDuelTempList - call CountCardsInDuelTempList - call ShuffleCards -.loop_3 - ld a, [hli] - cp $ff - jr z, .any_energy - ld [de], a - call CheckIfEnergyIsUseful - jr c, .loop_3 - ld a, [de] - call RemoveCardFromDuelTempList - dec hl - inc de - dec c - jr nz, .loop_3 - jr .end_retreat_list - -; third, discard any card until -; cost requirement is met -.any_energy - ld hl, wDuelTempList -.loop_4 - ld a, [hli] - cp $ff - jr z, .set_carry - ld [de], a - inc de - push de - call GetCardIDFromDeckIndex - ld a, e - pop de - cp DOUBLE_COLORLESS_ENERGY - jr nz, .not_double_colorless - dec c - jr z, .end_retreat_list -.not_double_colorless - dec c - jr nz, .loop_4 - -.end_retreat_list - ld a, $ff - ld [de], a - -.retreat - ld a, OPPACTION_ATTEMPT_RETREAT - bank1call AIMakeDecision - or a - ret -.set_carry - scf - ret - -; handle Mysterious Fossil and Clefairy Doll -; if there are bench Pokémon, use effect to discard card -; this is equivalent to using its Pokémon Power -.mysterious_fossil_or_clefairy_doll - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 2 - jr nc, .has_bench - ; doesn't have any bench - pop af - jr .set_carry - -.has_bench - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ldh [hTempCardIndex_ff9f], a - xor a - ldh [hTemp_ffa0], a - ld a, OPPACTION_USE_PKMN_POWER - bank1call AIMakeDecision - pop af - ldh [hAIPkmnPowerEffectParam], a - ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT - bank1call AIMakeDecision - ld a, OPPACTION_DUEL_MAIN_SCENE - bank1call AIMakeDecision - or a - ret +; determine AI score for retreating +; return carry if AI decides to retreat +AIDecideWhetherToRetreat: ; 158b2 (5:58b2) + ld a, [wGotHeadsFromConfusionCheckDuringRetreat] + or a + jp nz, .no_carry + xor a + ld [wAIPlayEnergyCardForRetreat], a + call LoadDefendingPokemonColorWRAndPrizeCards + ld a, $80 ; initial retreat score + ld [wAIScore], a + ld a, [wcdb4] + or a + jr z, .check_status + srl a + srl a + sla a + call AddToAIScore + +.check_status + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + or a + jr z, .check_ko_1 ; no status + and DOUBLE_POISONED + jr z, .check_cnf ; no poison + ld a, 2 + call AddToAIScore +.check_cnf + ld a, [hl] + and CNF_SLP_PRZ + cp CONFUSED + jr nz, .check_ko_1 + ld a, 1 + call AddToAIScore + +.check_ko_1 + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .active_cant_ko_1 + call CheckIfSelectedAttackIsUnusable + jp nc, .active_cant_use_atk + call LookForEnergyNeededForAttackInHand + jr nc, .active_cant_ko_1 + +.active_cant_use_atk + ld a, 5 + call SubFromAIScore + ld a, [wAIOpponentPrizeCount] + cp 2 + jr nc, .active_cant_ko_1 + ld a, 35 + call SubFromAIScore + +.active_cant_ko_1 + call CheckIfDefendingPokemonCanKnockOut + jr nc, .defending_cant_ko + ld a, 2 + call AddToAIScore + + call CheckIfNotABossDeckID + jr c, .check_resistance_1 + ld a, [wAIPlayerPrizeCount] + cp 2 + jr nc, .check_prize_count + ld a, $01 + ld [wAIPlayEnergyCardForRetreat], a + +.defending_cant_ko + call CheckIfNotABossDeckID + jr c, .check_resistance_1 + ld a, [wAIPlayerPrizeCount] + cp 2 + jr nc, .check_prize_count + ld a, 2 + call AddToAIScore + +.check_prize_count + ld a, [wAIOpponentPrizeCount] + cp 2 + jr nc, .check_resistance_1 + ld a, 2 + call SubFromAIScore + +.check_resistance_1 + call GetArenaCardColor + call TranslateColorToWR + ld b, a + ld a, [wAIPlayerResistance] + and b + jr z, .check_weakness_1 + ld a, 1 + call AddToAIScore + +; check bench for Pokémon that +; the defending card is not resistant to +; if one is found, skip SubFromAIScore + ld a, [wAIPlayerResistance] + ld b, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable +.loop_resistance_1 + ld a, [hli] + cp $ff + jr z, .exit_loop_resistance_1 + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + call TranslateColorToWR + and b + jr nz, .loop_resistance_1 + jr .check_weakness_1 +.exit_loop_resistance_1 + ld a, 2 + call SubFromAIScore + +.check_weakness_1 + ld a, [wAIPlayerColor] + ld b, a + call GetArenaCardWeakness + and b + jr z, .check_resistance_2 + ld a, 2 + call AddToAIScore + +; check bench for Pokémon that +; is not weak to defending Pokémon +; if one is found, skip SubFromAIScore + ld a, [wAIPlayerColor] + ld b, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable +.loop_weakness_1 + ld a, [hli] + cp $ff + jr z, .exit_loop_weakness_1 + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Weakness] + and b + jr nz, .loop_weakness_1 + jr .check_resistance_2 +.exit_loop_weakness_1 + ld a, 3 + call SubFromAIScore + +.check_resistance_2 + ld a, [wAIPlayerColor] + ld b, a + call GetArenaCardResistance + and b + jr z, .check_weakness_2 + ld a, 3 + call SubFromAIScore + +; check bench for Pokémon that +; is the defending Pokémon's weakness +; if none is found, skip AddToAIScore +.check_weakness_2 + ld a, [wAIPlayerWeakness] + ld b, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld e, $00 +.loop_weakness_2 + inc e + ld a, [hli] + cp $ff + jr z, .check_resistance_3 + push de + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + call TranslateColorToWR + pop de + and b + jr z, .loop_weakness_2 + ld a, 2 + call AddToAIScore + + push de + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + pop de + cp PORYGON + jr nz, .check_weakness_3 + +; handle Porygon + ld a, e + call CheckIfCanDamageDefendingPokemon + jr nc, .check_weakness_3 + ld a, 10 + call AddToAIScore + jr .check_resistance_3 + +.check_weakness_3 + call GetArenaCardColor + call TranslateColorToWR + ld b, a + ld a, [wAIPlayerWeakness] + and b + jr z, .check_resistance_3 + ld a, 3 + call SubFromAIScore + +; check bench for Pokémon that +; is resistant to defending Pokémon +; if none is found, skip AddToAIScore +.check_resistance_3 + ld a, [wAIPlayerColor] + ld b, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable +.loop_resistance_2 + ld a, [hli] + cp $ff + jr z, .check_ko_2 + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Resistance] + and b + jr z, .loop_resistance_2 + ld a, 1 + call AddToAIScore + +; check bench for Pokémon that +; can KO defending Pokémon +; if none is found, skip AddToAIScore +.check_ko_2 + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld c, 0 +.loop_ko_1 + inc c + ld a, [hli] + cp $ff + jr z, .check_defending_id + ld a, c + ldh [hTempPlayAreaLocation_ff9d], a + push hl + push bc + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .no_ko + call CheckIfSelectedAttackIsUnusable + jr nc, .success + call LookForEnergyNeededForAttackInHand + jr c, .success +.no_ko + pop bc + pop hl + jr .loop_ko_1 +.success + pop bc + pop hl + ld a, 2 + call AddToAIScore + +; a bench Pokémon was found that can KO +; if this is a boss deck and it's at last prize card +; if arena Pokémon cannot KO, add to AI score +; and set wAIPlayEnergyCardForRetreat to $01 + + ld a, [wAIOpponentPrizeCount] + cp 2 + jr nc, .check_defending_id + call CheckIfNotABossDeckID + jr c, .check_defending_id + + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .active_cant_ko_2 + call CheckIfSelectedAttackIsUnusable + jp nc, .check_defending_id +.active_cant_ko_2 + ld a, 40 + call AddToAIScore + ld a, $01 + ld [wAIPlayEnergyCardForRetreat], a + +.check_defending_id + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MR_MIME + jr z, .mr_mime_or_hitmonlee + cp HITMONLEE ; ?? + jr nz, .check_retreat_cost + +; check bench if there's any Pokémon +; that can damage defending Pokémon +; this is done because of Mr. Mime's PKMN PWR +; but why Hitmonlee ($87) as well? +.mr_mime_or_hitmonlee + xor a + call CheckIfCanDamageDefendingPokemon + jr c, .check_retreat_cost + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld c, 0 +.loop_damage + inc c + ld a, [hli] + cp $ff + jr z, .check_retreat_cost + ld a, c + push hl + push bc + call CheckIfCanDamageDefendingPokemon + jr c, .can_damage + pop bc + pop hl + jr .loop_damage +.can_damage + pop bc + pop hl + ld a, 5 + call AddToAIScore + ld a, $01 + ld [wAIPlayEnergyCardForRetreat], a + +; subtract from wAIScore if retreat cost is larger than 1 +; then check if any cards have at least half HP, +; are final evolutions and can use second attack in the bench +; and adds to wAIScore if the active Pokémon doesn't meet +; these conditions +.check_retreat_cost + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + cp 2 + jr c, .one_or_none + cp 3 + jr nc, .three_or_more + ; exactly two + ld a, 1 + call SubFromAIScore + jr .one_or_none + +.three_or_more + ld a, 2 + call SubFromAIScore + +.one_or_none + call CheckIfArenaCardIsAtHalfHPCanEvolveAndUseSecondAttack + jr c, .check_defending_can_ko + call CountNumberOfSetUpBenchPokemon + cp 2 + jr c, .check_defending_can_ko + call AddToAIScore + +; check bench for Pokémon that +; the defending Pokémon can't knock out +; if none is found, skip SubFromAIScore +.check_defending_can_ko + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld e, 0 +.loop_ko_2 + inc e + ld a, [hli] + cp $ff + jr z, .exit_loop_ko + push de + push hl + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2ID] + pop hl + pop de + cp MYSTERIOUS_FOSSIL + jr z, .loop_ko_2 + cp CLEFAIRY_DOLL + jr z, .loop_ko_2 + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + push de + push hl + call CheckIfDefendingPokemonCanKnockOut + pop hl + pop de + jr c, .loop_ko_2 + jr .check_active_id +.exit_loop_ko + ld a, 20 + call SubFromAIScore + +.check_active_id + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp MYSTERIOUS_FOSSIL + jr z, .mysterious_fossil_or_clefairy_doll + cp CLEFAIRY_DOLL + jr z, .mysterious_fossil_or_clefairy_doll + +; if wAIScore is at least 131, set carry + ld a, [wAIScore] + cp 131 + jr nc, .set_carry +.no_carry + or a + ret +.set_carry + scf + ret + +; set carry regardless if active card is +; either Mysterious Fossil or Clefairy Doll +; and there's a bench Pokémon who is not KO'd +; by defending Pokémon and can damage it +.mysterious_fossil_or_clefairy_doll + ld e, 0 +.loop_ko_3 + inc e + ld a, e + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + cp $ff + jr z, .no_carry + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + push de + call CheckIfDefendingPokemonCanKnockOut + pop de + jr c, .loop_ko_3 + ld a, e + push de + call CheckIfCanDamageDefendingPokemon + pop de + jr nc, .loop_ko_3 + jr .set_carry + +; if player's turn and loaded attack is not a Pokémon Power OR +; if opponent's turn and wcddb == 0 +; set wcdda's bit 7 flag +Func_15b54: ; 15b54 (5:5b54) + xor a + ld [wcdda], a + ld a, [wWhoseTurn] + cp OPPONENT_TURN + jr z, .opponent + +; player + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + ret z + jr .set_flag + +.opponent + ld a, [wcddb] + or a + ret nz + +.set_flag + ld a, %10000000 + ld [wcdda], a + ret + +; calculates AI score for bench Pokémon +; returns in a and [hTempPlayAreaLocation_ff9d] the +; Play Area location of best card to switch to. +; returns carry if no Bench Pokemon. +AIDecideBenchPokemonToSwitchTo: ; 15b72 (5:5b72) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + ret c + +; has at least 2 Pokémon in Play Area + call Func_15b54 + call LoadDefendingPokemonColorWRAndPrizeCards + ld a, 50 + ld [wAIScore], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, PLAY_AREA_ARENA + push bc + jp .store_score + +.next_bench + push bc + ld a, c + ldh [hTempPlayAreaLocation_ff9d], a + ld a, 50 + ld [wAIScore], a + +; check if card can KO defending Pokémon +; if it can, raise AI score +; if on last prize card, raise AI score again + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .check_can_use_atks + call CheckIfSelectedAttackIsUnusable + jr c, .check_can_use_atks + ld a, 10 + call AddToAIScore + ld a, [wcdda] + or %00000001 + ld [wcdda], a + call CountPrizes + cp 2 + jp nc, .check_defending_weak + ld a, 10 + call AddToAIScore + +; calculates damage of both attacks +; to raise AI score accordingly +.check_can_use_atks + xor a + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + call nc, .HandleAttackDamageScore + ld a, $01 + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + call nc, .HandleAttackDamageScore + jr .check_energy_card + +; adds to AI score depending on amount of damage +; it can inflict to the defending Pokémon +; AI score += floor(Damage / 10) + 1 +.HandleAttackDamageScore + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + call CalculateByteTensDigit + inc a + call AddToAIScore + ret + +; if an energy card that is needed is found in hand +; calculate damage of the move and raise AI score +; AI score += floor(Damage / 20) +.check_energy_card + call LookForEnergyNeededInHand + jr nc, .check_attached_energy + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + call CalculateByteTensDigit + srl a + call AddToAIScore + +; if no energies attached to card, lower AI score +.check_attached_energy + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr nz, .check_mr_mime + ld a, 1 + call SubFromAIScore + +; if can damage Mr Mime, raise AI score +.check_mr_mime + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call LoadCardDataToBuffer2_FromDeckIndex + call SwapTurn + cp MR_MIME + jr nz, .check_defending_weak + xor a + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr nz, .can_damage + ld a, $01 + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr z, .check_defending_weak +.can_damage + ld a, 5 + call AddToAIScore + +; if defending card is weak to this card, raise AI score +.check_defending_weak + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + call TranslateColorToWR + ld c, a + ld hl, wAIPlayerWeakness + and [hl] + jr z, .check_defending_resist + ld a, 3 + call AddToAIScore + +; if defending card is resistant to this card, lower AI score +.check_defending_resist + ld a, c + ld hl, wAIPlayerResistance + and [hl] + jr z, .check_resistance + ld a, 2 + call SubFromAIScore + +; if this card is resistant to defending Pokémon, raise AI score +.check_resistance + ld a, [wAIPlayerColor] + ld hl, wLoadedCard1Resistance + and [hl] + jr z, .check_weakness + ld a, 2 + call AddToAIScore + +; if this card is weak to defending Pokémon, lower AI score +.check_weakness + ld a, [wAIPlayerColor] + ld hl, wLoadedCard1Weakness + and [hl] + jr z, .check_retreat_cost + ld a, 3 + call SubFromAIScore + +; if this card's retreat cost < 2, raise AI score +; if this card's retreat cost > 2, lower AI score +.check_retreat_cost + call GetPlayAreaCardRetreatCost + cp 2 + jr c, .one_or_none + jr z, .check_player_prize_count + ld a, 1 + call SubFromAIScore + jr .check_player_prize_count +.one_or_none + ld a, 1 + call AddToAIScore + +; if wcdda != $81 +; if defending Pokémon can KO this card +; if player is not at last prize card, lower 3 from AI score +; if player is at last prize card, lower 10 from AI score +.check_player_prize_count + ld a, [wcdda] + cp %10000000 | %00000001 + jr z, .check_hp + call CheckIfDefendingPokemonCanKnockOut + jr nc, .check_hp + ld e, 3 + ld a, [wAIPlayerPrizeCount] + cp 1 + jr nz, .lower_score_1 + ld e, 10 +.lower_score_1 + ld a, e + call SubFromAIScore + +; if this card's HP is 0, make AI score 0 +.check_hp + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + or a + jr nz, .add_hp_score + ld [wAIScore], a + jr .store_score + +; AI score += floor(HP/40) +.add_hp_score + ld b, a + ld a, 4 + call CalculateBDividedByA_Bank5 + call CalculateByteTensDigit + call AddToAIScore + +; raise AI score if +; - is a Mr Mime OR +; - is a Mew1 and defending card is not basic stage + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + cp MR_MIME + jr z, .raise_score + cp MEW1 + jr nz, .asm_15cf0 + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Stage] + or a + jr z, .asm_15cf0 +.raise_score + ld a, 5 + call AddToAIScore + +; if wLoadedCard1Unknown2 == $01, lower AI score +.asm_15cf0 + ld a, [wLoadedCard1Unknown2] + cp $01 + jr nz, .mysterious_fossil_or_clefairy_doll + ld a, 2 + call SubFromAIScore + +; if card is Mysterious Fossil or Clefairy Doll, +; lower AI score +.mysterious_fossil_or_clefairy_doll + ld a, [wLoadedCard1ID] + cp MYSTERIOUS_FOSSIL + jr z, .lower_score_2 + cp CLEFAIRY_DOLL + jr nz, .ai_score_bonus +.lower_score_2 + ld a, 10 + call SubFromAIScore + +.ai_score_bonus + ld b, a + ld a, [wAICardListRetreatBonus + 1] + or a + jr z, .store_score + ld h, a + ld a, [wAICardListRetreatBonus] + ld l, a + +.loop_ids + ld a, [hli] + or a + jr z, .store_score ; list is over + cp b + jr nz, .next_id + ld a, [hl] + cp $80 + jr c, .subtract_score + sub $80 + call AddToAIScore + jr .next_id +.subtract_score + ld c, a + ld a, $80 + sub c + call SubFromAIScore +.next_id + inc hl + jr .loop_ids + +.store_score + ldh a, [hTempPlayAreaLocation_ff9d] + ld c, a + ld b, $00 + ld hl, wPlayAreaAIScore + add hl, bc + ld a, [wAIScore] + ld [hl], a + pop bc + inc c + dec b + jp nz, .next_bench + +; done + xor a + ld [wcdb4], a + jp FindHighestBenchScore + +; handles AI action of retreating Arena Pokémon +; and chooses which energy cards to discard. +; if card can't discard, return carry. +; in case it's Clefairy Doll or Mysterious Fossil, +; handle its effect to discard itself instead of retreating. +; input: +; - a = Play Area location (PLAY_AREA_*) of card to retreat to. +AITryToRetreat: ; 15d4f (5:5d4f) + push af + ld a, [wAIPlayEnergyCardForRetreat] + or a + jr z, .check_id + +; AI is allowed to play an energy card +; from the hand in order to provide +; the necessary energy for retreat cost + +; check status + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + cp ASLEEP + jp z, .check_id + cp PARALYZED + jp z, .check_id + +; if an energy card hasn't been played yet, +; checks if the Pokémon needs just one more energy to retreat +; if it does, check if there are any energy cards in hand +; and if there are, play that energy card + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .check_id + ld e, PLAY_AREA_ARENA + call CountNumberOfEnergyCardsAttached + push af + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + pop bc + cp b + jr c, .check_id + jr z, .check_id + ; energy attached < retreat cost + sub b + cp 1 + jr nz, .check_id + call CreateEnergyCardListFromHand + jr c, .check_id + ld a, [wDuelTempList] + ldh [hTemp_ffa0], a + xor a + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_PLAY_ENERGY + bank1call AIMakeDecision + +.check_id + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp MYSTERIOUS_FOSSIL + jp z, .mysterious_fossil_or_clefairy_doll + cp CLEFAIRY_DOLL + jp z, .mysterious_fossil_or_clefairy_doll + +; if card is Asleep or Paralyzed, set carry and exit +; else, load the status in hTemp_ffa0 + pop af + ldh [hTempPlayAreaLocation_ffa1], a + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + ld b, a + and CNF_SLP_PRZ + cp ASLEEP + jp z, .set_carry + cp PARALYZED + jp z, .set_carry + ld a, b + ldh [hTemp_ffa0], a + ld a, $ff + ldh [hTempRetreatCostCards], a + +; check energy required to retreat +; if the cost is 0, retreat right away + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + ld [wTempCardRetreatCost], a + or a + jp z, .retreat + +; if cost > 0 and number of energy cards attached == cost +; discard them all + xor a + call CreateArenaOrBenchEnergyCardList + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + ld c, a + ld a, [wTempCardRetreatCost] + cp c + jr nz, .choose_energy_discard + + ld hl, hTempRetreatCostCards + ld de, wDuelTempList +.loop_1 + ld a, [de] + inc de + ld [hli], a + cp $ff + jr nz, .loop_1 + jp .retreat + +; if cost > 0 and number of energy cards attached > cost +; choose energy cards to discard according to color +.choose_energy_discard + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + ld a, [wTempCardRetreatCost] + ld c, a + +; first, look for and discard double colorless energy +; if retreat cost is >= 2 + ld hl, wDuelTempList + ld de, hTempRetreatCostCards +.loop_2 + ld a, c + cp 2 + jr c, .energy_not_same_color + ld a, [hli] + cp $ff + jr z, .energy_not_same_color + ld [de], a + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp DOUBLE_COLORLESS_ENERGY + jr nz, .loop_2 + ld a, [de] + call RemoveCardFromDuelTempList + dec hl + inc de + dec c + dec c + jr nz, .loop_2 + jr .end_retreat_list + +; second, shuffle attached cards and discard energy cards +; that are not of the same type as the Pokémon +; the exception for this are cards that are needed for +; some attacks but are not of the same color as the Pokémon +; (i.e. Psyduck's Headache attack) +; and energy cards attached to Eevee corresponding to a +; color of any of its evolutions (water, fire, lightning) +.energy_not_same_color + ld hl, wDuelTempList + call CountCardsInDuelTempList + call ShuffleCards +.loop_3 + ld a, [hli] + cp $ff + jr z, .any_energy + ld [de], a + call CheckIfEnergyIsUseful + jr c, .loop_3 + ld a, [de] + call RemoveCardFromDuelTempList + dec hl + inc de + dec c + jr nz, .loop_3 + jr .end_retreat_list + +; third, discard any card until +; cost requirement is met +.any_energy + ld hl, wDuelTempList +.loop_4 + ld a, [hli] + cp $ff + jr z, .set_carry + ld [de], a + inc de + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp DOUBLE_COLORLESS_ENERGY + jr nz, .not_double_colorless + dec c + jr z, .end_retreat_list +.not_double_colorless + dec c + jr nz, .loop_4 + +.end_retreat_list + ld a, $ff + ld [de], a + +.retreat + ld a, OPPACTION_ATTEMPT_RETREAT + bank1call AIMakeDecision + or a + ret +.set_carry + scf + ret + +; handle Mysterious Fossil and Clefairy Doll +; if there are bench Pokémon, use effect to discard card +; this is equivalent to using its Pokémon Power +.mysterious_fossil_or_clefairy_doll + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + jr nc, .has_bench + ; doesn't have any bench + pop af + jr .set_carry + +.has_bench + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff9f], a + xor a + ldh [hTemp_ffa0], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + pop af + ldh [hAIPkmnPowerEffectParam], a + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + or a + ret diff --git a/src/engine/ai/special_attacks.asm b/src/engine/ai/special_attacks.asm index 5ec465e..770324e 100644 --- a/src/engine/ai/special_attacks.asm +++ b/src/engine/ai/special_attacks.asm @@ -1,481 +1,481 @@ -; this function handles attacks with the SPECIAL_AI_HANDLING set, -; and makes specific checks in each of these attacks -; to either return a positive score (value above $80) -; or a negative score (value below $80). -; input: -; hTempPlayAreaLocation_ff9d = location of card with attack. -HandleSpecialAIAttacks: ; 16dcd (5:6dcd) - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - - cp NIDORANF - jr z, .NidoranFCallForFamily - cp ODDISH - jr z, .CallForFamily - cp BELLSPROUT - jr z, .CallForFamily - cp EXEGGUTOR - jp z, .Teleport - cp SCYTHER - jp z, .SwordsDanceAndFocusEnergy - cp KRABBY - jr z, .CallForFamily - cp VAPOREON1 - jp z, .SwordsDanceAndFocusEnergy - cp ELECTRODE2 - jp z, .ChainLightning - cp MAROWAK1 - jr z, .CallForFriend - cp MEW3 - jp z, .DevolutionBeam - cp JIGGLYPUFF2 - jp z, .FriendshipSong - cp PORYGON - jp z, .Conversion - cp MEWTWO3 - jp z, .EnergyAbsorption - cp MEWTWO2 - jp z, .EnergyAbsorption - cp NINETALES2 - jp z, .MixUp - cp ZAPDOS3 - jp z, .BigThunder - cp KANGASKHAN - jp z, .Fetch - cp DUGTRIO - jp z, .Earthquake - cp ELECTRODE1 - jp z, .EnergySpike - cp GOLDUCK - jp z, .HyperBeam - cp DRAGONAIR - jp z, .HyperBeam - -; return zero score. -.zero_score - xor a - ret - -; if any of card ID in a is found in deck, -; return a score of $80 + slots available in bench. -.CallForFamily: ; 16e3e (5:6e3e) - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr nc, .zero_score - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_BENCH_POKEMON - jr nc, .zero_score - ld b, a - ld a, MAX_BENCH_POKEMON - sub b - add $80 - ret - -; if any of NidoranM or NidoranF is found in deck, -; return a score of $80 + slots available in bench. -.NidoranFCallForFamily: ; 16e55 (5:6e55) - ld e, NIDORANM - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found_nidoran - ld e, NIDORANF - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr nc, .zero_score -.found_nidoran - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_PLAY_AREA_POKEMON - jr nc, .zero_score - ld b, a - ld a, MAX_PLAY_AREA_POKEMON - sub b - add $80 - ret - -; checks for certain card IDs of Fighting color in deck. -; if any of them are found, return a score of -; $80 + slots available in bench. -.CallForFriend: ; 16e77 (5:6e77) - ld e, GEODUDE - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found_fighting_card - ld e, ONIX - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found_fighting_card - ld e, CUBONE - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found_fighting_card - ld e, RHYHORN - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found_fighting_card - jr .zero_score -.found_fighting_card - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_BENCH_POKEMON - jr nc, .zero_score - ld b, a - ld a, MAX_BENCH_POKEMON - sub b - add $80 - ret - -; if any basic cards are found in deck, -; return a score of $80 + slots available in bench. -.FriendshipSong: ; 16ead (5:6ead) - call CheckIfAnyBasicPokemonInDeck - jr nc, .zero_score - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_PLAY_AREA_POKEMON - jr nc, .zero_score - ld b, a - ld a, MAX_PLAY_AREA_POKEMON - sub b - add $80 - ret - -; if AI decides to retreat, return a score of $80 + 10. -.Teleport: ; 16ec2 (5:6ec2) - call AIDecideWhetherToRetreat - jp nc, .zero_score - ld a, $8a - ret - -; tests for the following conditions: -; - player is under No Damage substatus; -; - second attack is unusable; -; - second attack deals no damage; -; if any are true, returns score of $80 + 5. -.SwordsDanceAndFocusEnergy: ; 16ecb (5:6ecb) - ld a, [wAICannotDamage] - or a - jr nz, .swords_dance_focus_energy_success - ld a, SECOND_ATTACK - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - jr c, .swords_dance_focus_energy_success - ld a, SECOND_ATTACK - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - or a - jp nz, .zero_score -.swords_dance_focus_energy_success - ld a, $85 - ret - -; checks player's active card color, then -; loops through bench looking for a Pokémon -; with that same color. -; if none are found, returns score of $80 + 2. -.ChainLightning: ; 16eea (5:6eea) - call SwapTurn - call GetArenaCardColor - call SwapTurn - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop_chain_lightning_bench - ld a, [hli] - cp $ff - jr z, .chain_lightning_success - push bc - call GetCardIDFromDeckIndex - call GetCardType - pop bc - cp b - jr nz, .loop_chain_lightning_bench - jp .zero_score -.chain_lightning_success - ld a, $82 - ret - -.DevolutionBeam: ; 16f0f (5:6f0f) - call LookForCardThatIsKnockedOutOnDevolution - jp nc, .zero_score - ld a, $85 - ret - -; first checks if card is confused, and if so return 0. -; then checks number of Pokémon in bench that are viable to use: -; - if that number is < 2 and this attack is Conversion 1 OR -; - if that number is >= 2 and this attack is Conversion 2 -; then return score of $80 + 2. -; otherwise return score of $80 + 1. -.Conversion: ; 16f18 (5:6f18) - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and CNF_SLP_PRZ - cp CONFUSED - jp z, .zero_score - - ld a, [wSelectedAttack] - or a - jr nz, .conversion_2 - -; conversion 1 - call CountNumberOfSetUpBenchPokemon - cp 2 - jr c, .low_conversion_score - ld a, $82 - ret - -.conversion_2 - call CountNumberOfSetUpBenchPokemon - cp 2 - jr nc, .low_conversion_score - ld a, $82 - ret - -.low_conversion_score - ld a, $81 - ret - -; if any Psychic Energy is found in the Discard Pile, -; return a score of $80 + 2. -.EnergyAbsorption: ; 16f41 (5:6f41) - ld e, PSYCHIC_ENERGY - ld a, CARD_LOCATION_DISCARD_PILE - call CheckIfAnyCardIDinLocation - jp nc, .zero_score - ld a, $82 - ret - -; if player has cards in hand, AI calls Random: -; - 1/3 chance to encourage attack regardless; -; - 1/3 chance to dismiss attack regardless; -; - 1/3 change to make some checks to player's hand. -; AI tallies number of basic cards in hand, and if this -; number is >= 2, encourage attack. -; otherwise, if it finds an evolution card in hand that -; can evolve a card in player's deck, encourage. -; if encouraged, returns a score of $80 + 3. -.MixUp: ; 16f4e (5:6f4e) - ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND - call GetNonTurnDuelistVariable - or a - ret z - - ld a, 3 - call Random - or a - jr z, .encourage_mix_up - dec a - ret z - call SwapTurn - call CreateHandCardList - call SwapTurn - or a - ret z ; return if no hand cards (again) - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetNonTurnDuelistVariable - cp 3 - jr nc, .mix_up_check_play_area - - ld hl, wDuelTempList - ld b, 0 -.loop_mix_up_hand - ld a, [hli] - cp $ff - jr z, .tally_basic_cards - push bc - call SwapTurn - call LoadCardDataToBuffer2_FromDeckIndex - call SwapTurn - pop bc - ld a, [wLoadedCard2Type] - cp TYPE_ENERGY - jr nc, .loop_mix_up_hand - ld a, [wLoadedCard2Stage] - or a - jr nz, .loop_mix_up_hand - ; is a basic Pokémon card - inc b - jr .loop_mix_up_hand -.tally_basic_cards - ld a, b - cp 2 - jr nc, .encourage_mix_up - -; less than 2 basic cards in hand -.mix_up_check_play_area - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable -.loop_mix_up_play_area - ld a, [hli] - cp $ff - jp z, .zero_score - push hl - call SwapTurn - call CheckForEvolutionInList - call SwapTurn - pop hl - jr nc, .loop_mix_up_play_area - -.encourage_mix_up - ld a, $83 - ret - -; return score of $80 + 3. -.BigThunder: ; 16fb8 (5:6fb8) - ld a, $83 - ret - -; dismiss attack if cards in deck <= 20. -; otherwise return a score of $80 + 0. -.Fetch: ; 16fbb (5:6fbb) - ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK - call GetTurnDuelistVariable - cp 41 - jp nc, .zero_score - ld a, $80 - ret - -; dismiss attack if number of own benched cards which would -; be KOd is greater than or equal to the number -; of prize cards left for player. -.Earthquake: ; 16fc8 (5:6fc8) - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - - lb de, 0, 0 -.loop_earthquake - inc e - ld a, [hli] - cp $ff - jr z, .count_prizes - ld a, e - add DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - cp 20 - jr nc, .loop_earthquake - inc d - jr .loop_earthquake - -.count_prizes - push de - call CountPrizes - pop de - cp d - jp c, .zero_score - jp z, .zero_score - ld a, $80 - ret - -; if there's any lightning energy cards in deck, -; return a score of $80 + 3. -.EnergySpike: ; 16ff2 (5:6ff2) - ld a, CARD_LOCATION_DECK - ld e, LIGHTNING_ENERGY - call CheckIfAnyCardIDinLocation - jp nc, .zero_score - call AIProcessButDontPlayEnergy_SkipEvolution - jp nc, .zero_score - ld a, $83 - ret - -; only incentivize attack if player's active card, -; has any energy cards attached, and if so, -; return a score of $80 + 3. -.HyperBeam: ; 17005 (5:7005) - call SwapTurn - ld e, PLAY_AREA_ARENA - call CountNumberOfEnergyCardsAttached - call SwapTurn - or a - jr z, .hyper_beam_neutral - ld a, $83 - ret -.hyper_beam_neutral - ld a, $80 - ret - -; called when second attack is determined by AI to have -; more AI score than the first attack, so that it checks -; whether the first attack is a better alternative. -CheckWhetherToSwitchToFirstAttack: ; 17019 (5:7019) -; this checks whether the first attack is also viable -; (has more than minimum score to be used) - ld a, [wFirstAttackAIScore] - cp $50 - jr c, .keep_second_attack - -; first attack has more than minimum score to be used. -; check if second attack can KO. -; in case it can't, the AI keeps it as the attack to be used. -; (possibly due to the assumption that if the -; second attack cannot KO, the first attack can't KO as well.) - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call EstimateDamage_VersusDefendingCard - ld a, DUELVARS_ARENA_CARD_HP - call GetNonTurnDuelistVariable - ld hl, wDamage - sub [hl] - jr z, .check_flag - jr nc, .keep_second_attack - -; second attack can ko, check its flag. -; in case its effect is to heal user or nullify/weaken damage -; next turn, keep second attack as the option. -; otherwise switch to the first attack. -.check_flag - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ld d, a - ld e, SECOND_ATTACK - call CopyAttackDataAndDamage_FromDeckIndex - ld a, ATTACK_FLAG2_ADDRESS | HEAL_USER_F - call CheckLoadedAttackFlag - jr c, .keep_second_attack - ld a, ATTACK_FLAG2_ADDRESS | NULLIFY_OR_WEAKEN_ATTACK_F - call CheckLoadedAttackFlag - jr c, .keep_second_attack -; switch to first attack - xor a - ld [wSelectedAttack], a - ret -.keep_second_attack - ld a, $01 - ld [wSelectedAttack], a - ret - -; returns carry if there are -; any basic Pokémon cards in deck. -CheckIfAnyBasicPokemonInDeck: ; 17057 (5:7057) - ld e, 0 -.loop - ld a, DUELVARS_CARD_LOCATIONS - add e - call GetTurnDuelistVariable - cp CARD_LOCATION_DECK - jr nz, .next - push de - ld a, e - call LoadCardDataToBuffer2_FromDeckIndex - pop de - ld a, [wLoadedCard2Type] - cp TYPE_ENERGY - jr nc, .next - ld a, [wLoadedCard2Stage] - or a - jr z, .set_carry -.next - inc e - ld a, DECK_SIZE - cp e - jr nz, .loop - or a - ret -.set_carry - scf - ret +; this function handles attacks with the SPECIAL_AI_HANDLING set, +; and makes specific checks in each of these attacks +; to either return a positive score (value above $80) +; or a negative score (value below $80). +; input: +; hTempPlayAreaLocation_ff9d = location of card with attack. +HandleSpecialAIAttacks: ; 16dcd (5:6dcd) + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + + cp NIDORANF + jr z, .NidoranFCallForFamily + cp ODDISH + jr z, .CallForFamily + cp BELLSPROUT + jr z, .CallForFamily + cp EXEGGUTOR + jp z, .Teleport + cp SCYTHER + jp z, .SwordsDanceAndFocusEnergy + cp KRABBY + jr z, .CallForFamily + cp VAPOREON1 + jp z, .SwordsDanceAndFocusEnergy + cp ELECTRODE2 + jp z, .ChainLightning + cp MAROWAK1 + jr z, .CallForFriend + cp MEW3 + jp z, .DevolutionBeam + cp JIGGLYPUFF2 + jp z, .FriendshipSong + cp PORYGON + jp z, .Conversion + cp MEWTWO3 + jp z, .EnergyAbsorption + cp MEWTWO2 + jp z, .EnergyAbsorption + cp NINETALES2 + jp z, .MixUp + cp ZAPDOS3 + jp z, .BigThunder + cp KANGASKHAN + jp z, .Fetch + cp DUGTRIO + jp z, .Earthquake + cp ELECTRODE1 + jp z, .EnergySpike + cp GOLDUCK + jp z, .HyperBeam + cp DRAGONAIR + jp z, .HyperBeam + +; return zero score. +.zero_score + xor a + ret + +; if any of card ID in a is found in deck, +; return a score of $80 + slots available in bench. +.CallForFamily: ; 16e3e (5:6e3e) + ld a, CARD_LOCATION_DECK + call CheckIfAnyCardIDinLocation + jr nc, .zero_score + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_BENCH_POKEMON + jr nc, .zero_score + ld b, a + ld a, MAX_BENCH_POKEMON + sub b + add $80 + ret + +; if any of NidoranM or NidoranF is found in deck, +; return a score of $80 + slots available in bench. +.NidoranFCallForFamily: ; 16e55 (5:6e55) + ld e, NIDORANM + ld a, CARD_LOCATION_DECK + call CheckIfAnyCardIDinLocation + jr c, .found_nidoran + ld e, NIDORANF + ld a, CARD_LOCATION_DECK + call CheckIfAnyCardIDinLocation + jr nc, .zero_score +.found_nidoran + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr nc, .zero_score + ld b, a + ld a, MAX_PLAY_AREA_POKEMON + sub b + add $80 + ret + +; checks for certain card IDs of Fighting color in deck. +; if any of them are found, return a score of +; $80 + slots available in bench. +.CallForFriend: ; 16e77 (5:6e77) + ld e, GEODUDE + ld a, CARD_LOCATION_DECK + call CheckIfAnyCardIDinLocation + jr c, .found_fighting_card + ld e, ONIX + ld a, CARD_LOCATION_DECK + call CheckIfAnyCardIDinLocation + jr c, .found_fighting_card + ld e, CUBONE + ld a, CARD_LOCATION_DECK + call CheckIfAnyCardIDinLocation + jr c, .found_fighting_card + ld e, RHYHORN + ld a, CARD_LOCATION_DECK + call CheckIfAnyCardIDinLocation + jr c, .found_fighting_card + jr .zero_score +.found_fighting_card + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_BENCH_POKEMON + jr nc, .zero_score + ld b, a + ld a, MAX_BENCH_POKEMON + sub b + add $80 + ret + +; if any basic cards are found in deck, +; return a score of $80 + slots available in bench. +.FriendshipSong: ; 16ead (5:6ead) + call CheckIfAnyBasicPokemonInDeck + jr nc, .zero_score + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr nc, .zero_score + ld b, a + ld a, MAX_PLAY_AREA_POKEMON + sub b + add $80 + ret + +; if AI decides to retreat, return a score of $80 + 10. +.Teleport: ; 16ec2 (5:6ec2) + call AIDecideWhetherToRetreat + jp nc, .zero_score + ld a, $8a + ret + +; tests for the following conditions: +; - player is under No Damage substatus; +; - second attack is unusable; +; - second attack deals no damage; +; if any are true, returns score of $80 + 5. +.SwordsDanceAndFocusEnergy: ; 16ecb (5:6ecb) + ld a, [wAICannotDamage] + or a + jr nz, .swords_dance_focus_energy_success + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .swords_dance_focus_energy_success + ld a, SECOND_ATTACK + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jp nz, .zero_score +.swords_dance_focus_energy_success + ld a, $85 + ret + +; checks player's active card color, then +; loops through bench looking for a Pokémon +; with that same color. +; if none are found, returns score of $80 + 2. +.ChainLightning: ; 16eea (5:6eea) + call SwapTurn + call GetArenaCardColor + call SwapTurn + ld b, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable +.loop_chain_lightning_bench + ld a, [hli] + cp $ff + jr z, .chain_lightning_success + push bc + call GetCardIDFromDeckIndex + call GetCardType + pop bc + cp b + jr nz, .loop_chain_lightning_bench + jp .zero_score +.chain_lightning_success + ld a, $82 + ret + +.DevolutionBeam: ; 16f0f (5:6f0f) + call LookForCardThatIsKnockedOutOnDevolution + jp nc, .zero_score + ld a, $85 + ret + +; first checks if card is confused, and if so return 0. +; then checks number of Pokémon in bench that are viable to use: +; - if that number is < 2 and this attack is Conversion 1 OR +; - if that number is >= 2 and this attack is Conversion 2 +; then return score of $80 + 2. +; otherwise return score of $80 + 1. +.Conversion: ; 16f18 (5:6f18) + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + cp CONFUSED + jp z, .zero_score + + ld a, [wSelectedAttack] + or a + jr nz, .conversion_2 + +; conversion 1 + call CountNumberOfSetUpBenchPokemon + cp 2 + jr c, .low_conversion_score + ld a, $82 + ret + +.conversion_2 + call CountNumberOfSetUpBenchPokemon + cp 2 + jr nc, .low_conversion_score + ld a, $82 + ret + +.low_conversion_score + ld a, $81 + ret + +; if any Psychic Energy is found in the Discard Pile, +; return a score of $80 + 2. +.EnergyAbsorption: ; 16f41 (5:6f41) + ld e, PSYCHIC_ENERGY + ld a, CARD_LOCATION_DISCARD_PILE + call CheckIfAnyCardIDinLocation + jp nc, .zero_score + ld a, $82 + ret + +; if player has cards in hand, AI calls Random: +; - 1/3 chance to encourage attack regardless; +; - 1/3 chance to dismiss attack regardless; +; - 1/3 change to make some checks to player's hand. +; AI tallies number of basic cards in hand, and if this +; number is >= 2, encourage attack. +; otherwise, if it finds an evolution card in hand that +; can evolve a card in player's deck, encourage. +; if encouraged, returns a score of $80 + 3. +.MixUp: ; 16f4e (5:6f4e) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetNonTurnDuelistVariable + or a + ret z + + ld a, 3 + call Random + or a + jr z, .encourage_mix_up + dec a + ret z + call SwapTurn + call CreateHandCardList + call SwapTurn + or a + ret z ; return if no hand cards (again) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 3 + jr nc, .mix_up_check_play_area + + ld hl, wDuelTempList + ld b, 0 +.loop_mix_up_hand + ld a, [hli] + cp $ff + jr z, .tally_basic_cards + push bc + call SwapTurn + call LoadCardDataToBuffer2_FromDeckIndex + call SwapTurn + pop bc + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .loop_mix_up_hand + ld a, [wLoadedCard2Stage] + or a + jr nz, .loop_mix_up_hand + ; is a basic Pokémon card + inc b + jr .loop_mix_up_hand +.tally_basic_cards + ld a, b + cp 2 + jr nc, .encourage_mix_up + +; less than 2 basic cards in hand +.mix_up_check_play_area + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable +.loop_mix_up_play_area + ld a, [hli] + cp $ff + jp z, .zero_score + push hl + call SwapTurn + call CheckForEvolutionInList + call SwapTurn + pop hl + jr nc, .loop_mix_up_play_area + +.encourage_mix_up + ld a, $83 + ret + +; return score of $80 + 3. +.BigThunder: ; 16fb8 (5:6fb8) + ld a, $83 + ret + +; dismiss attack if cards in deck <= 20. +; otherwise return a score of $80 + 0. +.Fetch: ; 16fbb (5:6fbb) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp 41 + jp nc, .zero_score + ld a, $80 + ret + +; dismiss attack if number of own benched cards which would +; be KOd is greater than or equal to the number +; of prize cards left for player. +.Earthquake: ; 16fc8 (5:6fc8) + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + + lb de, 0, 0 +.loop_earthquake + inc e + ld a, [hli] + cp $ff + jr z, .count_prizes + ld a, e + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + cp 20 + jr nc, .loop_earthquake + inc d + jr .loop_earthquake + +.count_prizes + push de + call CountPrizes + pop de + cp d + jp c, .zero_score + jp z, .zero_score + ld a, $80 + ret + +; if there's any lightning energy cards in deck, +; return a score of $80 + 3. +.EnergySpike: ; 16ff2 (5:6ff2) + ld a, CARD_LOCATION_DECK + ld e, LIGHTNING_ENERGY + call CheckIfAnyCardIDinLocation + jp nc, .zero_score + call AIProcessButDontPlayEnergy_SkipEvolution + jp nc, .zero_score + ld a, $83 + ret + +; only incentivize attack if player's active card, +; has any energy cards attached, and if so, +; return a score of $80 + 3. +.HyperBeam: ; 17005 (5:7005) + call SwapTurn + ld e, PLAY_AREA_ARENA + call CountNumberOfEnergyCardsAttached + call SwapTurn + or a + jr z, .hyper_beam_neutral + ld a, $83 + ret +.hyper_beam_neutral + ld a, $80 + ret + +; called when second attack is determined by AI to have +; more AI score than the first attack, so that it checks +; whether the first attack is a better alternative. +CheckWhetherToSwitchToFirstAttack: ; 17019 (5:7019) +; this checks whether the first attack is also viable +; (has more than minimum score to be used) + ld a, [wFirstAttackAIScore] + cp $50 + jr c, .keep_second_attack + +; first attack has more than minimum score to be used. +; check if second attack can KO. +; in case it can't, the AI keeps it as the attack to be used. +; (possibly due to the assumption that if the +; second attack cannot KO, the first attack can't KO as well.) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld hl, wDamage + sub [hl] + jr z, .check_flag + jr nc, .keep_second_attack + +; second attack can ko, check its flag. +; in case its effect is to heal user or nullify/weaken damage +; next turn, keep second attack as the option. +; otherwise switch to the first attack. +.check_flag + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + ld e, SECOND_ATTACK + call CopyAttackDataAndDamage_FromDeckIndex + ld a, ATTACK_FLAG2_ADDRESS | HEAL_USER_F + call CheckLoadedAttackFlag + jr c, .keep_second_attack + ld a, ATTACK_FLAG2_ADDRESS | NULLIFY_OR_WEAKEN_ATTACK_F + call CheckLoadedAttackFlag + jr c, .keep_second_attack +; switch to first attack + xor a + ld [wSelectedAttack], a + ret +.keep_second_attack + ld a, $01 + ld [wSelectedAttack], a + ret + +; returns carry if there are +; any basic Pokémon cards in deck. +CheckIfAnyBasicPokemonInDeck: ; 17057 (5:7057) + ld e, 0 +.loop + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + cp CARD_LOCATION_DECK + jr nz, .next + push de + ld a, e + call LoadCardDataToBuffer2_FromDeckIndex + pop de + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .next + ld a, [wLoadedCard2Stage] + or a + jr z, .set_carry +.next + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop + or a + ret +.set_carry + scf + ret diff --git a/src/engine/sequences/credits_sequence_commands.asm b/src/engine/sequences/credits_sequence_commands.asm index 9b66fd4..0fde871 100644 --- a/src/engine/sequences/credits_sequence_commands.asm +++ b/src/engine/sequences/credits_sequence_commands.asm @@ -1,464 +1,464 @@ -SetCreditsSequenceCmdPtr: ; 1d7fc (7:57fc) - ld a, LOW(CreditsSequence) - ld [wSequenceCmdPtr + 0], a - ld a, HIGH(CreditsSequence) - ld [wSequenceCmdPtr + 1], a - xor a - ld [wSequenceDelay], a - ret - -ExecuteCreditsSequenceCmd: ; 1d80b (7:580b) - ld a, [wSequenceDelay] - or a - jr z, .call_func - cp $ff - ret z ; sequence ended - - dec a ; still waiting - ld [wSequenceDelay], a - ret - -.call_func - ld a, [wSequenceCmdPtr + 0] - ld l, a - ld a, [wSequenceCmdPtr + 1] - ld h, a - ld a, [hli] - ld e, a - ld a, [hli] - ld d, a - push de - ld a, [hli] - ld c, a - ld a, [hli] - ld b, a - ld a, [hli] - ld e, a - ld a, [hli] - ld d, a - pop hl - call CallHL2 - jr ExecuteCreditsSequenceCmd - - ret ; stray ret - -AdvanceCreditsSequenceCmdPtrBy2: ; 1d835 (7:5835) - ld a, 2 - jr AdvanceCreditsSequenceCmdPtr - -AdvanceCreditsSequenceCmdPtrBy3: ; 1d839 (7:5839) - ld a, 3 - jr AdvanceCreditsSequenceCmdPtr - -AdvanceCreditsSequenceCmdPtrBy5: ; 1d83d (7:583d) - ld a, 5 - jr AdvanceCreditsSequenceCmdPtr - -AdvanceCreditsSequenceCmdPtrBy6: ; 1d841 (7:5841) - ld a, 6 - jr AdvanceCreditsSequenceCmdPtr - -AdvanceCreditsSequenceCmdPtrBy4: ; 1d845 (7:5845) - ld a, 4 -; fallthrough - -AdvanceCreditsSequenceCmdPtr: ; 1d847 (7:5847) - push hl - ld hl, wSequenceCmdPtr - add [hl] - ld [hli], a - ld a, [hl] - adc 0 - ld [hl], a - pop hl - ret - -CreditsSequenceCmd_Wait: ; 1d853 (7:5853) - ld a, c - ld [wSequenceDelay], a - jp AdvanceCreditsSequenceCmdPtrBy3 - -CreditsSequenceCmd_LoadScene: ; 1d85a (7:585a) - push bc - push de - farcall ClearNumLoadedFramesetSubgroups - call EmptyScreen - xor a - ldh [hSCX], a - ldh [hSCY], a - farcall Func_1288c - pop de - pop bc - ld a, c - ld c, b - ld b, a - ld a, e - call LoadScene - jp AdvanceCreditsSequenceCmdPtrBy5 - -CreditsSequenceCmd_LoadBooster: ; 1d878 (7:5878) - push bc - push de - farcall ClearNumLoadedFramesetSubgroups - call EmptyScreen - xor a - ldh [hSCX], a - ldh [hSCY], a - farcall Func_1288c - pop de - pop bc - ld a, c - ld c, b - ld b, a - ld a, e - farcall LoadBoosterGfx - jp AdvanceCreditsSequenceCmdPtrBy5 - -CreditsSequenceCmd_LoadClubMap: ; 1d897 (7:5897) - ld b, $00 - ld hl, wMastersBeatenList - add hl, bc - ld a, [hl] - or a - jr nz, .at_least_1 - inc a -.at_least_1 - dec a - ld c, a - add a - add a - add c ; *5 - ld c, a - ld hl, .CreditsOWClubMaps - add hl, bc - ld a, [hli] ; map x coord - ld c, a - ld a, [hli] ; map y coord - ld b, a - ld a, [hli] ; map ID - ld e, a - push hl - call LoadOWMapForCreditsSequence - pop hl - ld a, [hli] - ld h, [hl] - ld l, a - or h - jr z, .done - -.loop_npcs - ld a, [hli] ; NPC ID - or a - jr z, .done - ld d, a - ld a, [hli] ; NPC x coord - ld c, a - ld a, [hli] ; NPC y coord - ld b, a - ld a, [hli] ; NPC direction - ld e, a - push hl - call LoadNPCForCreditsSequence - pop hl - jr .loop_npcs - -.done - jp AdvanceCreditsSequenceCmdPtrBy3 - -credits_club_map: MACRO - db \1 ; x - db \2 ; y - db \3 ; OW map - dw \4 ; list of NPCs to load -ENDM - -.CreditsOWClubMaps - credits_club_map 16, 0, FIGHTING_CLUB, .CreditsNPCs_FightingClub - credits_club_map 32, 0, ROCK_CLUB, .CreditsNPCs_RockClub - credits_club_map 64, 0, WATER_CLUB, .CreditsNPCs_WaterClub - credits_club_map 32, 0, LIGHTNING_CLUB, .CreditsNPCs_LightningClub - credits_club_map 32, 0, GRASS_CLUB, .CreditsNPCs_GrassClub - credits_club_map 32, 16, PSYCHIC_CLUB, .CreditsNPCs_PsychicClub - credits_club_map 0, 0, SCIENCE_CLUB, .CreditsNPCs_ScienceClub - credits_club_map 32, 0, FIRE_CLUB, .CreditsNPCs_FireClub - credits_club_map 32, 0, CHALLENGE_HALL, .CreditsNPCs_ChallengeHall - credits_club_map 48, 0, POKEMON_DOME, .CreditsNPCs_PokemonDome - -.CreditsNPCs_FightingClub - ; NPC ID, x, y, direction - db NPC_CHRIS, 4, 8, SOUTH - db NPC_MICHAEL, 14, 10, SOUTH - db NPC_JESSICA, 18, 6, EAST - db NPC_MITCH, 10, 4, SOUTH - db NPC_PLAYER_CREDITS, 10, 6, NORTH - db $00 - -.CreditsNPCs_RockClub - ; NPC ID, x, y, direction - db NPC_RYAN, 20, 14, EAST - db NPC_GENE, 12, 6, SOUTH - db NPC_PLAYER_CREDITS, 12, 8, NORTH - db $00 - -.CreditsNPCs_WaterClub - ; NPC ID, x, y, direction - db NPC_JOSHUA, 22, 8, SOUTH - db NPC_AMY, 22, 4, NORTH - db NPC_PLAYER_CREDITS, 18, 10, NORTH - db $00 - -.CreditsNPCs_LightningClub - ; NPC ID, x, y, direction - db NPC_NICHOLAS, 6, 10, SOUTH - db NPC_BRANDON, 22, 12, NORTH - db NPC_ISAAC, 12, 4, NORTH - db NPC_PLAYER_CREDITS, 12, 10, NORTH - db $00 - -.CreditsNPCs_GrassClub - ; NPC ID, x, y, direction - db NPC_KRISTIN, 4, 10, EAST - db NPC_HEATHER, 14, 16, SOUTH - db NPC_NIKKI, 12, 4, SOUTH - db NPC_PLAYER_CREDITS, 12, 6, NORTH - db $00 - -.CreditsNPCs_PsychicClub - ; NPC ID, x, y, direction - db NPC_DANIEL, 8, 8, NORTH - db NPC_STEPHANIE, 22, 12, EAST - db NPC_MURRAY1, 12, 6, SOUTH - db NPC_PLAYER_CREDITS, 12, 8, NORTH - db $00 - -.CreditsNPCs_ScienceClub - ; NPC ID, x, y, direction - db NPC_JOSEPH, 10, 10, WEST - db NPC_RICK, 4, 4, SOUTH - db NPC_PLAYER_CREDITS, 4, 6, NORTH - db $00 - -.CreditsNPCs_FireClub - ; NPC ID, x, y, direction - db NPC_ADAM, 8, 14, SOUTH - db NPC_JONATHAN, 18, 10, SOUTH - db NPC_KEN, 14, 4, SOUTH - db NPC_PLAYER_CREDITS, 14, 6, NORTH - db $00 - -.CreditsNPCs_ChallengeHall - ; NPC ID, x, y, direction - db NPC_HOST, 14, 4, SOUTH - db NPC_RONALD1, 18, 8, WEST - db NPC_PLAYER_CREDITS, 12, 8, EAST - db $00 - -.CreditsNPCs_PokemonDome - ; NPC ID, x, y, direction - db NPC_COURTNEY, 18, 4, SOUTH - db NPC_STEVE, 22, 4, SOUTH - db NPC_JACK, 8, 4, SOUTH - db NPC_ROD, 14, 6, SOUTH - db NPC_PLAYER_CREDITS, 14, 10, NORTH - db $00 - -; bc = coordinates -; e = OW map -LoadOWMapForCreditsSequence: ; 1d9a6 (7:59a6) - push bc - push de - call EmptyScreen - pop de - pop bc - - ; set input coordinates and map - ld a, c - ldh [hSCX], a - ld a, b - ldh [hSCY], a - ld a, e - ld [wCurMap], a - - farcall LoadMapTilesAndPals - farcall Func_c9c7 - farcall SafelyCopyBGMapFromSRAMToVRAM - farcall DoMapOWFrame - xor a - ld [wd4ca], a - ld [wd4cb], a - ld a, PALETTE_29 - farcall LoadPaletteData - ret - -CreditsSequenceCmd_LoadOWMap: ; 1d9d5 (7:59d5) - call LoadOWMapForCreditsSequence - jp AdvanceCreditsSequenceCmdPtrBy5 - -CreditsSequenceCmd_DisableLCD: ; 1d9db (7:59db) - call DisableLCD - jp AdvanceCreditsSequenceCmdPtrBy2 - -CreditsSequenceCmd_FadeIn: ; 1d9e1 (7:59e1) - call DisableLCD - call Set_WD_on - farcall Func_10af9 - jp AdvanceCreditsSequenceCmdPtrBy2 - -CreditsSequenceCmd_FadeOut: ; 1d9ee (7:59ee) - farcall Func_10ab4 - call Func_3ca4 - call EnableLCD - call DoFrameIfLCDEnabled - call DisableLCD - call Set_WD_off - jp AdvanceCreditsSequenceCmdPtrBy2 - -CreditsSequenceCmd_DrawRectangle: ; 1da04 (7:5a04) - ld a, c - or $20 - ld e, a - ld d, $00 - ld c, b - ld b, 20 - xor a - lb hl, 0, 0 - call FillRectangle - jp AdvanceCreditsSequenceCmdPtrBy4 - -CreditsSequenceCmd_PrintText: ; 1da17 (7:5a17) - ld a, $01 - ld [wLineSeparation], a - push de - ld d, c - ld a, b - or $20 - ld e, a - call InitTextPrinting - pop hl - call PrintTextNoDelay - jp AdvanceCreditsSequenceCmdPtrBy6 - -CreditsSequenceCmd_PrintTextBox: ; 1da2c (7:5a2c) - ld a, $01 - ld [wLineSeparation], a - push de - ld d, c - ld e, b - call InitTextPrinting - pop hl - call PrintTextNoDelay - jp AdvanceCreditsSequenceCmdPtrBy6 - -CreditsSequenceCmd_InitOverlay: ; 1da3e (7:5a3e) - ld a, c - ld [wd647], a - ld a, b - ld [wd648], a - ld a, e - ld [wd649], a - ld a, d - ld [wd64a], a - call Func_1d765 - jp AdvanceCreditsSequenceCmdPtrBy6 - -CreditsSequenceCmd_LoadNPC: ; 1da54 (7:5a54) - call LoadNPCForCreditsSequence - jp AdvanceCreditsSequenceCmdPtrBy6 - -; bc = coordinates -; e = direction -; d = NPC ID -LoadNPCForCreditsSequence: ; 1da5a (7:5a5a) - ld a, c - ld [wLoadNPCXPos], a - ld a, b - ld [wLoadNPCYPos], a - ld a, e - ld [wLoadNPCDirection], a - ld a, d - farcall LoadNPCSpriteData - ld a, [wNPCSpriteID] - farcall CreateSpriteAndAnimBufferEntry - - ld c, SPRITE_ANIM_COORD_X - call GetSpriteAnimBufferProperty - ldh a, [hSCX] - ld c, a - ld a, [wLoadNPCXPos] - add a - add a - add a ; *8 - add 8 - sub c - ld [hli], a ; x - ldh a, [hSCY] - ld c, a - ld a, [wLoadNPCYPos] - add a - add a - add a ; *8 - add 16 - sub c - ld [hli], a ; y - - ld a, [wNPCAnim] - ld c, a - ld a, [wLoadNPCDirection] - add c - farcall StartNewSpriteAnimation - ret - -CreditsSequenceCmd_InitVolcanoSprite: ; 1da9e (7:5a9e) - farcall OverworldMap_InitVolcanoSprite - jp AdvanceCreditsSequenceCmdPtrBy2 - -CreditsSequenceCmd_TransformOverlay: ; 1daa5 (7:5aa5) -; either stretches or shrinks overlay -; to the input configurations - ld l, 0 - ld a, [wd647] - call .Func_1dade - ld [wd647], a - ld a, [wd648] - ld c, b - call .Func_1dade - ld [wd648], a - ld a, [wd649] - ld c, e - call .Func_1dade - ld [wd649], a - ld a, [wd64a] - ld c, d - call .Func_1dade - ld [wd64a], a - ld a, l - or a - jr z, .advance_sequence - ld a, 1 - ld [wSequenceDelay], a - ret - -.advance_sequence - call Func_1d765 - jp AdvanceCreditsSequenceCmdPtrBy6 - -; compares a with c -; if it's smaller: increase by 2 and increment l -; if it's larger: decrease by 2 and increment l -; if it's equal or $ff: do nothing -.Func_1dade - cp $ff - jr z, .done - cp c - jr z, .done - inc l - jr c, .incr_a -; decr a - dec a - dec a - jr .done -.incr_a - inc a - inc a -.done - ret +SetCreditsSequenceCmdPtr: ; 1d7fc (7:57fc) + ld a, LOW(CreditsSequence) + ld [wSequenceCmdPtr + 0], a + ld a, HIGH(CreditsSequence) + ld [wSequenceCmdPtr + 1], a + xor a + ld [wSequenceDelay], a + ret + +ExecuteCreditsSequenceCmd: ; 1d80b (7:580b) + ld a, [wSequenceDelay] + or a + jr z, .call_func + cp $ff + ret z ; sequence ended + + dec a ; still waiting + ld [wSequenceDelay], a + ret + +.call_func + ld a, [wSequenceCmdPtr + 0] + ld l, a + ld a, [wSequenceCmdPtr + 1] + ld h, a + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + push de + ld a, [hli] + ld c, a + ld a, [hli] + ld b, a + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + pop hl + call CallHL2 + jr ExecuteCreditsSequenceCmd + + ret ; stray ret + +AdvanceCreditsSequenceCmdPtrBy2: ; 1d835 (7:5835) + ld a, 2 + jr AdvanceCreditsSequenceCmdPtr + +AdvanceCreditsSequenceCmdPtrBy3: ; 1d839 (7:5839) + ld a, 3 + jr AdvanceCreditsSequenceCmdPtr + +AdvanceCreditsSequenceCmdPtrBy5: ; 1d83d (7:583d) + ld a, 5 + jr AdvanceCreditsSequenceCmdPtr + +AdvanceCreditsSequenceCmdPtrBy6: ; 1d841 (7:5841) + ld a, 6 + jr AdvanceCreditsSequenceCmdPtr + +AdvanceCreditsSequenceCmdPtrBy4: ; 1d845 (7:5845) + ld a, 4 +; fallthrough + +AdvanceCreditsSequenceCmdPtr: ; 1d847 (7:5847) + push hl + ld hl, wSequenceCmdPtr + add [hl] + ld [hli], a + ld a, [hl] + adc 0 + ld [hl], a + pop hl + ret + +CreditsSequenceCmd_Wait: ; 1d853 (7:5853) + ld a, c + ld [wSequenceDelay], a + jp AdvanceCreditsSequenceCmdPtrBy3 + +CreditsSequenceCmd_LoadScene: ; 1d85a (7:585a) + push bc + push de + farcall ClearNumLoadedFramesetSubgroups + call EmptyScreen + xor a + ldh [hSCX], a + ldh [hSCY], a + farcall Func_1288c + pop de + pop bc + ld a, c + ld c, b + ld b, a + ld a, e + call LoadScene + jp AdvanceCreditsSequenceCmdPtrBy5 + +CreditsSequenceCmd_LoadBooster: ; 1d878 (7:5878) + push bc + push de + farcall ClearNumLoadedFramesetSubgroups + call EmptyScreen + xor a + ldh [hSCX], a + ldh [hSCY], a + farcall Func_1288c + pop de + pop bc + ld a, c + ld c, b + ld b, a + ld a, e + farcall LoadBoosterGfx + jp AdvanceCreditsSequenceCmdPtrBy5 + +CreditsSequenceCmd_LoadClubMap: ; 1d897 (7:5897) + ld b, $00 + ld hl, wMastersBeatenList + add hl, bc + ld a, [hl] + or a + jr nz, .at_least_1 + inc a +.at_least_1 + dec a + ld c, a + add a + add a + add c ; *5 + ld c, a + ld hl, .CreditsOWClubMaps + add hl, bc + ld a, [hli] ; map x coord + ld c, a + ld a, [hli] ; map y coord + ld b, a + ld a, [hli] ; map ID + ld e, a + push hl + call LoadOWMapForCreditsSequence + pop hl + ld a, [hli] + ld h, [hl] + ld l, a + or h + jr z, .done + +.loop_npcs + ld a, [hli] ; NPC ID + or a + jr z, .done + ld d, a + ld a, [hli] ; NPC x coord + ld c, a + ld a, [hli] ; NPC y coord + ld b, a + ld a, [hli] ; NPC direction + ld e, a + push hl + call LoadNPCForCreditsSequence + pop hl + jr .loop_npcs + +.done + jp AdvanceCreditsSequenceCmdPtrBy3 + +credits_club_map: MACRO + db \1 ; x + db \2 ; y + db \3 ; OW map + dw \4 ; list of NPCs to load +ENDM + +.CreditsOWClubMaps + credits_club_map 16, 0, FIGHTING_CLUB, .CreditsNPCs_FightingClub + credits_club_map 32, 0, ROCK_CLUB, .CreditsNPCs_RockClub + credits_club_map 64, 0, WATER_CLUB, .CreditsNPCs_WaterClub + credits_club_map 32, 0, LIGHTNING_CLUB, .CreditsNPCs_LightningClub + credits_club_map 32, 0, GRASS_CLUB, .CreditsNPCs_GrassClub + credits_club_map 32, 16, PSYCHIC_CLUB, .CreditsNPCs_PsychicClub + credits_club_map 0, 0, SCIENCE_CLUB, .CreditsNPCs_ScienceClub + credits_club_map 32, 0, FIRE_CLUB, .CreditsNPCs_FireClub + credits_club_map 32, 0, CHALLENGE_HALL, .CreditsNPCs_ChallengeHall + credits_club_map 48, 0, POKEMON_DOME, .CreditsNPCs_PokemonDome + +.CreditsNPCs_FightingClub + ; NPC ID, x, y, direction + db NPC_CHRIS, 4, 8, SOUTH + db NPC_MICHAEL, 14, 10, SOUTH + db NPC_JESSICA, 18, 6, EAST + db NPC_MITCH, 10, 4, SOUTH + db NPC_PLAYER_CREDITS, 10, 6, NORTH + db $00 + +.CreditsNPCs_RockClub + ; NPC ID, x, y, direction + db NPC_RYAN, 20, 14, EAST + db NPC_GENE, 12, 6, SOUTH + db NPC_PLAYER_CREDITS, 12, 8, NORTH + db $00 + +.CreditsNPCs_WaterClub + ; NPC ID, x, y, direction + db NPC_JOSHUA, 22, 8, SOUTH + db NPC_AMY, 22, 4, NORTH + db NPC_PLAYER_CREDITS, 18, 10, NORTH + db $00 + +.CreditsNPCs_LightningClub + ; NPC ID, x, y, direction + db NPC_NICHOLAS, 6, 10, SOUTH + db NPC_BRANDON, 22, 12, NORTH + db NPC_ISAAC, 12, 4, NORTH + db NPC_PLAYER_CREDITS, 12, 10, NORTH + db $00 + +.CreditsNPCs_GrassClub + ; NPC ID, x, y, direction + db NPC_KRISTIN, 4, 10, EAST + db NPC_HEATHER, 14, 16, SOUTH + db NPC_NIKKI, 12, 4, SOUTH + db NPC_PLAYER_CREDITS, 12, 6, NORTH + db $00 + +.CreditsNPCs_PsychicClub + ; NPC ID, x, y, direction + db NPC_DANIEL, 8, 8, NORTH + db NPC_STEPHANIE, 22, 12, EAST + db NPC_MURRAY1, 12, 6, SOUTH + db NPC_PLAYER_CREDITS, 12, 8, NORTH + db $00 + +.CreditsNPCs_ScienceClub + ; NPC ID, x, y, direction + db NPC_JOSEPH, 10, 10, WEST + db NPC_RICK, 4, 4, SOUTH + db NPC_PLAYER_CREDITS, 4, 6, NORTH + db $00 + +.CreditsNPCs_FireClub + ; NPC ID, x, y, direction + db NPC_ADAM, 8, 14, SOUTH + db NPC_JONATHAN, 18, 10, SOUTH + db NPC_KEN, 14, 4, SOUTH + db NPC_PLAYER_CREDITS, 14, 6, NORTH + db $00 + +.CreditsNPCs_ChallengeHall + ; NPC ID, x, y, direction + db NPC_HOST, 14, 4, SOUTH + db NPC_RONALD1, 18, 8, WEST + db NPC_PLAYER_CREDITS, 12, 8, EAST + db $00 + +.CreditsNPCs_PokemonDome + ; NPC ID, x, y, direction + db NPC_COURTNEY, 18, 4, SOUTH + db NPC_STEVE, 22, 4, SOUTH + db NPC_JACK, 8, 4, SOUTH + db NPC_ROD, 14, 6, SOUTH + db NPC_PLAYER_CREDITS, 14, 10, NORTH + db $00 + +; bc = coordinates +; e = OW map +LoadOWMapForCreditsSequence: ; 1d9a6 (7:59a6) + push bc + push de + call EmptyScreen + pop de + pop bc + + ; set input coordinates and map + ld a, c + ldh [hSCX], a + ld a, b + ldh [hSCY], a + ld a, e + ld [wCurMap], a + + farcall LoadMapTilesAndPals + farcall Func_c9c7 + farcall SafelyCopyBGMapFromSRAMToVRAM + farcall DoMapOWFrame + xor a + ld [wd4ca], a + ld [wd4cb], a + ld a, PALETTE_29 + farcall LoadPaletteData + ret + +CreditsSequenceCmd_LoadOWMap: ; 1d9d5 (7:59d5) + call LoadOWMapForCreditsSequence + jp AdvanceCreditsSequenceCmdPtrBy5 + +CreditsSequenceCmd_DisableLCD: ; 1d9db (7:59db) + call DisableLCD + jp AdvanceCreditsSequenceCmdPtrBy2 + +CreditsSequenceCmd_FadeIn: ; 1d9e1 (7:59e1) + call DisableLCD + call Set_WD_on + farcall Func_10af9 + jp AdvanceCreditsSequenceCmdPtrBy2 + +CreditsSequenceCmd_FadeOut: ; 1d9ee (7:59ee) + farcall Func_10ab4 + call Func_3ca4 + call EnableLCD + call DoFrameIfLCDEnabled + call DisableLCD + call Set_WD_off + jp AdvanceCreditsSequenceCmdPtrBy2 + +CreditsSequenceCmd_DrawRectangle: ; 1da04 (7:5a04) + ld a, c + or $20 + ld e, a + ld d, $00 + ld c, b + ld b, 20 + xor a + lb hl, 0, 0 + call FillRectangle + jp AdvanceCreditsSequenceCmdPtrBy4 + +CreditsSequenceCmd_PrintText: ; 1da17 (7:5a17) + ld a, $01 + ld [wLineSeparation], a + push de + ld d, c + ld a, b + or $20 + ld e, a + call InitTextPrinting + pop hl + call PrintTextNoDelay + jp AdvanceCreditsSequenceCmdPtrBy6 + +CreditsSequenceCmd_PrintTextBox: ; 1da2c (7:5a2c) + ld a, $01 + ld [wLineSeparation], a + push de + ld d, c + ld e, b + call InitTextPrinting + pop hl + call PrintTextNoDelay + jp AdvanceCreditsSequenceCmdPtrBy6 + +CreditsSequenceCmd_InitOverlay: ; 1da3e (7:5a3e) + ld a, c + ld [wd647], a + ld a, b + ld [wd648], a + ld a, e + ld [wd649], a + ld a, d + ld [wd64a], a + call Func_1d765 + jp AdvanceCreditsSequenceCmdPtrBy6 + +CreditsSequenceCmd_LoadNPC: ; 1da54 (7:5a54) + call LoadNPCForCreditsSequence + jp AdvanceCreditsSequenceCmdPtrBy6 + +; bc = coordinates +; e = direction +; d = NPC ID +LoadNPCForCreditsSequence: ; 1da5a (7:5a5a) + ld a, c + ld [wLoadNPCXPos], a + ld a, b + ld [wLoadNPCYPos], a + ld a, e + ld [wLoadNPCDirection], a + ld a, d + farcall LoadNPCSpriteData + ld a, [wNPCSpriteID] + farcall CreateSpriteAndAnimBufferEntry + + ld c, SPRITE_ANIM_COORD_X + call GetSpriteAnimBufferProperty + ldh a, [hSCX] + ld c, a + ld a, [wLoadNPCXPos] + add a + add a + add a ; *8 + add 8 + sub c + ld [hli], a ; x + ldh a, [hSCY] + ld c, a + ld a, [wLoadNPCYPos] + add a + add a + add a ; *8 + add 16 + sub c + ld [hli], a ; y + + ld a, [wNPCAnim] + ld c, a + ld a, [wLoadNPCDirection] + add c + farcall StartNewSpriteAnimation + ret + +CreditsSequenceCmd_InitVolcanoSprite: ; 1da9e (7:5a9e) + farcall OverworldMap_InitVolcanoSprite + jp AdvanceCreditsSequenceCmdPtrBy2 + +CreditsSequenceCmd_TransformOverlay: ; 1daa5 (7:5aa5) +; either stretches or shrinks overlay +; to the input configurations + ld l, 0 + ld a, [wd647] + call .Func_1dade + ld [wd647], a + ld a, [wd648] + ld c, b + call .Func_1dade + ld [wd648], a + ld a, [wd649] + ld c, e + call .Func_1dade + ld [wd649], a + ld a, [wd64a] + ld c, d + call .Func_1dade + ld [wd64a], a + ld a, l + or a + jr z, .advance_sequence + ld a, 1 + ld [wSequenceDelay], a + ret + +.advance_sequence + call Func_1d765 + jp AdvanceCreditsSequenceCmdPtrBy6 + +; compares a with c +; if it's smaller: increase by 2 and increment l +; if it's larger: decrease by 2 and increment l +; if it's equal or $ff: do nothing +.Func_1dade + cp $ff + jr z, .done + cp c + jr z, .done + inc l + jr c, .incr_a +; decr a + dec a + dec a + jr .done +.incr_a + inc a + inc a +.done + ret diff --git a/src/engine/sequences/opening_sequence_commands.asm b/src/engine/sequences/opening_sequence_commands.asm index fac3aa7..6697b11 100644 --- a/src/engine/sequences/opening_sequence_commands.asm +++ b/src/engine/sequences/opening_sequence_commands.asm @@ -1,325 +1,325 @@ -ExecuteOpeningSequenceCmd: ; 1d408 (7:5408) - ld a, [wSequenceDelay] - or a - jr z, .call_function - cp $ff - ret z ; sequence ended - - dec a ; still waiting - ld [wSequenceDelay], a - ret - -.call_function - ld a, [wSequenceCmdPtr + 0] - ld l, a - ld a, [wSequenceCmdPtr + 1] - ld h, a - ld a, [hli] - ld e, a - ld a, [hli] - ld d, a - ld a, [hli] - ld c, a - ld a, [hli] - ld b, a - ld l, e - ld h, d - call CallHL2 - jr c, ExecuteOpeningSequenceCmd - ret - -AdvanceOpeningSequenceCmdPtrBy2: ; 1d42e (7:542e) - ld a, 2 - jr AdvanceOpeningSequenceCmdPtr - -AdvanceOpeningSequenceCmdPtrBy3: ; 1d432 (7:5432) - ld a, 3 - jr AdvanceOpeningSequenceCmdPtr - -AdvanceOpeningSequenceCmdPtrBy4: ; 1d436 (7:5436) - ld a, 4 -; fallthrough - -AdvanceOpeningSequenceCmdPtr: ; 1d438 (7:5438) - push hl - ld hl, wSequenceCmdPtr - add [hl] - ld [hli], a - ld a, [hl] - adc 0 - ld [hl], a - pop hl - ret - -OpeningSequenceCmd_WaitOrbsAnimation: ; 1d444 (7:5444) - ld c, $7 - ld de, wTitleScreenSprites -.loop - ld a, [de] - ld [wWhichSprite], a - farcall GetSpriteAnimCounter - cp $ff - jr nz, .no_carry - inc de - dec c - jr nz, .loop - call AdvanceOpeningSequenceCmdPtrBy2 - scf - ret - -.no_carry - or a - ret - -OpeningSequenceCmd_Wait: ; 1d460 (7:5460) - ld a, c - ld [wSequenceDelay], a - call AdvanceOpeningSequenceCmdPtrBy3 - scf - ret - -OpeningSequenceCmd_SetOrbsAnimations: ; 1d469 (7:5469) - ld l, c - ld h, b - - ld c, $7 - ld de, wTitleScreenSprites -.loop - push bc - push de - ld a, [de] - ld [wWhichSprite], a - ld a, [hli] - farcall StartSpriteAnimation - pop de - pop bc - inc de - dec c - jr nz, .loop - - call AdvanceOpeningSequenceCmdPtrBy4 - scf - ret - -OpeningSequenceCmd_SetOrbsCoordinates: ; 1d486 (7:5486) - ld l, c - ld h, b - - ld c, $7 - ld de, wTitleScreenSprites -.loop - push bc - push de - ld a, [de] - ld [wWhichSprite], a - push hl - ld c, SPRITE_ANIM_COORD_X - call GetSpriteAnimBufferProperty - ld e, l - ld d, h - pop hl - ld a, [hli] - add 8 - ld [de], a ; x - inc de - ld a, [hli] - add 16 - ld [de], a ; y - pop de - pop bc - inc de - dec c - jr nz, .loop - - call AdvanceOpeningSequenceCmdPtrBy4 - scf - ret - -OpeningOrbAnimations_CharizardScene: ; 1d4b0 (7:54b0) - db $c0 ; GRASS - db $c1 ; FIRE - db $c1 ; WATER - db $c0 ; COLORLESS - db $c1 ; LIGHTNING - db $c0 ; PSYCHIC - db $c1 ; FIGHTING - -OpeningOrbCoordinates_CharizardScene: ; 1d4b7 (7:54b7) - ; x coord, y coord - db 240, 28 ; GRASS - db 160, 120 ; FIRE - db 160, 8 ; WATER - db 240, 64 ; COLORLESS - db 160, 84 ; LIGHTNING - db 240, 100 ; PSYCHIC - db 160, 44 ; FIGHTING - -OpeningOrbAnimations_ScytherScene: ; 1d4c5 (7:54c5) - db $c1 ; GRASS - db $c0 ; FIRE - db $c0 ; WATER - db $c1 ; COLORLESS - db $c0 ; LIGHTNING - db $c1 ; PSYCHIC - db $c0 ; FIGHTING - -OpeningOrbCoordinates_ScytherScene: ; 1d4cc (7:54cc) - ; x coord, y coord - db 160, 28 ; GRASS - db 240, 120 ; FIRE - db 240, 8 ; WATER - db 160, 64 ; COLORLESS - db 240, 84 ; LIGHTNING - db 160, 100 ; PSYCHIC - db 240, 44 ; FIGHTING - -OpeningOrbAnimations_AerodactylScene: ; 1d4da (7:54da) - db $c2 ; GRASS - db $c5 ; FIRE - db $c8 ; WATER - db $cb ; COLORLESS - db $ce ; LIGHTNING - db $d1 ; PSYCHIC - db $d4 ; FIGHTING - -OpeningOrbCoordinates_AerodactylScene: ; 1d4e1 (7:54e1) - ; x coord, y coord - db 240, 32 ; GRASS - db 160, 112 ; FIRE - db 160, 16 ; WATER - db 240, 64 ; COLORLESS - db 160, 80 ; LIGHTNING - db 240, 96 ; PSYCHIC - db 160, 48 ; FIGHTING - -OpeningOrbAnimations_InitialTitleScreen: ; 1d4ef (7:54ef) - db $c3 ; GRASS - db $c6 ; FIRE - db $c9 ; WATER - db $cc ; COLORLESS - db $cf ; LIGHTNING - db $d2 ; PSYCHIC - db $d5 ; FIGHTING - -OpeningOrbCoordinates_InitialTitleScreen: ; 1d4f6 (7:54f6) - ; x coord, y coord - db 112, 144 ; GRASS - db 12, 144 ; FIRE - db 32, 144 ; WATER - db 92, 144 ; COLORLESS - db 52, 144 ; LIGHTNING - db 132, 144 ; PSYCHIC - db 72, 144 ; FIGHTING - -OpeningOrbAnimations_InTitleScreen: ; 1d504 (7:5504) - db $c4 ; GRASS - db $c7 ; FIRE - db $ca ; WATER - db $cd ; COLORLESS - db $d0 ; LIGHTNING - db $d3 ; PSYCHIC - db $d6 ; FIGHTING - -OpeningOrbCoordinates_InTitleScreen: ; 1d50b (7:550b) - ; x coord, y coord - db 112, 76 ; GRASS - db 0, 28 ; FIRE - db 32, 76 ; WATER - db 92, 252 ; COLORLESS - db 52, 252 ; LIGHTNING - db 144, 28 ; PSYCHIC - db 72, 76 ; FIGHTING - -OpeningSequenceCmd_PlayTitleScreenMusic: ; 1d519 (7:5519) - ld a, MUSIC_TITLESCREEN - call PlaySong - call AdvanceOpeningSequenceCmdPtrBy2 - scf - ret - -OpeningSequenceCmd_WaitSFX: ; 1d523 (7:5523) - call AssertSFXFinished - or a - jr nz, .no_carry - call AdvanceOpeningSequenceCmdPtrBy2 - scf - ret - -.no_carry - or a - ret - -OpeningSequenceCmd_PlaySFX: ; 1d530 (7:5530) - ld a, c - call PlaySFX - call AdvanceOpeningSequenceCmdPtrBy3 - scf - ret - -OpeningSequenceCmd_FadeIn: ; 1d539 (7:5539) - ld a, TRUE - ld [wOpeningSequencePalsNeedUpdate], a - call AdvanceOpeningSequenceCmdPtrBy2 - scf - ret - -OpeningSequenceCmd_FadeOut: ; 1d543 (7:5543) - farcall Func_10d50 - ld a, TRUE - ld [wOpeningSequencePalsNeedUpdate], a - call AdvanceOpeningSequenceCmdPtrBy2 - scf - ret - -OpeningSequenceCmd_LoadCharizardScene: ; 1d551 (7:5551) - lb bc, 6, 3 - ld a, SCENE_CHARIZARD_INTRO - jr LoadOpeningSceneAndUpdateSGBBorder - -OpeningSequenceCmd_LoadScytherScene: ; 1d558 (7:5558) - lb bc, 6, 3 - ld a, SCENE_SCYTHER_INTRO - jr LoadOpeningSceneAndUpdateSGBBorder - -OpeningSequenceCmd_LoadAerodactylScene: ; 1d55f (7:555f) - lb bc, 6, 3 - ld a, SCENE_AERODACTYL_INTRO -; fallthrough - -LoadOpeningSceneAndUpdateSGBBorder: ; 1d564 (7:5564) - call LoadOpeningScene - ld l, %001010 - lb bc, 0, 0 - lb de, 20, 18 - farcall Func_70498 - scf - ret - -OpeningSequenceCmd_LoadTitleScreenScene: ; 1d575 (7:5575) - lb bc, 0, 0 - ld a, SCENE_TITLE_SCREEN - call LoadOpeningScene - call OpeningSequenceEmptyFunc - scf - ret - -; a = scene ID -; bc = coordinates for scene -LoadOpeningScene: ; 1d582 (7:5582) - push af - push bc - call DisableLCD - pop bc - pop af - - farcall _LoadScene ; TODO change func name? - farcall Func_10d17 - - xor a - ld [wOpeningSequencePalsNeedUpdate], a - call AdvanceOpeningSequenceCmdPtrBy2 - call EnableLCD - ret - -OpeningSequenceEmptyFunc: ; 1d59c (7:559c) - ret +ExecuteOpeningSequenceCmd: ; 1d408 (7:5408) + ld a, [wSequenceDelay] + or a + jr z, .call_function + cp $ff + ret z ; sequence ended + + dec a ; still waiting + ld [wSequenceDelay], a + ret + +.call_function + ld a, [wSequenceCmdPtr + 0] + ld l, a + ld a, [wSequenceCmdPtr + 1] + ld h, a + ld a, [hli] + ld e, a + ld a, [hli] + ld d, a + ld a, [hli] + ld c, a + ld a, [hli] + ld b, a + ld l, e + ld h, d + call CallHL2 + jr c, ExecuteOpeningSequenceCmd + ret + +AdvanceOpeningSequenceCmdPtrBy2: ; 1d42e (7:542e) + ld a, 2 + jr AdvanceOpeningSequenceCmdPtr + +AdvanceOpeningSequenceCmdPtrBy3: ; 1d432 (7:5432) + ld a, 3 + jr AdvanceOpeningSequenceCmdPtr + +AdvanceOpeningSequenceCmdPtrBy4: ; 1d436 (7:5436) + ld a, 4 +; fallthrough + +AdvanceOpeningSequenceCmdPtr: ; 1d438 (7:5438) + push hl + ld hl, wSequenceCmdPtr + add [hl] + ld [hli], a + ld a, [hl] + adc 0 + ld [hl], a + pop hl + ret + +OpeningSequenceCmd_WaitOrbsAnimation: ; 1d444 (7:5444) + ld c, $7 + ld de, wTitleScreenSprites +.loop + ld a, [de] + ld [wWhichSprite], a + farcall GetSpriteAnimCounter + cp $ff + jr nz, .no_carry + inc de + dec c + jr nz, .loop + call AdvanceOpeningSequenceCmdPtrBy2 + scf + ret + +.no_carry + or a + ret + +OpeningSequenceCmd_Wait: ; 1d460 (7:5460) + ld a, c + ld [wSequenceDelay], a + call AdvanceOpeningSequenceCmdPtrBy3 + scf + ret + +OpeningSequenceCmd_SetOrbsAnimations: ; 1d469 (7:5469) + ld l, c + ld h, b + + ld c, $7 + ld de, wTitleScreenSprites +.loop + push bc + push de + ld a, [de] + ld [wWhichSprite], a + ld a, [hli] + farcall StartSpriteAnimation + pop de + pop bc + inc de + dec c + jr nz, .loop + + call AdvanceOpeningSequenceCmdPtrBy4 + scf + ret + +OpeningSequenceCmd_SetOrbsCoordinates: ; 1d486 (7:5486) + ld l, c + ld h, b + + ld c, $7 + ld de, wTitleScreenSprites +.loop + push bc + push de + ld a, [de] + ld [wWhichSprite], a + push hl + ld c, SPRITE_ANIM_COORD_X + call GetSpriteAnimBufferProperty + ld e, l + ld d, h + pop hl + ld a, [hli] + add 8 + ld [de], a ; x + inc de + ld a, [hli] + add 16 + ld [de], a ; y + pop de + pop bc + inc de + dec c + jr nz, .loop + + call AdvanceOpeningSequenceCmdPtrBy4 + scf + ret + +OpeningOrbAnimations_CharizardScene: ; 1d4b0 (7:54b0) + db $c0 ; GRASS + db $c1 ; FIRE + db $c1 ; WATER + db $c0 ; COLORLESS + db $c1 ; LIGHTNING + db $c0 ; PSYCHIC + db $c1 ; FIGHTING + +OpeningOrbCoordinates_CharizardScene: ; 1d4b7 (7:54b7) + ; x coord, y coord + db 240, 28 ; GRASS + db 160, 120 ; FIRE + db 160, 8 ; WATER + db 240, 64 ; COLORLESS + db 160, 84 ; LIGHTNING + db 240, 100 ; PSYCHIC + db 160, 44 ; FIGHTING + +OpeningOrbAnimations_ScytherScene: ; 1d4c5 (7:54c5) + db $c1 ; GRASS + db $c0 ; FIRE + db $c0 ; WATER + db $c1 ; COLORLESS + db $c0 ; LIGHTNING + db $c1 ; PSYCHIC + db $c0 ; FIGHTING + +OpeningOrbCoordinates_ScytherScene: ; 1d4cc (7:54cc) + ; x coord, y coord + db 160, 28 ; GRASS + db 240, 120 ; FIRE + db 240, 8 ; WATER + db 160, 64 ; COLORLESS + db 240, 84 ; LIGHTNING + db 160, 100 ; PSYCHIC + db 240, 44 ; FIGHTING + +OpeningOrbAnimations_AerodactylScene: ; 1d4da (7:54da) + db $c2 ; GRASS + db $c5 ; FIRE + db $c8 ; WATER + db $cb ; COLORLESS + db $ce ; LIGHTNING + db $d1 ; PSYCHIC + db $d4 ; FIGHTING + +OpeningOrbCoordinates_AerodactylScene: ; 1d4e1 (7:54e1) + ; x coord, y coord + db 240, 32 ; GRASS + db 160, 112 ; FIRE + db 160, 16 ; WATER + db 240, 64 ; COLORLESS + db 160, 80 ; LIGHTNING + db 240, 96 ; PSYCHIC + db 160, 48 ; FIGHTING + +OpeningOrbAnimations_InitialTitleScreen: ; 1d4ef (7:54ef) + db $c3 ; GRASS + db $c6 ; FIRE + db $c9 ; WATER + db $cc ; COLORLESS + db $cf ; LIGHTNING + db $d2 ; PSYCHIC + db $d5 ; FIGHTING + +OpeningOrbCoordinates_InitialTitleScreen: ; 1d4f6 (7:54f6) + ; x coord, y coord + db 112, 144 ; GRASS + db 12, 144 ; FIRE + db 32, 144 ; WATER + db 92, 144 ; COLORLESS + db 52, 144 ; LIGHTNING + db 132, 144 ; PSYCHIC + db 72, 144 ; FIGHTING + +OpeningOrbAnimations_InTitleScreen: ; 1d504 (7:5504) + db $c4 ; GRASS + db $c7 ; FIRE + db $ca ; WATER + db $cd ; COLORLESS + db $d0 ; LIGHTNING + db $d3 ; PSYCHIC + db $d6 ; FIGHTING + +OpeningOrbCoordinates_InTitleScreen: ; 1d50b (7:550b) + ; x coord, y coord + db 112, 76 ; GRASS + db 0, 28 ; FIRE + db 32, 76 ; WATER + db 92, 252 ; COLORLESS + db 52, 252 ; LIGHTNING + db 144, 28 ; PSYCHIC + db 72, 76 ; FIGHTING + +OpeningSequenceCmd_PlayTitleScreenMusic: ; 1d519 (7:5519) + ld a, MUSIC_TITLESCREEN + call PlaySong + call AdvanceOpeningSequenceCmdPtrBy2 + scf + ret + +OpeningSequenceCmd_WaitSFX: ; 1d523 (7:5523) + call AssertSFXFinished + or a + jr nz, .no_carry + call AdvanceOpeningSequenceCmdPtrBy2 + scf + ret + +.no_carry + or a + ret + +OpeningSequenceCmd_PlaySFX: ; 1d530 (7:5530) + ld a, c + call PlaySFX + call AdvanceOpeningSequenceCmdPtrBy3 + scf + ret + +OpeningSequenceCmd_FadeIn: ; 1d539 (7:5539) + ld a, TRUE + ld [wOpeningSequencePalsNeedUpdate], a + call AdvanceOpeningSequenceCmdPtrBy2 + scf + ret + +OpeningSequenceCmd_FadeOut: ; 1d543 (7:5543) + farcall Func_10d50 + ld a, TRUE + ld [wOpeningSequencePalsNeedUpdate], a + call AdvanceOpeningSequenceCmdPtrBy2 + scf + ret + +OpeningSequenceCmd_LoadCharizardScene: ; 1d551 (7:5551) + lb bc, 6, 3 + ld a, SCENE_CHARIZARD_INTRO + jr LoadOpeningSceneAndUpdateSGBBorder + +OpeningSequenceCmd_LoadScytherScene: ; 1d558 (7:5558) + lb bc, 6, 3 + ld a, SCENE_SCYTHER_INTRO + jr LoadOpeningSceneAndUpdateSGBBorder + +OpeningSequenceCmd_LoadAerodactylScene: ; 1d55f (7:555f) + lb bc, 6, 3 + ld a, SCENE_AERODACTYL_INTRO +; fallthrough + +LoadOpeningSceneAndUpdateSGBBorder: ; 1d564 (7:5564) + call LoadOpeningScene + ld l, %001010 + lb bc, 0, 0 + lb de, 20, 18 + farcall Func_70498 + scf + ret + +OpeningSequenceCmd_LoadTitleScreenScene: ; 1d575 (7:5575) + lb bc, 0, 0 + ld a, SCENE_TITLE_SCREEN + call LoadOpeningScene + call OpeningSequenceEmptyFunc + scf + ret + +; a = scene ID +; bc = coordinates for scene +LoadOpeningScene: ; 1d582 (7:5582) + push af + push bc + call DisableLCD + pop bc + pop af + + farcall _LoadScene ; TODO change func name? + farcall Func_10d17 + + xor a + ld [wOpeningSequencePalsNeedUpdate], a + call AdvanceOpeningSequenceCmdPtrBy2 + call EnableLCD + ret + +OpeningSequenceEmptyFunc: ; 1d59c (7:559c) + ret -- cgit v1.2.3