diff options
| author | ElectroDeoxys <ElectroDeoxys@gmail.com> | 2021-09-29 09:02:02 +0100 |
|---|---|---|
| committer | ElectroDeoxys <ElectroDeoxys@gmail.com> | 2021-09-29 09:02:02 +0100 |
| commit | bae47106f18266d2c3b97b6d954b91d9b16c0ccf (patch) | |
| tree | 4d1a24de37ddb1b22ff22991a275fca351a3651d /src/engine/duel | |
| parent | b0487fa979d0b5f3241e9e0659dd4b29a7e38f18 (diff) | |
Reorganise some folders in engine/
Diffstat (limited to 'src/engine/duel')
34 files changed, 29273 insertions, 0 deletions
diff --git a/src/engine/duel/ai/attacks.asm b/src/engine/duel/ai/attacks.asm new file mode 100644 index 0000000..69ae2e1 --- /dev/null +++ b/src/engine/duel/ai/attacks.asm @@ -0,0 +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, TRUE + ld [wAITriedAttack], 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/duel/ai/boss_deck_set_up.asm b/src/engine/duel/ai/boss_deck_set_up.asm new file mode 100644 index 0000000..ebcd2ea --- /dev/null +++ b/src/engine/duel/ai/boss_deck_set_up.asm @@ -0,0 +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 diff --git a/src/engine/duel/ai/common.asm b/src/engine/duel/ai/common.asm new file mode 100644 index 0000000..d4f1da4 --- /dev/null +++ b/src/engine/duel/ai/common.asm @@ -0,0 +1,970 @@ +; runs through Player's whole deck and +; sets carry if there's any Pokemon other +; than Mewtwo1. +CheckIfPlayerHasPokemonOtherThanMewtwo1: ; 227a9 (8:67a9) + call SwapTurn + ld e, 0 +.loop_deck + ld a, e + push de + call LoadCardDataToBuffer2_FromDeckIndex + pop de + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jp nc, .next ; can be a jr + ld a, [wLoadedCard2ID] + cp MEWTWO1 + jr nz, .not_mewtwo1 +.next + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop_deck + +; no carry + call SwapTurn + or a + ret + +.not_mewtwo1 + call SwapTurn + scf + ret + +; returns no carry if, given the Player is using a Mewtwo1 mill deck, +; the AI already has a Bench fully set up, in which case it +; will process some Trainer cards in hand (namely Energy Removals). +; this is used to check whether to skip some normal AI routines +; this turn and jump right to the attacking phase. +HandleAIAntiMewtwoDeckStrategy: ; 227d3 (8:67d3) +; return carry if Player is not playing Mewtwo1 mill deck + ld a, [wAIBarrierFlagCounter] + bit AI_MEWTWO_MILL_F, a + jr z, .set_carry + +; else, check if there's been less than 2 turns +; without the Player using Barrier. + cp AI_MEWTWO_MILL + 2 + jr c, .count_bench + +; if there has been, reset wAIBarrierFlagCounter +; and return carry. + xor a + ld [wAIBarrierFlagCounter], a + jr .set_carry + +; else, check number of Pokemon that are set up in Bench +; if less than 4, return carry. +.count_bench + farcall CountNumberOfSetUpBenchPokemon + cp 4 + jr c, .set_carry + +; if there's at least 4 Pokemon in the Bench set up, +; process Trainer hand cards of AI_TRAINER_CARD_PHASE_05 + ld a, AI_TRAINER_CARD_PHASE_05 + farcall AIProcessHandTrainerCards + or a + ret + +.set_carry + scf + ret + +; lists in wDuelTempList all the basic energy cards +; in card location of a. +; outputs in a number of cards found. +; returns carry if none were found. +; input: +; a = CARD_LOCATION_* to look +; output: +; a = number of cards found +FindBasicEnergyCardsInLocation: ; 227f6 (8:67f6) + ld [wTempAI], a + lb de, 0, 0 + ld hl, wDuelTempList + +; d = number of basic energy cards found +; e = current card in deck +; loop entire deck +.loop + ld a, DUELVARS_CARD_LOCATIONS + add e + push hl + call GetTurnDuelistVariable + ld hl, wTempAI + cp [hl] + pop hl + jr nz, .next_card + +; is in the card location we're looking for + ld a, e + push de + push hl + call GetCardIDFromDeckIndex + pop hl + ld a, e + pop de + cp DOUBLE_COLORLESS_ENERGY + ; only basic energy cards + ; will set carry here + jr nc, .next_card + +; is a basic energy card +; add this card to the TempList + ld a, e + ld [hli], a + inc d +.next_card + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop + +; check if any were found + ld a, d + or a + jr z, .set_carry + +; some were found, add the termination byte on TempList + ld a, $ff + ld [hl], a + ld a, d + ret + +.set_carry + scf + ret + +; returns in a the card index of energy card +; attached to Pokémon in Play Area location a, +; that is to be discarded by the AI for an effect. +; outputs $ff is none was found. +; input: +; a = PLAY_AREA_* constant of card +; output: +; a = deck index of attached energy card chosen +AIPickEnergyCardToDiscard: ; 2282e (8:682e) +; load Pokémon's attached energy cards. + ldh [hTempPlayAreaLocation_ff9d], a + call CreateArenaOrBenchEnergyCardList + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr z, .no_energy + +; load card's ID and type. + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + +; find a card that is not useful. +; if none is found, just return the first energy card attached. + ld hl, wDuelTempList +.loop + ld a, [hl] + cp $ff + jr z, .not_found + farcall CheckIfEnergyIsUseful + jr nc, .found + inc hl + jr .loop + +.found + ld a, [hl] + ret +.not_found + ld hl, wDuelTempList + ld a, [hl] + ret +.no_energy + ld a, $ff + ret + +; returns in a the deck index of an energy card attached to card +; in player's Play Area location a to remove. +; prioritizes double colorless energy, then any useful energy, +; then defaults to the first energy card attached if neither +; of those are found. +; returns $ff in a if there are no energy cards attached. +; input: +; a = Play Area location to check +; output: +; a = deck index of attached energy card +PickAttachedEnergyCardToRemove: ; 22875 (8:6875) +; construct energy list and check if there are any energy cards attached + ldh [hTempPlayAreaLocation_ff9d], a + call CreateArenaOrBenchEnergyCardList + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr z, .no_energy + +; load card data and store its type + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + +; first look for any double colorless energy + ld hl, wDuelTempList +.loop_1 + ld a, [hl] + cp $ff + jr z, .check_useful + push hl + call GetCardIDFromDeckIndex + ld a, e + cp DOUBLE_COLORLESS_ENERGY + pop hl + jr z, .found + inc hl + jr .loop_1 + +; then look for any energy cards that are useful +.check_useful + ld hl, wDuelTempList +.loop_2 + ld a, [hl] + cp $ff + jr z, .default + farcall CheckIfEnergyIsUseful + jr c, .found + inc hl + jr .loop_2 + +; return the energy card that was found +.found + ld a, [hl] + ret + +; if none were found with the above criteria, +; just return the first option +.default + ld hl, wDuelTempList + ld a, [hl] + ret + +; return $ff if no energy cards attached +.no_energy + ld a, $ff + ret + +; stores in wTempAI and wCurCardCanAttack the deck indices +; of energy cards attached to card in Play Area location a. +; prioritizes double colorless energy, then any useful energy, +; then defaults to the first two energy cards attached if neither +; of those are found. +; returns $ff in a if there are no energy cards attached. +; input: +; a = Play Area location to check +; output: +; [wTempAI] = deck index of attached energy card +; [wCurCardCanAttack] = deck index of attached energy card +PickTwoAttachedEnergyCards: ; 228d1 (8:68d1) + ldh [hTempPlayAreaLocation_ff9d], a + call CreateArenaOrBenchEnergyCardList + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + farcall CountNumberOfEnergyCardsAttached + cp 2 + jp c, .not_enough + +; load card data and store its type + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + ld a, $ff + ld [wTempAI], a + ld [wCurCardCanAttack], a + +; first look for any double colorless energy + ld hl, wDuelTempList +.loop_1 + ld a, [hl] + cp $ff + jr z, .check_useful + push hl + call GetCardIDFromDeckIndex + ld a, e + cp DOUBLE_COLORLESS_ENERGY + pop hl + jr z, .found_double_colorless + inc hl + jr .loop_1 +.found_double_colorless + ld a, [wTempAI] + cp $ff + jr nz, .already_chosen_1 + ld a, [hli] + ld [wTempAI], a + jr .loop_1 +.already_chosen_1 + ld a, [hl] + ld [wCurCardCanAttack], a + jr .done + +; then look for any energy cards that are useful +.check_useful + ld hl, wDuelTempList +.loop_2 + ld a, [hl] + cp $ff + jr z, .default + farcall CheckIfEnergyIsUseful + jr c, .found_useful + inc hl + jr .loop_2 +.found_useful + ld a, [wTempAI] + cp $ff + jr nz, .already_chosen_2 + ld a, [hli] + ld [wTempAI], a + jr .loop_2 +.already_chosen_2 + ld a, [hl] + ld [wCurCardCanAttack], a + jr .done + +; if none were found with the above criteria, +; just return the first 2 options +.default + ld hl, wDuelTempList + ld a, [wTempAI] + cp $ff + jr nz, .pick_one_card + +; pick 2 cards + ld a, [hli] + ld [wTempAI], a + ld a, [hl] + ld [wCurCardCanAttack], a + jr .done +.pick_one_card + ld a, [wTempAI] + ld b, a +.loop_3 + ld a, [hli] + cp b + jr z, .loop_3 ; already picked + ld [wCurCardCanAttack], a + +.done + ld a, [wCurCardCanAttack] + ld b, a + ld a, [wTempAI] + ret + +; return $ff if no energy cards attached +.not_enough + ld a, $ff + ret + +; copies $ff terminated buffer from hl to de +CopyBuffer: ; 2297b (8:697b) + ld a, [hli] + ld [de], a + cp $ff + ret z + inc de + jr CopyBuffer + +; zeroes a bytes starting at hl +ClearMemory_Bank8: ; 22983 (8:6983) + push af + push bc + push hl + ld b, a + xor a +.loop + ld [hli], a + dec b + jr nz, .loop + pop hl + pop bc + pop af + ret + +; counts number of energy cards found in hand +; and outputs result in a +; sets carry if none are found +; output: +; a = number of energy cards found +CountOppEnergyCardsInHand: ; 22990 (8:6990) + farcall CreateEnergyCardListFromHand + ret c + ld b, -1 + ld hl, wDuelTempList +.loop + inc b + ld a, [hli] + cp $ff + jr nz, .loop + ld a, b + or a + ret + +; converts HP in a to number of equivalent damage counters +; input: +; a = HP +; output: +; a = number of damage counters +ConvertHPToCounters: ; 229a3 (8:69a3) + push bc + ld c, 0 +.loop + sub 10 + jr c, .carry + inc c + jr .loop +.carry + ld a, c + pop bc + ret + +; calculates floor(hl / 10) +CalculateWordTensDigit: ; 229b0 (8:69b0) + push bc + push de + lb bc, $ff, -10 + lb de, $ff, -1 +.asm_229b8 + inc de + add hl, bc + jr c, .asm_229b8 + ld h, d + ld l, e + pop de + pop bc + ret + +; returns in a division of b by a +CalculateBDividedByA_Bank8: ; 229c1 (8:69c1) + push bc + ld c, a + ld a, b + ld b, c + ld c, 0 +.loop + sub b + jr c, .done + inc c + jr .loop +.done + ld a, c + pop bc + ret + +; returns in a the deck index of the first +; instance of card with ID equal to the ID in e +; in card location a. +; returns carry if found. +; input: +; a = CARD_LOCATION_* +; e = card ID to look for +LookForCardIDInLocation: ; 229d0 (8:69d0) + ld b, a + ld c, e + lb de, $00, 0 ; d is never used +.loop + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + cp b + jr nz, .next + ld a, e + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp c + jr z, .found +.next + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop + +; not found + or a + ret +.found + ld a, e + scf + ret + +; return carry if card ID loaded in a is found in hand +; and outputs in a the deck index of that card +; input: +; a = card ID +; output: +; a = card deck index, if found +; carry set if found +LookForCardIDInHandList_Bank8: ; 229f3 (8:69f3) + ld [wTempCardIDToLook], a + call CreateHandCardList + ld hl, wDuelTempList + +.loop + ld a, [hli] + cp $ff + ret z + + ldh [hTempCardIndex_ff98], a + call LoadCardDataToBuffer1_FromDeckIndex + ld b, a + ld a, [wTempCardIDToLook] + cp b + jr nz, .loop + + ldh a, [hTempCardIndex_ff98] + scf + ret + +; searches in deck for card ID 1 in a, and +; if found, searches in Hand/Play Area for card ID 2 in b, and +; if found, searches for card ID 1 in Hand/Play Area, and +; if none found, return carry and output deck index +; of the card ID 1 in deck. +; input: +; a = card ID 1 +; b = card ID 2 +; output: +; a = index of card ID 1 in deck +LookForCardIDInDeck_GivenCardIDInHandAndPlayArea: ; 22a10 (8:6a10) +; store a in wCurCardCanAttack +; and b in wTempAI + ld c, a + ld a, b + ld [wTempAI], a + ld a, c + ld [wCurCardCanAttack], a + +; look for the card ID 1 in deck + ld e, a + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret nc + +; was found, store its deck index in memory + ld [wTempAIPokemonCard], a + +; look for the card ID 2 +; in Hand and Play Area, return if not found. + ld a, [wTempAI] + call LookForCardIDInHandAndPlayArea + ret nc + +; look for the card ID 1 in the Hand and Play Area +; if any card is found, return no carry. + ld a, [wCurCardCanAttack] + call LookForCardIDInHandAndPlayArea + jr c, .no_carry +; none found + + ld a, [wTempAIPokemonCard] + scf + ret + +.no_carry + or a + ret + +; returns carry if card ID in a +; is found in Play Area or in hand +; input: +; a = card ID +LookForCardIDInHandAndPlayArea: ; 22a39 (8:6a39) + ld b, a + push bc + call LookForCardIDInHandList_Bank8 + pop bc + ret c + + ld a, b + ld b, PLAY_AREA_ARENA + call LookForCardIDInPlayArea_Bank8 + ret c + or a + ret + +; searches in deck for card ID 1 in a, and +; if found, searches in Hand Area for card ID 2 in b, and +; if found, searches for card ID 1 in Hand/Play Area, and +; if none found, return carry and output deck index +; of the card ID 1 in deck. +; input: +; a = card ID 1 +; b = card ID 2 +; output: +; a = index of card ID 1 in deck +LookForCardIDInDeck_GivenCardIDInHand: ; 22a49 (8:6a49) +; store a in wCurCardCanAttack +; and b in wTempAI + ld c, a + ld a, b + ld [wTempAI], a + ld a, c + ld [wCurCardCanAttack], a + +; look for the card ID 1 in deck + ld e, a + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret nc + +; was found, store its deck index in memory + ld [wTempAIPokemonCard], a + +; look for the card ID 2 in hand, return if not found. + ld a, [wTempAI] + call LookForCardIDInHandList_Bank8 + ret nc + +; look for the card ID 1 in the Hand and Play Area +; if any card is found, return no carry. + ld a, [wCurCardCanAttack] + call LookForCardIDInHandAndPlayArea + jr c, .no_carry +; none found + + ld a, [wTempAIPokemonCard] + scf + ret + +.no_carry + or a + ret + +; returns carry if card ID in a +; is found in Play Area, starting with +; location in b +; input: +; a = card ID +; b = PLAY_AREA_* to start with +; output: +; a = PLAY_AREA_* of found card +; carry set if found +LookForCardIDInPlayArea_Bank8: ; 22a72 (8:6a72) + ld [wTempCardIDToLook], a +.loop + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + cp $ff + ret z + + call LoadCardDataToBuffer1_FromDeckIndex + ld c, a + ld a, [wTempCardIDToLook] + cp c + jr z, .is_same + + inc b + ld a, MAX_PLAY_AREA_POKEMON + cp b + jr nz, .loop + ld b, $ff + or a + ret + +.is_same + ld a, b + scf + ret + +; runs through list avoiding card in e. +; removes first card in list not equal to e +; and that has a type allowed to be removed, in d. +; returns carry if successful in finding a card. +; input: +; d = type of card allowed to be removed +; ($00 = Trainer, $01 = Pokemon, $02 = Energy) +; e = card deck index to avoid removing +; output: +; a = card index of removed card +RemoveFromListDifferentCardOfGivenType: ; 22a95 (8:6a95) + push hl + push de + push bc + call CountCardsInDuelTempList + call ShuffleCards + +; loop list until a card with +; deck index different from e is found. +.loop_list + ld a, [hli] + cp $ff + jr z, .no_carry + cp e + jr z, .loop_list + +; get this card's type + ldh [hTempCardIndex_ff98], a + push de + call GetCardIDFromDeckIndex + call GetCardType + pop de + cp TYPE_ENERGY + jr c, .pkmn_card + cp TYPE_TRAINER + jr nz, .energy + +; only remove from list specific type. + +; trainer + ld a, d + or a + jr nz, .loop_list + jr .remove_card +.energy + ld a, d + cp $02 + jr nz, .loop_list + jr .remove_card +.pkmn_card + ld a, d + cp $01 + jr nz, .loop_list + ; fallthrough + +.remove_card + ld d, h + ld e, l + dec hl +.loop_remove + ld a, [de] + inc de + ld [hli], a + cp $ff + jr nz, .loop_remove + +; success + ldh a, [hTempCardIndex_ff98] + pop bc + pop de + pop hl + scf + ret +.no_carry + pop bc + pop de + pop hl + or a + ret + +; used in Pokemon Trader checks to look for a specific +; card in the deck to trade with a card in hand that +; has a card ID different from e. +; returns carry if successful. +; input: +; a = card ID 1 +; e = card ID 2 +; output: +; a = deck index of card ID 1 found in deck +; e = deck index of Pokemon card in hand different than card ID 2 +LookForCardIDToTradeWithDifferentHandCard: ; 22ae0 (8:6ae0) + ld hl, wCurCardCanAttack + ld [hl], e + ld [wTempAI], a + +; if card ID 1 is in hand, return no carry. + call LookForCardIDInHandList_Bank8 + jr c, .no_carry + +; if card ID 1 is not in deck, return no carry. + ld a, [wTempAI] + ld e, a + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jr nc, .no_carry + +; store its deck index + ld [wTempAI], a + +; look in hand for Pokemon card ID that +; is different from card ID 2. + ld a, [wCurCardCanAttack] + ld c, a + call CreateHandCardList + ld hl, wDuelTempList + +.loop_hand + ld a, [hli] + cp $ff + jr z, .no_carry + ld b, a + call LoadCardDataToBuffer1_FromDeckIndex + cp c + jr z, .loop_hand + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr nc, .loop_hand + +; found, output deck index of card ID 1 in deck +; and deck index of card found in hand, and set carry + ld e, b + ld a, [wTempAI] + scf + ret + +.no_carry + or a + ret + +; returns carry if at least one card in the hand +; has the card ID of input. Outputs its index. +; input: +; a = card ID to look for +; output: +; a = deck index of card in hand found +CheckIfHasCardIDInHand: ; 22b1f (8:6b1f) + ld [wTempCardIDToLook], a + call CreateHandCardList + ld hl, wDuelTempList + ld c, 0 + +.loop_hand + ld a, [hli] + cp $ff + ret z + ldh [hTempCardIndex_ff98], a + call LoadCardDataToBuffer1_FromDeckIndex + ld b, a + ld a, [wTempCardIDToLook] + cp b + jr nz, .loop_hand + ld a, c + or a + jr nz, .set_carry + inc c + jr nz, .loop_hand + +.set_carry + ldh a, [hTempCardIndex_ff98] + scf + ret + +; outputs in a total number of Pokemon cards in hand +; plus Pokemon in Turn Duelist's Play Area. +CountPokemonCardsInHandAndInPlayArea: ; 22b45 (8:6b45) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld [wTempAI], a + call CreateHandCardList + ld hl, wDuelTempList +.loop_hand + ld a, [hli] + cp $ff + jr z, .done + call GetCardIDFromDeckIndex + call GetCardType + cp TYPE_ENERGY + jr nc, .loop_hand + ld a, [wTempAI] + inc a + ld [wTempAI], a + jr .loop_hand +.done + ld a, [wTempAI] + ret + +; returns carry if a duplicate Pokemon card is found in hand. +; outputs in a the deck index of one of them. +FindDuplicatePokemonCards: ; 22b6f (8:6b6f) + ld a, $ff + ld [wTempAI], a + call CreateHandCardList + ld hl, wDuelTempList + push hl + +.loop_hand_outer + pop hl + ld a, [hli] + cp $ff + jr z, .done + call GetCardIDFromDeckIndex + ld b, e + push hl + +.loop_hand_inner + ld a, [hli] + cp $ff + jr z, .loop_hand_outer + ld c, a + call GetCardIDFromDeckIndex + ld a, e + cp b + jr nz, .loop_hand_inner + +; found two cards with same ID, +; if they are Pokemon cards, store its deck index. + push bc + call GetCardType + pop bc + cp TYPE_ENERGY + jr nc, .loop_hand_outer + ld a, c + ld [wTempAI], a + ; for some reason loop still continues + ; even though if some other duplicate + ; cards are found, it overwrites the result. + jr .loop_hand_outer + +.done + ld a, [wTempAI] + cp $ff + jr z, .no_carry + +; found + scf + ret +.no_carry + or a + ret + +; return carry flag if attack is not high recoil. +AICheckIfAttackIsHighRecoil: ; 22bad (8:6bad) + farcall AIProcessButDontUseAttack + ret nc + ld a, [wSelectedAttack] + ld e, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F + call CheckLoadedAttackFlag + ccf + ret diff --git a/src/engine/duel/ai/core.asm b/src/engine/duel/ai/core.asm new file mode 100644 index 0000000..9604322 --- /dev/null +++ b/src/engine/duel/ai/core.asm @@ -0,0 +1,2770 @@ +INCLUDE "engine/duel/ai/decks/unreferenced.asm" + +; returns carry if damage dealt from any of +; a card's attacks KOs defending Pokémon +; outputs index of the attack that KOs +; input: +; [hTempPlayAreaLocation_ff9d] = location of attacking card to consider +; output: +; [wSelectedAttack] = attack index that KOs +CheckIfAnyAttackKnocksOutDefendingCard: ; 140ae (5:40ae) + xor a ; first attack + call CheckIfAttackKnocksOutDefendingCard + ret c + ld a, SECOND_ATTACK +; fallthrough + +CheckIfAttackKnocksOutDefendingCard: ; 140b5 (5:40b5) + call EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld hl, wDamage + sub [hl] + ret c + ret nz + scf + ret + +; returns carry if any of the defending Pokémon's attacks +; brings card at hTempPlayAreaLocation_ff9d down +; to exactly 0 HP. +; outputs that attack index in wSelectedAttack. +CheckIfAnyDefendingPokemonAttackDealsSameDamageAsHP: ; 140c5 (5:40c5) + xor a ; FIRST_ATTACK_OR_PKMN_POWER + call .check_damage + ret c + ld a, SECOND_ATTACK + +.check_damage + call EstimateDamage_FromDefendingPokemon + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld hl, wDamage + sub [hl] + jr z, .true + ret +.true + scf + ret + +; checks AI scores for all benched Pokémon +; returns the location of the card with highest score +; in a and [hTempPlayAreaLocation_ff9d] +FindHighestBenchScore: ; 140df (5:40df) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, 0 + ld e, c + ld d, c + ld hl, wPlayAreaAIScore + 1 + jp .next + +.loop + ld a, [hli] + cp e + jr c, .next + ld e, a + ld d, c +.next + inc c + dec b + jr nz, .loop + + ld a, d + ldh [hTempPlayAreaLocation_ff9d], a + or a + ret + +; adds a to wAIScore +; if there's overflow, it's capped at $ff +; output: +; a = a + wAIScore (capped at $ff) +AddToAIScore: ; 140fe (5:40fe) + push hl + ld hl, wAIScore + add [hl] + jr nc, .no_cap + ld a, $ff +.no_cap + ld [hl], a + pop hl + ret + +; subs a from wAIScore +; if there's underflow, it's capped at $00 +SubFromAIScore: ; 1410a (5:410a) + push hl + push de + ld e, a + ld hl, wAIScore + ld a, [hl] + or a + jr z, .done + sub e + ld [hl], a + jr nc, .done + ld [hl], $00 +.done + pop de + pop hl + ret + +; loads defending Pokémon's weakness/resistance +; and the number of prize cards in both sides +LoadDefendingPokemonColorWRAndPrizeCards: ; 1411d (5:411d) + call SwapTurn + call GetArenaCardColor + call TranslateColorToWR + ld [wAIPlayerColor], a + call GetArenaCardWeakness + ld [wAIPlayerWeakness], a + call GetArenaCardResistance + ld [wAIPlayerResistance], a + call CountPrizes + ld [wAIPlayerPrizeCount], a + call SwapTurn + call CountPrizes + ld [wAIOpponentPrizeCount], a + ret + +; called when AI has chosen its attack. +; executes all effects and damage. +; handles AI choosing parameters for certain attacks as well. +AITryUseAttack: ; 14145 (5:4145) + ld a, [wSelectedAttack] + ldh [hTemp_ffa0], a + ld e, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff9f], a + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + ld a, OPPACTION_BEGIN_ATTACK + bank1call AIMakeDecision + ret c + + call AISelectSpecialAttackParameters + jr c, .use_attack + ld a, EFFECTCMDTYPE_AI_SELECTION + call TryExecuteEffectCommandFunction + +.use_attack + ld a, [wSelectedAttack] + ld e, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + ld a, OPPACTION_USE_ATTACK + bank1call AIMakeDecision + ret c + + ld a, EFFECTCMDTYPE_AI_SWITCH_DEFENDING_PKMN + call TryExecuteEffectCommandFunction + ld a, OPPACTION_ATTACK_ANIM_AND_DAMAGE + bank1call AIMakeDecision + ret + +; return carry if any of the following is satisfied: +; - deck index in a corresponds to a double colorless energy card; +; - card type in wTempCardType is colorless; +; - card ID in wTempCardID is a Pokémon card that has +; attacks that require energy other than its color and +; the deck index in a corresponds to that energy type; +; - card ID is Eevee and a corresponds to an energy type +; of water, fire or lightning; +; - type of card in register a is the same as wTempCardType. +; used for knowing if a given energy card can be discarded +; from a given Pokémon card +; input: +; a = energy card attached to Pokémon to check +; [wTempCardType] = TYPE_ENERGY_* of given Pokémon +; [wTempCardID] = card index of Pokémon card to check +CheckIfEnergyIsUseful: ; 14184 (5:4184) + push de + call GetCardIDFromDeckIndex + ld a, e + cp DOUBLE_COLORLESS_ENERGY + jr z, .set_carry + ld a, [wTempCardType] + cp TYPE_ENERGY_DOUBLE_COLORLESS + jr z, .set_carry + ld a, [wTempCardID] + + ld d, PSYCHIC_ENERGY + cp EXEGGCUTE + jr z, .check_energy + cp EXEGGUTOR + jr z, .check_energy + cp PSYDUCK + jr z, .check_energy + cp GOLDUCK + jr z, .check_energy + + ld d, WATER_ENERGY + cp SURFING_PIKACHU1 + jr z, .check_energy + cp SURFING_PIKACHU2 + jr z, .check_energy + + cp EEVEE + jr nz, .check_type + ld a, e + cp WATER_ENERGY + jr z, .set_carry + cp FIRE_ENERGY + jr z, .set_carry + cp LIGHTNING_ENERGY + jr z, .set_carry + +.check_type + ld d, $00 ; unnecessary? + call GetCardType + ld d, a + ld a, [wTempCardType] + cp d + jr z, .set_carry + pop de + or a + ret + +.check_energy + ld a, d + cp e + jr nz, .check_type +.set_carry + pop de + scf + ret + +; pick a random Pokemon in the bench. +; output: +; - a = PLAY_AREA_* of Bench Pokemon picked. +PickRandomBenchPokemon: ; 141da (5:41da) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + call Random + inc a + ret + +AIPickPrizeCards: ; 141e5 (5:41e5) + ld a, [wNumberPrizeCardsToTake] + ld b, a +.loop + call .PickPrizeCard + ld a, DUELVARS_PRIZES + call GetTurnDuelistVariable + or a + jr z, .done + dec b + jr nz, .loop +.done + ret + +; picks a prize card at random +; and adds it to the hand. +.PickPrizeCard: ; 141f8 (5:41f8) + ld a, DUELVARS_PRIZES + call GetTurnDuelistVariable + push hl + ld c, a + +; choose a random prize card until +; one is found that isn't taken already. +.loop_pick_prize + ld a, 6 + call Random + ld e, a + ld d, $00 + ld hl, .prize_flags + add hl, de + ld a, [hl] + and c + jr z, .loop_pick_prize ; no prize + +; prize card was found +; remove this prize from wOpponentPrizes + ld a, [hl] + pop hl + cpl + and [hl] + ld [hl], a + +; add this prize card to the hand + ld a, e + add DUELVARS_PRIZE_CARDS + call GetTurnDuelistVariable + call AddCardToHand + ret + +.prize_flags ; 1421e (5:421e) + db $1 << 0 + db $1 << 1 + db $1 << 2 + db $1 << 3 + db $1 << 4 + db $1 << 5 + db $1 << 6 + db $1 << 7 + +; routine for AI to play all Basic cards from its hand +; in the beginning of the Duel. +AIPlayInitialBasicCards: ; 14226 (5:4226) + call CreateHandCardList + ld hl, wDuelTempList +.check_for_next_card + ld a, [hli] + ldh [hTempCardIndex_ff98], a + cp $ff + ret z ; return when done + + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr nc, .check_for_next_card ; skip if not Pokemon card + ld a, [wLoadedCard1Stage] + or a + jr nz, .check_for_next_card ; skip if not Basic Stage + +; play Basic card from hand + push hl + ldh a, [hTempCardIndex_ff98] + call PutHandPokemonCardInPlayArea + pop hl + jr .check_for_next_card + +; returns carry if Pokémon at hTempPlayAreaLocation_ff9d +; can't use an attack or if that selected attack doesn't have enough energy +; input: +; [hTempPlayAreaLocation_ff9d] = location of Pokémon card +; [wSelectedAttack] = selected attack to examine +CheckIfSelectedAttackIsUnusable: ; 1424b (5:424b) + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .bench + + bank1call HandleCantAttackSubstatus + ret c + bank1call CheckIfActiveCardParalyzedOrAsleep + ret c + + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + ld a, [wSelectedAttack] + ld e, a + call CopyAttackDataAndDamage_FromDeckIndex + call HandleAmnesiaSubstatus + ret c + ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1 + call TryExecuteEffectCommandFunction + ret c + +.bench + call CheckEnergyNeededForAttack + ret c ; can't be used + ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_5_F + call CheckLoadedAttackFlag + ret + +; load selected attack from Pokémon in hTempPlayAreaLocation_ff9d +; and checks if there is enough energy to execute the selected attack +; input: +; [hTempPlayAreaLocation_ff9d] = location of Pokémon card +; [wSelectedAttack] = selected attack to examine +; output: +; b = basic energy still needed +; c = colorless energy still needed +; e = output of ConvertColorToEnergyCardID, or $0 if not an attack +; carry set if no attack +; OR if it's a Pokémon Power +; OR if not enough energy for attack +CheckEnergyNeededForAttack: ; 14279 (5:4279) + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + ld a, [wSelectedAttack] + ld e, a + call CopyAttackDataAndDamage_FromDeckIndex + ld hl, wLoadedAttackName + ld a, [hli] + or [hl] + jr z, .no_attack + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr nz, .is_attack +.no_attack + lb bc, 0, 0 + ld e, c + scf + ret + +.is_attack + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + bank1call HandleEnergyBurn + + xor a + ld [wTempLoadedAttackEnergyCost], a + ld [wTempLoadedAttackEnergyNeededAmount], a + ld [wTempLoadedAttackEnergyNeededType], a + + ld hl, wAttachedEnergies + ld de, wLoadedAttackEnergyCost + ld b, 0 + ld c, (NUM_TYPES / 2) - 1 + +.loop + ; check all basic energy cards except colorless + ld a, [de] + swap a + call CheckIfEnoughParticularAttachedEnergy + ld a, [de] + call CheckIfEnoughParticularAttachedEnergy + inc de + dec c + jr nz, .loop + +; running CheckIfEnoughParticularAttachedEnergy back to back like this +; overwrites the results of a previous call of this function, +; however, no attack in the game has energy requirements for two +; different energy types (excluding colorless), so this routine +; will always just return the result for one type of basic energy, +; while all others will necessarily have an energy cost of 0 +; if attacks are added to the game with energy requirements of +; two different basic energy types, then this routine only accounts +; for the type with the highest index + + ; colorless + ld a, [de] + swap a + and %00001111 + ld b, a ; colorless energy still needed + ld a, [wTempLoadedAttackEnergyCost] + ld hl, wTempLoadedAttackEnergyNeededAmount + sub [hl] + ld c, a ; basic energy still needed + ld a, [wTotalAttachedEnergies] + sub c + sub b + jr c, .not_enough + + ld a, [wTempLoadedAttackEnergyNeededAmount] + or a + ret z + +; being here means the energy cost isn't satisfied, +; including with colorless energy + xor a +.not_enough + cpl + inc a + ld c, a ; colorless energy still needed + ld a, [wTempLoadedAttackEnergyNeededAmount] + ld b, a ; basic energy still needed + ld a, [wTempLoadedAttackEnergyNeededType] + call ConvertColorToEnergyCardID + ld e, a + ld d, 0 + scf + ret + +; takes as input the energy cost of an attack for a +; particular energy, stored in the lower nibble of a +; if the attack costs some amount of this energy, the lower nibble of a != 0, +; and this amount is stored in wTempLoadedAttackEnergyCost +; sets carry flag if not enough energy of this type attached +; input: +; a = this energy cost of attack (lower nibble) +; [hl] = attached energy +; output: +; carry set if not enough of this energy type attached +CheckIfEnoughParticularAttachedEnergy: ; 142f4 (5:42f4) + and %00001111 + jr nz, .check +.has_enough + inc hl + inc b + or a + ret + +.check + ld [wTempLoadedAttackEnergyCost], a + sub [hl] + jr z, .has_enough + jr c, .has_enough + + ; not enough energy + ld [wTempLoadedAttackEnergyNeededAmount], a + ld a, b + ld [wTempLoadedAttackEnergyNeededType], a + inc hl + inc b + scf + ret + +; input: +; a = energy type +; output: +; a = energy card ID +ConvertColorToEnergyCardID: ; 1430f (5:430f) + push hl + push de + ld e, a + ld d, 0 + ld hl, .card_id + add hl, de + ld a, [hl] + pop de + pop hl + ret + +.card_id + db FIRE_ENERGY + db GRASS_ENERGY + db LIGHTNING_ENERGY + db WATER_ENERGY + db FIGHTING_ENERGY + db PSYCHIC_ENERGY + db DOUBLE_COLORLESS_ENERGY + +; returns carry if loaded attack effect has +; an "initial effect 2" or "require selection" command type +; unreferenced +Func_14323: ; 14323 (5:4323) + ld hl, wLoadedAttackEffectCommands + ld a, [hli] + ld h, [hl] + ld l, a + ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2 + push hl + call CheckMatchingCommand + pop hl + jr nc, .set_carry + ld a, EFFECTCMDTYPE_REQUIRE_SELECTION + call CheckMatchingCommand + jr nc, .set_carry + or a + ret +.set_carry + scf + ret + +; return carry depending on card index in a: +; - if energy card, return carry if no energy card has been played yet +; - if basic Pokémon card, return carry if there's space in bench +; - if evolution card, return carry if there's a Pokémon +; in Play Area it can evolve +; - if trainer card, return carry if it can be used +; input: +; a = card index to check +CheckIfCardCanBePlayed: ; 1433d (5:433d) + ldh [hTempCardIndex_ff9f], a + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr c, .pokemon_card + cp TYPE_TRAINER + jr z, .trainer_card + +; energy card + ld a, [wAlreadyPlayedEnergy] + or a + ret z + scf + ret + +.pokemon_card + ld a, [wLoadedCard1Stage] + or a + jr nz, .evolution_card + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +.evolution_card + bank1call IsPrehistoricPowerActive + ret c + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld b, 0 +.loop + push bc + ld e, b + ldh a, [hTempCardIndex_ff9f] + ld d, a + call CheckIfCanEvolveInto + pop bc + ret nc + inc b + dec c + jr nz, .loop + scf + ret + +.trainer_card + bank1call CheckCantUseTrainerDueToHeadache + ret c + call LoadNonPokemonCardEffectCommands + ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1 + call TryExecuteEffectCommandFunction + ret + +; loads all the energy cards +; in hand in wDuelTempList +; return carry if no energy cards found +CreateEnergyCardListFromHand: ; 1438c (5:438c) + push hl + push de + push bc + ld de, wDuelTempList + ld b, a + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ld c, a + inc c + ld l, LOW(wOpponentHand) + jr .decrease + +.loop + ld a, [hli] + push de + call GetCardIDFromDeckIndex + call GetCardType + pop de + and TYPE_ENERGY + jr z, .decrease + dec hl + ld a, [hli] + ld [de], a + inc de +.decrease + dec c + jr nz, .loop + + ld a, $ff + ld [de], a + pop bc + pop de + pop hl + ld a, [wDuelTempList] + cp $ff + ccf + ret + +; looks for card ID in hand and +; sets carry if a card wasn't found +; as opposed to LookForCardIDInHandList_Bank5 +; this function doesn't create a list +; and preserves hl, de and bc +; input: +; a = card ID +; output: +; a = card deck index, if found +; carry set if NOT found +LookForCardIDInHand: ; 143bf (5:43bf) + push hl + push de + push bc + ld b, a + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ld c, a + inc c + ld l, DUELVARS_HAND + jr .next + +.loop + ld a, [hli] + call GetCardIDFromDeckIndex + ld a, e + cp b + jr z, .no_carry +.next + dec c + jr nz, .loop + + pop bc + pop de + pop hl + scf + ret + +.no_carry + dec hl + ld a, [hl] + pop bc + pop de + pop hl + or a + ret + +INCLUDE "engine/duel/ai/damage_calculation.asm" + +AIProcessHandTrainerCards: ; 14663 (5:4663) + farcall _AIProcessHandTrainerCards + ret + +INCLUDE "engine/duel/ai/deck_ai.asm" + +; return carry if card ID loaded in a is found in hand +; and outputs in a the deck index of that card +; as opposed to LookForCardIDInHand, this function +; creates a list in wDuelTempList +; input: +; a = card ID +; output: +; a = card deck index, if found +; carry set if found +LookForCardIDInHandList_Bank5: ; 155d2 (5:55d2) + ld [wTempCardIDToLook], a + call CreateHandCardList + ld hl, wDuelTempList + +.loop + ld a, [hli] + cp $ff + ret z + ldh [hTempCardIndex_ff98], a + call LoadCardDataToBuffer1_FromDeckIndex + ld b, a + ld a, [wTempCardIDToLook] + cp b + jr nz, .loop + + ldh a, [hTempCardIndex_ff98] + scf + ret + +; returns carry if card ID in a +; is found in Play Area, starting with +; location in b +; input: +; a = card ID +; b = PLAY_AREA_* to start with +; output: +; a = PLAY_AREA_* of found card +; carry set if found +LookForCardIDInPlayArea_Bank5: ; 155ef (5:55ef) + ld [wTempCardIDToLook], a + +.loop + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + cp $ff + ret z + call LoadCardDataToBuffer1_FromDeckIndex + ld c, a + ld a, [wTempCardIDToLook] + cp c + jr z, .found + inc b + ld a, MAX_PLAY_AREA_POKEMON + cp b + jr nz, .loop + + ld b, $ff + or a + ret +.found + ld a, b + scf + ret + +; check if energy card ID in e is in AI hand and, +; if so, attaches it to card ID in d in Play Area. +; input: +; e = Energy card ID +; d = Pokemon card ID +AIAttachEnergyInHandToCardInPlayArea: ; 15612 (5:5612) + ld a, e + push de + call LookForCardIDInHandList_Bank5 + pop de + ret nc ; not in hand + ld b, PLAY_AREA_ARENA + +.attach + ld e, a + ld a, d + call LookForCardIDInPlayArea_Bank5 + ldh [hTempPlayAreaLocation_ffa1], a + ld a, e + ldh [hTemp_ffa0], a + ld a, OPPACTION_PLAY_ENERGY + bank1call AIMakeDecision + ret + +; same as AIAttachEnergyInHandToCardInPlayArea but +; only look for card ID in the Bench. +AIAttachEnergyInHandToCardInBench: ; 1562b (5:562b) + ld a, e + push de + call LookForCardIDInHandList_Bank5 + pop de + ret nc + ld b, PLAY_AREA_BENCH_1 + jr AIAttachEnergyInHandToCardInPlayArea.attach + +INCLUDE "engine/duel/ai/init.asm" + +; load selected attack from Pokémon in hTempPlayAreaLocation_ff9d, +; gets an energy card to discard and subsequently +; check if there is enough energy to execute the selected attack +; after removing that attached energy card. +; input: +; [hTempPlayAreaLocation_ff9d] = location of Pokémon card +; [wSelectedAttack] = selected attack to examine +; output: +; b = basic energy still needed +; c = colorless energy still needed +; e = output of ConvertColorToEnergyCardID, or $0 if not an attack +; carry set if no attack +; OR if it's a Pokémon Power +; OR if not enough energy for attack +CheckEnergyNeededForAttackAfterDiscard: ; 156c3 (5:56c3) + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + ld a, [wSelectedAttack] + ld e, a + call CopyAttackDataAndDamage_FromDeckIndex + ld hl, wLoadedAttackName + ld a, [hli] + or [hl] + jr z, .no_attack + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr nz, .is_attack +.no_attack + lb bc, 0, 0 + ld e, c + scf + ret + +.is_attack + ldh a, [hTempPlayAreaLocation_ff9d] + farcall AIPickEnergyCardToDiscard + call LoadCardDataToBuffer1_FromDeckIndex + cp DOUBLE_COLORLESS_ENERGY + jr z, .colorless + +; color energy +; decrease respective attached energy by 1. + ld hl, wAttachedEnergies + dec a + ld c, a + ld b, $00 + add hl, bc + dec [hl] + ld hl, wTotalAttachedEnergies + dec [hl] + jr .asm_1570c +; decrease attached colorless by 2. +.colorless + ld hl, wAttachedEnergies + COLORLESS + dec [hl] + dec [hl] + ld hl, wTotalAttachedEnergies + dec [hl] + dec [hl] + +.asm_1570c + bank1call HandleEnergyBurn + xor a + ld [wTempLoadedAttackEnergyCost], a + ld [wTempLoadedAttackEnergyNeededAmount], a + ld [wTempLoadedAttackEnergyNeededType], a + ld hl, wAttachedEnergies + ld de, wLoadedAttackEnergyCost + ld b, 0 + ld c, (NUM_TYPES / 2) - 1 +.loop + ; check all basic energy cards except colorless + ld a, [de] + swap a + call CheckIfEnoughParticularAttachedEnergy + ld a, [de] + call CheckIfEnoughParticularAttachedEnergy + inc de + dec c + jr nz, .loop + + ld a, [de] + swap a + and $0f + ld b, a ; colorless energy still needed + ld a, [wTempLoadedAttackEnergyCost] + ld hl, wTempLoadedAttackEnergyNeededAmount + sub [hl] + ld c, a ; basic energy still needed + ld a, [wTotalAttachedEnergies] + sub c + sub b + jr c, .not_enough_energy + + ld a, [wTempLoadedAttackEnergyNeededAmount] + or a + ret z + +; being here means the energy cost isn't satisfied, +; including with colorless energy + xor a +.not_enough_energy + cpl + inc a + ld c, a ; colorless energy still needed + ld a, [wTempLoadedAttackEnergyNeededAmount] + ld b, a ; basic energy still needed + ld a, [wTempLoadedAttackEnergyNeededType] + call ConvertColorToEnergyCardID + ld e, a + ld d, 0 + scf + ret + +; zeroes a bytes starting at hl +ClearMemory_Bank5: ; 1575e (5:575e) + push af + push bc + push hl + ld b, a + xor a +.clear_loop + ld [hli], a + dec b + jr nz, .clear_loop + pop hl + pop bc + pop af + ret + +; returns in a the tens digit of value in a +CalculateByteTensDigit: ; 1576b (5:576b) + push bc + ld c, 0 +.loop + sub 10 + jr c, .done + inc c + jr .loop +.done + ld a, c + pop bc + ret + +; returns in a the result of +; dividing b by a, rounded down +; input: +; a = divisor +; b = dividend +CalculateBDividedByA_Bank5: ; 15778 (5:5778) + push bc + ld c, a + ld a, b + ld b, c + ld c, 0 +.loop + sub b + jr c, .done + inc c + jr .loop +.done + ld a, c + pop bc + ret + +; returns in a the number of energy cards attached +; to Pokémon in location held by e +; this assumes that colorless are paired so +; that one colorless energy card provides 2 colorless energy +; input: +; e = location to check, i.e. PLAY_AREA_* +; output: +; a = number of energy cards attached +CountNumberOfEnergyCardsAttached: ; 15787 (5:5787) + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + ret z + + xor a + push hl + push bc + ld b, NUM_COLORED_TYPES + ld hl, wAttachedEnergies +; sum all the attached energies +.loop + add [hl] + inc hl + dec b + jr nz, .loop + + ld b, [hl] + srl b +; counts colorless ad halves it + add b + pop bc + pop hl + ret + +; returns carry if any card with ID in e is found +; in card location in a +; input: +; a = card location to look in; +; e = card ID to look for. +; output: +; a = deck index of card found, if any +CheckIfAnyCardIDinLocation: ; 157a3 (5:57a3) + ld b, a + ld c, e + lb de, 0, 0 +.loop + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + cp b + jr nz, .next + ld a, e + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp c + jr z, .set_carry +.next + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop + or a + ret +.set_carry + ld a, e + scf + ret + +; counts total number of energy cards in opponent's hand +; plus all the cards attached in Turn Duelist's Play Area. +; output: +; a and wTempAI = total number of energy cards. +CountOppEnergyCardsInHandAndAttached: ; 157c6 (5:57c6) + xor a + ld [wTempAI], a + call CreateEnergyCardListFromHand + jr c, .attached + +; counts number of energy cards in hand + ld b, -1 + ld hl, wDuelTempList +.loop_hand + inc b + ld a, [hli] + cp $ff + jr nz, .loop_hand + ld a, b + ld [wTempAI], a + +; counts number of energy cards +; that are attached in Play Area +.attached + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA +.loop_play_area + call CountNumberOfEnergyCardsAttached + ld hl, wTempAI + add [hl] + ld [hl], a + inc e + dec d + jr nz, .loop_play_area + ret + +; returns carry if any card with ID in e is found +; in the list that is pointed by hl. +; if one is found, it is removed from the list. +; input: +; e = card ID to look for. +; hl = list to look in +RemoveCardIDInList: ; 157f3 (5:57f3) + push hl + push de + push bc + ld c, e + +.loop_1 + ld a, [hli] + cp $ff + jr z, .no_carry + + ldh [hTempCardIndex_ff98], a + call GetCardIDFromDeckIndex + ld a, c + cp e + jr nz, .loop_1 + +; found + ld d, h + ld e, l + dec hl + +; remove this index from the list +; and reposition the rest of the list ahead. +.loop_2 + ld a, [de] + inc de + ld [hli], a + cp $ff + jr nz, .loop_2 + + ldh a, [hTempCardIndex_ff98] + pop bc + pop de + pop hl + scf + ret + +.no_carry + pop bc + pop de + pop hl + or a + ret + +; play Pokemon cards from the hand to set the starting +; Play Area of Boss decks. +; each Boss deck has two ID lists in order of preference. +; one list is for the Arena card is the other is for the Bench cards. +; if Arena card could not be set (due to hand not having any card in its list) +; or if list is null, return carry and do not play any cards. +TrySetUpBossStartingPlayArea: ; 1581b (5:581b) + ld de, wAICardListArenaPriority + ld a, d + or a + jr z, .set_carry ; return if null + +; pick Arena card + call CreateHandCardList + ld hl, wDuelTempList + ld de, wAICardListArenaPriority + call .PlayPokemonCardInOrder + ret c + +; play Pokemon cards to Bench until there are +; a maximum of 3 cards in Play Area. +.loop + ld de, wAICardListBenchPriority + call .PlayPokemonCardInOrder + jr c, .done + cp 3 + jr c, .loop + +.done + or a + ret +.set_carry + scf + ret + +; runs through input card ID list in de. +; plays to Play Area first card that is found in hand. +; returns carry if none of the cards in the list are found. +; returns number of Pokemon in Play Area in a. +.PlayPokemonCardInOrder ; 1583f (5:583f) + ld a, [de] + ld c, a + inc de + ld a, [de] + ld d, a + ld e, c + +; go in order of the list in de and +; add first card that matches ID. +; returns carry if hand doesn't have any card in list. +.loop_id_list + ld a, [de] + inc de + or a + jr z, .not_found + push de + ld e, a + call RemoveCardIDInList + pop de + jr nc, .loop_id_list + + ; play this card to Play Area and return + push hl + call PutHandPokemonCardInPlayArea + pop hl + or a + ret + +.not_found + scf + ret + +; expects a $00-terminated list of 3-byte data with the following: +; - non-zero value (anything but $1 is ignored) +; - card ID to look for in Play Area +; - number of energy cards +; returns carry if a card ID is found in bench with at least the +; listed number of energy cards +; unreferenced +Func_1585b: ; 1585b (5:585b) + ld a, [hli] + or a + jr z, .no_carry + dec a + jr nz, .next_1 + ld a, [hli] + ld b, PLAY_AREA_BENCH_1 + push hl + call LookForCardIDInPlayArea_Bank5 + jr nc, .next_2 + ld e, a + push de + call CountNumberOfEnergyCardsAttached + pop de + pop hl + ld b, [hl] + cp b + jr nc, .set_carry + inc hl + jr Func_1585b + +.next_1 + inc hl + inc hl + jr Func_1585b + +.next_2 + pop hl + inc hl + jr Func_1585b + +.no_carry + or a + ret + +.set_carry + ld a, e + scf + ret + +; expects a $00-terminated list of 3-byte data with the following: +; - non-zero value +; - card ID +; - number of energy cards +; goes through the given list and if a card with a listed ID is found +; with less than the number of energy cards corresponding to its entry +; then have AI try to play an energy card from the hand to it +; unreferenced +Func_15886: ; 15886 (5:5886) + push hl + call CreateEnergyCardListFromHand + pop hl + ret c ; quit if no energy cards in hand + +.loop_energy_cards + ld a, [hli] + or a + ret z ; done + ld a, [hli] + ld b, PLAY_AREA_ARENA + push hl + call LookForCardIDInPlayArea_Bank5 + jr nc, .next ; skip if not found in Play Area + ld e, a + push de + call CountNumberOfEnergyCardsAttached + pop de + pop hl + cp [hl] + inc hl + jr nc, .loop_energy_cards + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + push hl + call AITryToPlayEnergyCard + pop hl + ret c + jr .loop_energy_cards +.next + pop hl + inc hl + jr .loop_energy_cards + +INCLUDE "engine/duel/ai/retreat.asm" + +; Copy cards from wDuelTempList in hl to wHandTempList in de +CopyHandCardList: ; 15ea6 (5:5ea6) + ld a, [hli] + ld [de], a + cp $ff + ret z + inc de + jr CopyHandCardList + +INCLUDE "engine/duel/ai/hand_pokemon.asm" + +; check if player's active Pokémon is Mr Mime +; if it isn't, set carry +; if it is, check if Pokémon at a +; can damage it, and if it can, set carry +; input: +; a = location of Pokémon card +CheckDamageToMrMime: ; 16270 (5:6270) + push af + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MR_MIME + pop bc + jr nz, .set_carry + ld a, b + call CheckIfCanDamageDefendingPokemon + jr c, .set_carry + or a + ret +.set_carry + scf + ret + +; returns carry if arena card +; can knock out defending Pokémon +CheckIfActiveCardCanKnockOut: ; 1628f (5:628f) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .fail + call CheckIfSelectedAttackIsUnusable + jp c, .fail + scf + ret + +.fail + or a + ret + +; outputs carry if any of the active Pokémon attacks +; can be used and are not residual +CheckIfActivePokemonCanUseAnyNonResidualAttack: ; 162a1 (5:62a1) + xor a ; active card + ldh [hTempPlayAreaLocation_ff9d], a +; first atk + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .next_atk + ld a, [wLoadedAttackCategory] + and RESIDUAL + jr z, .ok + +.next_atk +; second atk + ld a, $01 + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .fail + ld a, [wLoadedAttackCategory] + and RESIDUAL + jr z, .ok +.fail + or a + ret + +.ok + scf + ret + +; looks for energy card(s) in hand depending on +; what is needed for selected card, for both attacks +; - if one basic energy is required, look for that energy; +; - if one colorless is required, create a list at wDuelTempList +; of all energy cards; +; - if two colorless are required, look for double colorless; +; return carry if successful in finding card +; input: +; [hTempPlayAreaLocation_ff9d] = location of Pokémon card +LookForEnergyNeededInHand: ; 162c8 (5:62c8) + xor a ; first attack + ld [wSelectedAttack], a + call CheckEnergyNeededForAttack + ld a, b + add c + cp 1 + jr z, .one_energy + cp 2 + jr nz, .second_attack + ld a, c + cp 2 + jr z, .two_colorless + +.second_attack + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + call CheckEnergyNeededForAttack + ld a, b + add c + cp 1 + jr z, .one_energy + cp 2 + jr nz, .no_carry + ld a, c + cp 2 + jr z, .two_colorless +.no_carry + or a + ret + +.one_energy + ld a, b + or a + jr z, .one_colorless + ld a, e + call LookForCardIDInHandList_Bank5 + ret c + jr .no_carry + +.one_colorless + call CreateEnergyCardListFromHand + jr c, .no_carry + scf + ret + +.two_colorless + ld a, DOUBLE_COLORLESS_ENERGY + call LookForCardIDInHandList_Bank5 + ret c + jr .no_carry + +; looks for energy card(s) in hand depending on +; what is needed for selected card and attack +; - if one basic energy is required, look for that energy; +; - if one colorless is required, create a list at wDuelTempList +; of all energy cards; +; - if two colorless are required, look for double colorless; +; return carry if successful in finding card +; input: +; [hTempPlayAreaLocation_ff9d] = location of Pokémon card +; [wSelectedAttack] = selected attack to examine +LookForEnergyNeededForAttackInHand: ; 16311 (5:6311) + call CheckEnergyNeededForAttack + ld a, b + add c + cp 1 + jr z, .one_energy + cp 2 + jr nz, .done + ld a, c + cp 2 + jr z, .two_colorless +.done + or a + ret + +.one_energy + ld a, b + or a + jr z, .one_colorless + ld a, e + call LookForCardIDInHandList_Bank5 + ret c + jr .done + +.one_colorless + call CreateEnergyCardListFromHand + jr c, .done + scf + ret + +.two_colorless + ld a, DOUBLE_COLORLESS_ENERGY + call LookForCardIDInHandList_Bank5 + ret c + jr .done + +; goes through $00 terminated list pointed +; by wAICardListPlayFromHandPriority and compares it to each card in hand. +; Sorts the hand in wDuelTempList so that the found card IDs +; are in the same order as the list pointed by de. +SortTempHandByIDList: ; 1633f (5:633f) + ld a, [wAICardListPlayFromHandPriority+1] + or a + ret z ; return if list is empty + +; start going down the ID list + ld d, a + ld a, [wAICardListPlayFromHandPriority] + ld e, a + ld c, 0 +.loop_list_id +; get this item's ID +; if $00, list has ended + ld a, [de] + or a + ret z ; return when list is over + inc de + ld hl, wDuelTempList + ld b, 0 + add hl, bc + ld b, a + +; search in the hand card list +.next_hand_card + ld a, [hl] + ldh [hTempCardIndex_ff98], a + cp -1 + jr z, .loop_list_id + push bc + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + pop bc + cp b + jr nz, .not_same + +; found +; swap this hand card with the spot +; in hand corresponding to c + push bc + push hl + ld b, 0 + ld hl, wDuelTempList + add hl, bc + ld b, [hl] + ldh a, [hTempCardIndex_ff98] + ld [hl], a + pop hl + ld [hl], b + pop bc + inc c +.not_same + inc hl + jr .next_hand_card + +; looks for energy card(s) in list at wDuelTempList +; depending on energy flags set in a +; return carry if successful in finding card +; input: +; a = energy flags needed +CheckEnergyFlagsNeededInList: ; 1637b (5:637b) + ld e, a + ld hl, wDuelTempList +.next_card + ld a, [hli] + cp $ff + jr z, .no_carry + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + +; fire + cp FIRE_ENERGY + jr nz, .grass + ld a, FIRE_F + jr .check_energy +.grass + cp GRASS_ENERGY + jr nz, .lightning + ld a, GRASS_F + jr .check_energy +.lightning + cp LIGHTNING_ENERGY + jr nz, .water + ld a, LIGHTNING_F + jr .check_energy +.water + cp WATER_ENERGY + jr nz, .fighting + ld a, WATER_F + jr .check_energy +.fighting + cp FIGHTING_ENERGY + jr nz, .psychic + ld a, FIGHTING_F + jr .check_energy +.psychic + cp PSYCHIC_ENERGY + jr nz, .colorless + ld a, PSYCHIC_F + jr .check_energy +.colorless + cp DOUBLE_COLORLESS_ENERGY + jr nz, .next_card + ld a, COLORLESS_F + +; if energy card matches required energy, return carry +.check_energy + ld d, e + and e + ld e, d + jr z, .next_card + scf + ret +.no_carry + or a + ret + +; returns in a the energy cost of both attacks from card index in a +; represented by energy flags +; i.e. each bit represents a different energy type cost +; if any colorless energy is required, all bits are set +; input: +; a = card index +; output: +; a = bits of each energy requirement +GetAttacksEnergyCostBits: ; 163c9 (5:63c9) + call LoadCardDataToBuffer2_FromDeckIndex + ld hl, wLoadedCard2Atk1EnergyCost + call GetEnergyCostBits + ld b, a + + push bc + ld hl, wLoadedCard2Atk2EnergyCost + call GetEnergyCostBits + pop bc + or b + ret + +; returns in a the energy cost of an attack in [hl] +; represented by energy flags +; i.e. each bit represents a different energy type cost +; if any colorless energy is required, all bits are set +; input: +; [hl] = Loaded card attack energy cost +; output: +; a = bits of each energy requirement +GetEnergyCostBits: ; 163dd (5:63dd) + ld c, $00 + ld a, [hli] + ld b, a + +; fire + and $f0 + jr z, .grass + ld c, FIRE_F +.grass + ld a, b + and $0f + jr z, .lightning + ld a, GRASS_F + or c + ld c, a +.lightning + ld a, [hli] + ld b, a + and $f0 + jr z, .water + ld a, LIGHTNING_F + or c + ld c, a +.water + ld a, b + and $0f + jr z, .fighting + ld a, WATER_F + or c + ld c, a +.fighting + ld a, [hli] + ld b, a + and $f0 + jr z, .psychic + ld a, FIGHTING_F + or c + ld c, a +.psychic + ld a, b + and $0f + jr z, .colorless + ld a, PSYCHIC_F + or c + ld c, a +.colorless + ld a, [hli] + ld b, a + and $f0 + jr z, .done + ld a, %11111111 + or c ; unnecessary + ld c, a +.done + ld a, c + ret + +; set carry flag if any card in +; wDuelTempList evolves card index in a +; if found, the evolution card index is returned in a +; input: +; a = card index to check evolution +; output: +; a = card index of evolution found +CheckForEvolutionInList: ; 16422 (5:6422) + ld b, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + + push af + ld [hl], b + ld hl, wDuelTempList +.loop + ld a, [hli] + cp $ff + jr z, .no_carry + ld d, a + ld e, PLAY_AREA_ARENA + push de + push hl + call CheckIfCanEvolveInto + pop hl + pop de + jr c, .loop + + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + ld a, d + scf + ret + +.no_carry + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + or a + ret + +; set carry if it finds an evolution for +; the card index in a in the deck +; if found, return that evolution card index in a +; input: +; a = card index to check evolution +; output: +; a = card index of evolution found +CheckForEvolutionInDeck: ; 16451 (5:6451) + ld b, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + + push af + ld [hl], b + ld e, 0 +.loop + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + cp CARD_LOCATION_DECK + jr nz, .not_in_deck + push de + ld d, e + ld e, PLAY_AREA_ARENA + call CheckIfCanEvolveInto + pop de + jr nc, .set_carry + +; exit when it gets to the prize cards +.not_in_deck + inc e + ld a, DUELVARS_PRIZE_CARDS + cp e + jr nz, .loop + + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + or a + ret + +.set_carry + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + ld a, e + scf + ret + +INCLUDE "engine/duel/ai/energy.asm" + +INCLUDE "engine/duel/ai/attacks.asm" + +INCLUDE "engine/duel/ai/special_attacks.asm" + +; checks in other Play Area for non-basic cards. +; afterwards, that card is checked for damage, +; and if the damage counters it has is greater than or equal +; to the max HP of the card stage below it, +; return carry and that card's Play Area location in a. +; output: +; a = card location of Pokémon card, if found; +; carry set if such a card is found. +LookForCardThatIsKnockedOutOnDevolution: ; 17080 (5:7080) + ldh a, [hTempPlayAreaLocation_ff9d] + push af + call SwapTurn + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, PLAY_AREA_ARENA + +.loop + ld a, c + ldh [hTempPlayAreaLocation_ff9d], a + push bc + bank1call GetCardOneStageBelow + pop bc + jr c, .next + ; is not a basic card + ; compare its HP with current damage + ld a, d + push bc + call LoadCardDataToBuffer2_FromDeckIndex + pop bc + ld a, [wLoadedCard2HP] + ld [wTempAI], a + ld e, c + push bc + call GetCardDamageAndMaxHP + pop bc + ld e, a + ld a, [wTempAI] + cp e + jr c, .set_carry + jr z, .set_carry +.next + inc c + ld a, c + cp b + jr nz, .loop + + call SwapTurn + pop af + ldh [hTempPlayAreaLocation_ff9d], a + or a + ret + +.set_carry + call SwapTurn + pop af + ldh [hTempPlayAreaLocation_ff9d], a + ld a, c + scf + ret + +; returns carry if the following conditions are met: +; - arena card HP >= half max HP +; - arena card Unknown2's 4 bit is not set or +; is set but there's no evolution of card in hand/deck +; - arena card can use second attack +CheckIfArenaCardIsAtHalfHPCanEvolveAndUseSecondAttack: ; 170c9 (5:70c9) + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + push de + call LoadCardDataToBuffer1_FromDeckIndex + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld d, a + ld a, [wLoadedCard1HP] + rrca + cp d + pop de + jr nc, .no_carry + + ld a, [wLoadedCard1Unknown2] + and %00010000 + jr z, .check_second_attack + ld a, d + call CheckCardEvolutionInHandOrDeck + jr c, .no_carry + +.check_second_attack + xor a ; active card + ldh [hTempPlayAreaLocation_ff9d], a + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + push hl + call CheckIfSelectedAttackIsUnusable + pop hl + jr c, .no_carry + scf + ret +.no_carry + or a + ret + +; count Pokemon in the Bench that +; meet the following conditions: +; - card HP > half max HP +; - card Unknown2's 4 bit is not set or +; is set but there's no evolution of card in hand/deck +; - card can use second attack +; Outputs the number of Pokémon in bench +; that meet these requirements in a +; and returns carry if at least one is found +CountNumberOfSetUpBenchPokemon: ; 17101 (5:7101) + ldh a, [hTempPlayAreaLocation_ff9d] + ld d, a + ld a, [wSelectedAttack] + ld e, a + push de + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + lb bc, 0, 0 + push hl + +.next + inc c + pop hl + ld a, [hli] + push hl + cp $ff + jr z, .done + + ld d, a + push de + push bc + call LoadCardDataToBuffer1_FromDeckIndex + pop bc + +; compares card's current HP with max HP + ld a, c + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld d, a + ld a, [wLoadedCard1HP] + rrca + +; a = max HP / 2 +; d = current HP +; jumps if (current HP) <= (max HP / 2) + cp d + pop de + jr nc, .next + + ld a, [wLoadedCard1Unknown2] + and $10 + jr z, .check_second_attack + + ld a, d + push bc + call CheckCardEvolutionInHandOrDeck + pop bc + jr c, .next + +.check_second_attack + ld a, c + ldh [hTempPlayAreaLocation_ff9d], a + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + push bc + push hl + call CheckIfSelectedAttackIsUnusable + pop hl + pop bc + jr c, .next + inc b + jr .next + +.done + pop hl + pop de + ld a, e + ld [wSelectedAttack], a + ld a, d + ldh [hTempPlayAreaLocation_ff9d], a + ld a, b + or a + ret z + scf + ret + +; handles AI logic to determine some selections regarding certain attacks, +; if any of these attacks were chosen to be used. +; returns carry if selection was successful, +; and no carry if unable to make one. +; outputs in hTempPlayAreaLocation_ffa1 the chosen parameter. +AISelectSpecialAttackParameters: ; 17161 (5:7161) + ld a, [wSelectedAttack] + push af + call .SelectAttackParameters + pop bc + ld a, b + ld [wSelectedAttack], a + ret + +.SelectAttackParameters: ; 1716e (5:716e) + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp MEW3 + jr z, .DevolutionBeam + cp MEWTWO3 + jr z, .EnergyAbsorption + cp MEWTWO2 + jr z, .EnergyAbsorption + cp EXEGGUTOR + jr z, .Teleport + cp ELECTRODE1 + jr z, .EnergySpike + ; fallthrough + +.no_carry + or a + ret + +.DevolutionBeam +; in case selected attack is Devolution Beam +; store in hTempPlayAreaLocation_ffa1 +; the location of card to select to devolve + ld a, [wSelectedAttack] + or a + jp z, .no_carry ; can be jr + + ld a, $01 + ldh [hTemp_ffa0], a + call LookForCardThatIsKnockedOutOnDevolution + ldh [hTempPlayAreaLocation_ffa1], a + +.set_carry_1 + scf + ret + +.EnergyAbsorption +; in case selected attack is Energy Absorption +; make list from energy cards in Discard Pile + ld a, [wSelectedAttack] + or a + jp nz, .no_carry ; can be jr + + ld a, $ff + ldh [hTempPlayAreaLocation_ffa1], a + ldh [hTempRetreatCostCards], a + +; search for Psychic energy cards in Discard Pile + ld e, PSYCHIC_ENERGY + ld a, CARD_LOCATION_DISCARD_PILE + call CheckIfAnyCardIDinLocation + ldh [hTemp_ffa0], a + farcall CreateEnergyCardListFromDiscardPile_AllEnergy + +; find any energy card different from +; the one found by CheckIfAnyCardIDinLocation. +; since using this attack requires a Psychic energy card, +; and another one is in hTemp_ffa0, +; then any other energy card would account +; for the Energy Cost of Psyburn. + ld hl, wDuelTempList +.loop_energy_cards + ld a, [hli] + cp $ff + jr z, .set_carry_2 + ld b, a + ldh a, [hTemp_ffa0] + cp b + jr z, .loop_energy_cards ; same card, keep looking + +; store the deck index of energy card found + ld a, b + ldh [hTempPlayAreaLocation_ffa1], a + ; fallthrough + +.set_carry_2 + scf + ret + +.Teleport +; in case selected attack is Teleport +; decide Bench card to switch to. + ld a, [wSelectedAttack] + or a + jp nz, .no_carry ; can be jr + call AIDecideBenchPokemonToSwitchTo + jr c, .no_carry + ldh [hTemp_ffa0], a + scf + ret + +.EnergySpike +; in case selected attack is Energy Spike +; decide basic energy card to fetch from Deck. + ld a, [wSelectedAttack] + or a + jp z, .no_carry ; can be jr + + ld a, CARD_LOCATION_DECK + ld e, LIGHTNING_ENERGY + +; if none were found in Deck, return carry... + call CheckIfAnyCardIDinLocation + ldh [hTemp_ffa0], a + jp nc, .no_carry ; can be jr + +; ...else find a suitable Play Area Pokemon to +; attach the energy card to. + call AIProcessButDontPlayEnergy_SkipEvolution + jp nc, .no_carry ; can be jr + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempPlayAreaLocation_ffa1], a + scf + ret + +; return carry if Pokémon at play area location +; in hTempPlayAreaLocation_ff9d does not have +; energy required for the attack index in wSelectedAttack +; or has exactly the same amount of energy needed +; input: +; [hTempPlayAreaLocation_ff9d] = play area location +; [wSelectedAttack] = attack index to check +; output: +; a = number of extra energy cards attached +CheckIfNoSurplusEnergyForAttack: ; 171fb (5:71fb) + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + ld a, [wSelectedAttack] + ld e, a + call CopyAttackDataAndDamage_FromDeckIndex + ld hl, wLoadedAttackName + ld a, [hli] + or [hl] + jr z, .not_attack + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr nz, .is_attack +.not_attack + scf + ret + +.is_attack + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + bank1call HandleEnergyBurn + xor a + ld [wTempLoadedAttackEnergyCost], a + ld [wTempLoadedAttackEnergyNeededAmount], a + ld [wTempLoadedAttackEnergyNeededType], a + ld hl, wAttachedEnergies + ld de, wLoadedAttackEnergyCost + ld b, 0 + ld c, (NUM_TYPES / 2) - 1 +.loop + ; check all basic energy cards except colorless + ld a, [de] + swap a + call CalculateParticularAttachedEnergyNeeded + ld a, [de] + call CalculateParticularAttachedEnergyNeeded + inc de + dec c + jr nz, .loop + + ; colorless + ld a, [de] + swap a + and %00001111 + ld b, a + ld hl, wTempLoadedAttackEnergyCost + ld a, [wTotalAttachedEnergies] + sub [hl] + sub b + ret c ; return if not enough energy + + or a + ret nz ; return if surplus energy + + ; exactly the amount of energy needed + scf + ret + +; takes as input the energy cost of an attack for a +; particular energy, stored in the lower nibble of a +; if the attack costs some amount of this energy, the lower nibble of a != 0, +; and this amount is stored in wTempLoadedAttackEnergyCost +; also adds the amount of energy still needed +; to wTempLoadedAttackEnergyNeededAmount +; input: +; a = this energy cost of attack (lower nibble) +; [hl] = attached energy +; output: +; carry set if not enough of this energy type attached +CalculateParticularAttachedEnergyNeeded: ; 17258 (5:7258) + and %00001111 + jr nz, .check +.done + inc hl + inc b + ret + +.check + ld [wTempLoadedAttackEnergyCost], a + sub [hl] + jr z, .done + jr nc, .done + push bc + ld a, [wTempLoadedAttackEnergyCost] + ld b, a + ld a, [hl] + sub b + pop bc + ld [wTempLoadedAttackEnergyNeededAmount], a + jr .done + +; return carry if there is a card that +; can evolve a Pokémon in hand or deck. +; input: +; a = deck index of card to check; +; output: +; a = deck index of evolution in hand, if found; +; carry set if there's a card in hand that can evolve. +CheckCardEvolutionInHandOrDeck: ; 17274 (5:7274) + ld b, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + push af + ld [hl], b + ld e, 0 + +.loop + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + cp CARD_LOCATION_DECK + jr z, .deck_or_hand + cp CARD_LOCATION_HAND + jr nz, .next +.deck_or_hand + push de + ld d, e + ld e, PLAY_AREA_ARENA + call CheckIfCanEvolveInto + pop de + jr nc, .set_carry +.next + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop + + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + or a + ret + +.set_carry + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + ld [hl], a + ld a, e + scf + ret + +INCLUDE "engine/duel/ai/boss_deck_set_up.asm" + +; returns carry if Pokemon at PLAY_AREA* in a +; can damage defending Pokémon with any of its attacks +; input: +; a = location of card to check +CheckIfCanDamageDefendingPokemon: ; 17383 (5:7383) + ldh [hTempPlayAreaLocation_ff9d], a + xor a ; first attack + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .second_attack + xor a + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr nz, .set_carry + +.second_attack + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .no_carry + ld a, $01 + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr nz, .set_carry + +.no_carry + or a + ret +.set_carry + scf + ret + +; checks if defending Pokémon can knock out +; card at hTempPlayAreaLocation_ff9d with any of its attacks +; and if so, stores the damage to wce00 and wce01 +; sets carry if any on the attacks knocks out +; also outputs the largest damage dealt in a +; input: +; [hTempPlayAreaLocation_ff9d] = location of card to check +; output: +; a = largest damage of both attacks +; carry set if can knock out +CheckIfDefendingPokemonCanKnockOut: ; 173b1 (5:73b1) + xor a ; first attack + ld [wce00], a + ld [wce01], a + call CheckIfDefendingPokemonCanKnockOutWithAttack + jr nc, .second_attack + ld a, [wDamage] + ld [wce00], a + +.second_attack + ld a, SECOND_ATTACK + call CheckIfDefendingPokemonCanKnockOutWithAttack + jr nc, .return_if_neither_kos + ld a, [wDamage] + ld [wce01], a + jr .compare + +.return_if_neither_kos + ld a, [wce00] + or a + ret z + +.compare + ld a, [wce00] + ld b, a + ld a, [wce01] + cp b + jr nc, .set_carry + ld a, b +.set_carry + scf + ret + +; return carry if defending Pokémon can knock out +; card at hTempPlayAreaLocation_ff9d +; input: +; a = attack index +; [hTempPlayAreaLocation_ff9d] = location of card to check +CheckIfDefendingPokemonCanKnockOutWithAttack: ; 173e4 (5:73e4) + ld [wSelectedAttack], a + ldh a, [hTempPlayAreaLocation_ff9d] + push af + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call SwapTurn + call CheckIfSelectedAttackIsUnusable + call SwapTurn + pop bc + ld a, b + ldh [hTempPlayAreaLocation_ff9d], a + jr c, .done + +; player's active Pokémon can use attack + ld a, [wSelectedAttack] + call EstimateDamage_FromDefendingPokemon + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld hl, wDamage + sub [hl] + jr z, .set_carry + ret + +.set_carry + scf + ret + +.done + or a + ret + +; sets carry if Opponent's deck ID +; is between LEGENDARY_MOLTRES_DECK_ID (inclusive) +; and MUSCLES_FOR_BRAINS_DECK_ID (exclusive) +; these are the decks for Grandmaster/Club Master/Ronald +CheckIfOpponentHasBossDeckID: ; 17414 (5:7414) + push af + ld a, [wOpponentDeckID] + cp LEGENDARY_MOLTRES_DECK_ID + jr c, .no_carry + cp MUSCLES_FOR_BRAINS_DECK_ID + jr nc, .no_carry + pop af + scf + ret + +.no_carry + pop af + or a + ret + +; sets carry if not a boss fight +; and if hasn't received legendary cards yet +CheckIfNotABossDeckID: ; 17426 (5:7426) + call EnableSRAM + ld a, [sReceivedLegendaryCards] + call DisableSRAM + or a + jr nz, .no_carry + call CheckIfOpponentHasBossDeckID + jr nc, .set_carry +.no_carry + or a + ret + +.set_carry + scf + ret + +; probability to return carry: +; - 50% if deck AI is playing is on the list; +; - 25% for all other decks; +; - 0% for boss decks. +; used for certain decks to randomly choose +; not to play Trainer card or use PKMN Power +AIChooseRandomlyNotToDoAction: ; 1743b (5:743b) +; boss decks always use Trainer cards. + push hl + push de + call CheckIfNotABossDeckID + jr c, .check_deck + pop de + pop hl + ret + +.check_deck + ld a, [wOpponentDeckID] + cp MUSCLES_FOR_BRAINS_DECK_ID + jr z, .carry_50_percent + cp BLISTERING_POKEMON_DECK_ID + jr z, .carry_50_percent + cp WATERFRONT_POKEMON_DECK_ID + jr z, .carry_50_percent + cp BOOM_BOOM_SELFDESTRUCT_DECK_ID + jr z, .carry_50_percent + cp KALEIDOSCOPE_DECK_ID + jr z, .carry_50_percent + cp RESHUFFLE_DECK_ID + jr z, .carry_50_percent + +; carry 25 percent + ld a, 4 + call Random + cp 1 + pop de + pop hl + ret + +.carry_50_percent + ld a, 4 + call Random + cp 2 + pop de + pop hl + ret + +; checks if any bench Pokémon has same ID +; as input, and sets carry if it has more than +; half health and can use its second attack +; input: +; a = card ID to check for +; output: +; carry set if the above requirements are met +CheckForBenchIDAtHalfHPAndCanUseSecondAttack: ; 17474 (5:7474) + ld [wcdf9], a + ldh a, [hTempPlayAreaLocation_ff9d] + ld d, a + ld a, [wSelectedAttack] + ld e, a + push de + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + lb bc, 0, PLAY_AREA_ARENA + push hl + +.loop + inc c + pop hl + ld a, [hli] + push hl + cp $ff + jr z, .done + ld d, a + push de + push bc + call LoadCardDataToBuffer1_FromDeckIndex + pop bc + ld a, c + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld d, a + ld a, [wLoadedCard1HP] + rrca + cp d + pop de + jr nc, .loop + ; half max HP < current HP + ld a, [wLoadedCard1ID] + ld hl, wcdf9 + cp [hl] + jr nz, .loop + + ld a, c + ldh [hTempPlayAreaLocation_ff9d], a + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + push bc + call CheckIfSelectedAttackIsUnusable + pop bc + jr c, .loop + inc b +.done + pop hl + pop de + ld a, e + ld [wSelectedAttack], a + ld a, d + ldh [hTempPlayAreaLocation_ff9d], a + ld a, b + or a + ret z + scf + ret + +; add 5 to wPlayAreaEnergyAIScore AI score corresponding to all cards +; in bench that have same ID as register a +; input: +; a = card ID to look for +RaiseAIScoreToAllMatchingIDsInBench: ; 174cd (5:74cd) + ld d, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld e, 0 +.loop + inc e + ld a, [hli] + cp $ff + ret z + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp d + jr nz, .loop + ld c, e + ld b, $00 + push hl + ld hl, wPlayAreaEnergyAIScore + add hl, bc + ld a, 5 + add [hl] + ld [hl], a + pop hl + jr .loop + +; goes through each play area Pokémon, and +; for all cards of the same ID, determine which +; card has highest value calculated from Func_17583 +; the card with highest value gets increased wPlayAreaEnergyAIScore +; while all others get decreased wPlayAreaEnergyAIScore +Func_174f2: ; 174f2 (5:74f2) + ld a, MAX_PLAY_AREA_POKEMON + ld hl, wcdfa + call ClearMemory_Bank5 + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld e, 0 + +.loop_play_area + push hl + ld a, MAX_PLAY_AREA_POKEMON + ld hl, wcdea + call ClearMemory_Bank5 + pop hl + inc e + ld a, [hli] + cp $ff + ret z + + ld [wcdf9], a + push de + push hl + +; checks wcdfa + play area location in e +; if != 0, go to next in play area + ld d, $00 + ld hl, wcdfa + add hl, de + ld a, [hl] + or a + pop hl + pop de + jr nz, .loop_play_area + +; loads wcdf9 with card ID +; and call Func_17583 + push de + ld a, [wcdf9] + call GetCardIDFromDeckIndex + ld a, e + ld [wcdf9], a + pop de + push hl + push de + call Func_17583 + +; check play area Pokémon ahead +; if there is a card with the same ID, +; call Func_17583 for it as well +.loop_1 + inc e + ld a, [hli] + cp $ff + jr z, .check_if_repeated_id + push de + call GetCardIDFromDeckIndex + ld a, [wcdf9] + cp e + pop de + jr nz, .loop_1 + call Func_17583 + jr .loop_1 + +; if there are more than 1 of the same ID +; in play area, iterate bench backwards +; and determines which card has highest +; score in wcdea +.check_if_repeated_id + call Func_175a8 + jr c, .next + lb bc, 0, 0 + ld hl, wcdea + MAX_BENCH_POKEMON + ld d, MAX_PLAY_AREA_POKEMON +.loop_2 + dec d + jr z, .asm_17560 + ld a, [hld] + cp b + jr c, .loop_2 + ld b, a + ld c, d + jr .loop_2 + +; c = play area location of highest score +; decrease wPlayAreaEnergyAIScore score for all cards with same ID +; except for the one with highest score +; increase wPlayAreaEnergyAIScore score for card with highest ID +.asm_17560 + ld hl, wPlayAreaEnergyAIScore + ld de, wcdea + ld b, PLAY_AREA_ARENA +.loop_3 + ld a, c + cp b + jr z, .card_with_highest + ld a, [de] + or a + jr z, .check_next +; decrease score + dec [hl] + jr .check_next +.card_with_highest +; increase score + inc [hl] +.check_next + inc b + ld a, MAX_PLAY_AREA_POKEMON + cp b + jr z, .next + inc de + inc hl + jr .loop_3 + +.next + pop de + pop hl + jp .loop_play_area + +; loads wcdea + play area location in e +; with energy * 2 + $80 - floor(dam / 10) +; loads wcdfa + play area location in e +; with $01 +Func_17583: ; 17583 (5:7583) + push hl + push de + call GetCardDamageAndMaxHP + call CalculateByteTensDigit + ld b, a + push bc + call CountNumberOfEnergyCardsAttached + pop bc + sla a + add $80 + sub b + pop de + push de + ld d, $00 + ld hl, wcdea + add hl, de + ld [hl], a + ld hl, wcdfa + add hl, de + ld [hl], $01 + pop de + pop hl + ret + +; counts how many play area locations in wcdea +; are != 0, and outputs result in a +; also returns carry if result is < 2 +Func_175a8: ; 175a8 (5:75a8) + ld hl, wcdea + ld d, $00 + ld e, MAX_PLAY_AREA_POKEMON + 1 +.loop + dec e + jr z, .done + ld a, [hli] + or a + jr z, .loop + inc d + jr .loop +.done + ld a, d + cp 2 + ret + +; handle how AI scores giving out Energy Cards +; when using Legendary Articuno deck +HandleLegendaryArticunoEnergyScoring: ; 175bd (5:75bd) + ld a, [wOpponentDeckID] + cp LEGENDARY_ARTICUNO_DECK_ID + jr z, .articuno_deck + ret +.articuno_deck + call ScoreLegendaryArticunoCards + ret diff --git a/src/engine/duel/ai/damage_calculation.asm b/src/engine/duel/ai/damage_calculation.asm new file mode 100644 index 0000000..97c24b6 --- /dev/null +++ b/src/engine/duel/ai/damage_calculation.asm @@ -0,0 +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 diff --git a/src/engine/duel/ai/deck_ai.asm b/src/engine/duel/ai/deck_ai.asm new file mode 100644 index 0000000..b46de01 --- /dev/null +++ b/src/engine/duel/ai/deck_ai.asm @@ -0,0 +1,82 @@ +; AI card retreat score bonus +; when the AI retreat routine runs through the Bench to choose +; a Pokemon to switch to, it looks up in this list and if +; a card ID matches, applies a retreat score bonus to this card. +; positive (negative) means more (less) likely to switch to this card. +ai_retreat: MACRO + db \1 ; card ID + db $80 + \2 ; retreat score (ranges between -128 and 127) +ENDM + +; AI card energy attach score bonus +; when the AI energy attachment routine runs through the Play Area to choose +; a Pokemon to attach an energy card, it looks up in this list and if +; a card ID matches, skips this card if the maximum number of energy +; cards attached has been reached. If it hasn't been reached, additionally +; applies a positive (or negative) AI score to attach energy to this card. +ai_energy: MACRO + db \1 ; card ID + db \2 ; maximum number of attached cards + db $80 + \3 ; energy score (ranges between -128 and 127) +ENDM + +; stores in WRAM pointer to data in argument +; e.g. store_list_pointer wSomeListPointer, SomeData +store_list_pointer: MACRO + ld hl, \1 + ld de, \2 + ld [hl], e + inc hl + ld [hl], d +ENDM + +; deck AIs are specialized to work on a given deck ID. +; they decide what happens during a turn, what Pokemon cards +; to pick during the start of the duel, etc. +; the different scenarios these are used are listed in AIACTION_* constants. +; each of these have a pointer table with the following structure: +; dw .do_turn : never called; +; +; dw .do_turn : called to handle the main turn logic, from the beginning +; of the turn up to the attack (or lack thereof); +; +; dw .start_duel : called at the start of the duel to initialize some +; variables and optionally set up CPU hand and deck; +; +; dw .forced_switch : logic to determine what Pokemon to pick when there's +; an effect that forces AI to switch to Bench card; +; +; dw .ko_switch : logic for picking which card to use after a KO; +; +; dw .take_prize : logic to decide which prize card to pick. + +; optionally, decks can also declare card lists that will add +; more specialized logic during various generic AI routines, +; and read during the .start_duel routines. +; the pointers to these lists are stored in memory: +; wAICardListAvoidPrize : list of cards to avoid being placed as prize; +; wAICardListArenaPriority : priority list of Arena card at duel start; +; wAICardListBenchPriority : priority list of Bench cards at duel start; +; wAICardListPlayFromHandPriority : priority list of cards to play from hand; +; wAICardListRetreatBonus : scores given to certain cards for retreat; +; wAICardListEnergyBonus : max number of energy cards and card scores. + +INCLUDE "engine/duel/ai/decks/general.asm" +INCLUDE "engine/duel/ai/decks/sams_practice.asm" +INCLUDE "engine/duel/ai/decks/general_no_retreat.asm" +INCLUDE "engine/duel/ai/decks/legendary_moltres.asm" +INCLUDE "engine/duel/ai/decks/legendary_zapdos.asm" +INCLUDE "engine/duel/ai/decks/legendary_articuno.asm" +INCLUDE "engine/duel/ai/decks/legendary_dragonite.asm" +INCLUDE "engine/duel/ai/decks/first_strike.asm" +INCLUDE "engine/duel/ai/decks/rock_crusher.asm" +INCLUDE "engine/duel/ai/decks/go_go_rain_dance.asm" +INCLUDE "engine/duel/ai/decks/zapping_selfdestruct.asm" +INCLUDE "engine/duel/ai/decks/flower_power.asm" +INCLUDE "engine/duel/ai/decks/strange_psyshock.asm" +INCLUDE "engine/duel/ai/decks/wonders_of_science.asm" +INCLUDE "engine/duel/ai/decks/fire_charge.asm" +INCLUDE "engine/duel/ai/decks/im_ronald.asm" +INCLUDE "engine/duel/ai/decks/powerful_ronald.asm" +INCLUDE "engine/duel/ai/decks/invincible_ronald.asm" +INCLUDE "engine/duel/ai/decks/legendary_ronald.asm" diff --git a/src/engine/duel/ai/decks/fire_charge.asm b/src/engine/duel/ai/decks/fire_charge.asm new file mode 100644 index 0000000..f5b347b --- /dev/null +++ b/src/engine/duel/ai/decks/fire_charge.asm @@ -0,0 +1,80 @@ +AIActionTable_FireCharge: ; 15232 (5:5232) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 1523e (5:523e) + call AIMainTurnLogic + ret + +.start_duel ; 15242 (5:5242) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 15253 (5:5253) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 15257 (5:5257) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 1525b (5:525b) + call AIPickPrizeCards + ret + +.list_arena ; 1525f (5:525f) + db JIGGLYPUFF3 + db CHANSEY + db TAUROS + db MAGMAR1 + db JIGGLYPUFF1 + db GROWLITHE + db $00 + +.list_bench ; 15266 (5:5266) + db JIGGLYPUFF3 + db CHANSEY + db GROWLITHE + db MAGMAR1 + db JIGGLYPUFF1 + db TAUROS + db $00 + +.list_retreat ; 1526e (5:526e) + ai_retreat JIGGLYPUFF1, -1 + ai_retreat CHANSEY, -1 + ai_retreat GROWLITHE, -1 + db $00 + +.list_energy ; 15274 (5:5274) + ai_energy GROWLITHE, 3, +0 + ai_energy ARCANINE2, 4, +0 + ai_energy MAGMAR1, 3, +0 + ai_energy JIGGLYPUFF1, 3, +0 + ai_energy JIGGLYPUFF3, 2, +0 + ai_energy WIGGLYTUFF, 3, +0 + ai_energy CHANSEY, 4, +0 + ai_energy TAUROS, 3, +0 + db $00 + +.list_prize ; 1528d (5:528d) + db GAMBLER + db $00 + +.store_list_pointers ; 1528f (5:528f) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/first_strike.asm b/src/engine/duel/ai/decks/first_strike.asm new file mode 100644 index 0000000..2e636e1 --- /dev/null +++ b/src/engine/duel/ai/decks/first_strike.asm @@ -0,0 +1,76 @@ +AIActionTable_FirstStrike: ; 14e89 (5:4e89) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 14e95 (5:4e95) + call AIMainTurnLogic + ret + +.start_duel ; 14e99 (5:4e99) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 14eaa (5:4eaa) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14eae (5:4eae) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 14eb2 (5:4eb2) + call AIPickPrizeCards + ret + +.list_arena ; 14eb6 (5:1eb6) + db HITMONCHAN + db MACHOP + db HITMONLEE + db MANKEY + db $00 + +.list_bench ; 14ebb (5:1ebb) + db MACHOP + db HITMONLEE + db HITMONCHAN + db MANKEY + db $00 + +.list_retreat ; 14ec0 (5:1ec0) + ai_retreat MACHOP, -1 + ai_retreat MACHOKE, -1 + ai_retreat MANKEY, -2 + db $00 + +.list_energy ; 14ec7 (5:1ec7) + ai_energy MACHOP, 3, +0 + ai_energy MACHOKE, 4, +0 + ai_energy MACHAMP, 4, -1 + ai_energy HITMONCHAN, 3, +0 + ai_energy HITMONLEE, 3, +0 + ai_energy MANKEY, 2, -1 + ai_energy PRIMEAPE, 3, -1 + db $00 + +.list_prize ; 14edd (5:1edd) + db HITMONLEE + db HITMONCHAN + db $00 + +.store_list_pointers ; 14ee0 (5:4ee0) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/flower_power.asm b/src/engine/duel/ai/decks/flower_power.asm new file mode 100644 index 0000000..4d423a3 --- /dev/null +++ b/src/engine/duel/ai/decks/flower_power.asm @@ -0,0 +1,75 @@ +AIActionTable_FlowerPower: ; 1509b (5:509b) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 150a7 (5:50a7) + call AIMainTurnLogic + ret + +.start_duel ; 150ab (5:50ab) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 150bc (5:50bc) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 150c0 (5:50c0) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 150c4 (5:50c4) + call AIPickPrizeCards + ret + +.list_arena ; 150c8 (5:50c8) + db ODDISH + db EXEGGCUTE + db BULBASAUR + db $00 + +.list_bench ; 150cc (5:50cc) + db BULBASAUR + db EXEGGCUTE + db ODDISH + db $00 + +.list_retreat ; 150cf (5:50cf) + ai_retreat GLOOM, -2 + ai_retreat VILEPLUME, -2 + ai_retreat BULBASAUR, -2 + ai_retreat IVYSAUR, -2 + db $00 + +.list_energy ; 150d9 (5:50d9) + ai_energy BULBASAUR, 3, +0 + ai_energy IVYSAUR, 4, +0 + ai_energy VENUSAUR2, 4, +0 + ai_energy ODDISH, 2, +0 + ai_energy GLOOM, 3, -1 + ai_energy VILEPLUME, 3, -1 + ai_energy EXEGGCUTE, 3, +0 + ai_energy EXEGGUTOR, 22, +0 + db $00 + +.list_prize ; 150f2 (5:50f2) + db VENUSAUR2 + db $00 + +.store_list_pointers ; 150f4 (5:50f4) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/general.asm b/src/engine/duel/ai/decks/general.asm new file mode 100644 index 0000000..039e101 --- /dev/null +++ b/src/engine/duel/ai/decks/general.asm @@ -0,0 +1,194 @@ +; AI logic used by general decks +AIActionTable_GeneralDecks: ; 14668 (05:4668) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 14674 (5:4674) + call AIMainTurnLogic + ret + +.start_duel ; 14678 (5:4678) + call InitAIDuelVars + call AIPlayInitialBasicCards + ret + +.forced_switch ; 1467f (5:467f) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14683 (5:4683) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize: ; 14687 (5:4687) + call AIPickPrizeCards + ret + +; handle AI routines for a whole turn +AIMainTurnLogic: ; 1468b (5:468b) +; initialize variables + call InitAITurnVars + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + farcall HandleAIAntiMewtwoDeckStrategy + jp nc, .try_attack +; handle Pkmn Powers + farcall HandleAIGoGoRainDanceEnergy + farcall HandleAIDamageSwap + farcall HandleAIPkmnPowers + ret c ; return if turn ended + farcall HandleAICowardice +; process Trainer cards +; phase 2 through 4. + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_03 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards +; play Pokemon from hand + call AIDecidePlayPokemonCard + ret c ; return if turn ended +; process Trainer cards +; phase 5 through 12. + ld a, AI_TRAINER_CARD_PHASE_05 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_06 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_08 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_11 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_12 + call AIProcessHandTrainerCards +; play Energy card if possible + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_1 + call AIProcessAndTryToPlayEnergy +.skip_energy_attach_1 +; play Pokemon from hand again + call AIDecidePlayPokemonCard +; handle Pkmn Powers again + farcall HandleAIDamageSwap + farcall HandleAIPkmnPowers + ret c ; return if turn ended + farcall HandleAIGoGoRainDanceEnergy + ld a, AI_ENERGY_TRANS_ATTACK + farcall HandleAIEnergyTrans +; process Trainer cards phases 13 and 15 + ld a, AI_TRAINER_CARD_PHASE_13 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_15 + call AIProcessHandTrainerCards +; if used Professor Oak, process new hand +; if not, then proceed to attack. + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_PROFESSOR_OAK + jr z, .try_attack + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_03 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_05 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_06 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_08 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_11 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_12 + call AIProcessHandTrainerCards + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_2 + call AIProcessAndTryToPlayEnergy +.skip_energy_attach_2 + call AIDecidePlayPokemonCard + farcall HandleAIDamageSwap + farcall HandleAIPkmnPowers + ret c ; return if turn ended + farcall HandleAIGoGoRainDanceEnergy + ld a, AI_ENERGY_TRANS_ATTACK + farcall HandleAIEnergyTrans + ld a, AI_TRAINER_CARD_PHASE_13 + call AIProcessHandTrainerCards + ; skip AI_TRAINER_CARD_PHASE_15 +.try_attack + ld a, AI_ENERGY_TRANS_TO_BENCH + farcall HandleAIEnergyTrans +; attack if possible, if not, +; finish turn without attacking. + call AIProcessAndTryToUseAttack + ret c ; return if AI attacked + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret + +; handles AI retreating logic +AIProcessRetreat: ; 14786 (5:4786) + ld a, [wAIRetreatedThisTurn] + or a + ret nz ; return, already retreated this turn + + call AIDecideWhetherToRetreat + ret nc ; return if not retreating + + call AIDecideBenchPokemonToSwitchTo + ret c ; return if no Bench Pokemon + +; store Play Area to retreat to and +; set wAIRetreatedThisTurn to true + ld [wAIPlayAreaCardToSwitch], a + ld a, $01 + ld [wAIRetreatedThisTurn], a + +; if AI can use Switch from hand, use it instead... + ld a, AI_TRAINER_CARD_PHASE_09 + call AIProcessHandTrainerCards + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_SWITCH + jr nz, .used_switch +; ... else try retreating normally. + ld a, [wAIPlayAreaCardToSwitch] + call AITryToRetreat + ret + +.used_switch +; if AI used switch, unset its AI flag + ld a, [wPreviousAIFlags] + and ~AI_FLAG_USED_SWITCH ; clear Switch flag + ld [wPreviousAIFlags], a + +; bug, this doesn't make sense being here, since at this point +; Switch Trainer card was already used to retreat the Pokemon. +; what the routine will do is just transfer Energy cards to +; the Arena Pokemon for the purpose of retreating, and +; then not actually retreat, resulting in unusual behaviour. +; this would only work placed right after the AI checks whether +; they have Switch card in hand to use and doesn't have one. +; (and probably that was the original intention.) + ld a, AI_ENERGY_TRANS_RETREAT ; retreat + farcall HandleAIEnergyTrans + ret diff --git a/src/engine/duel/ai/decks/general_no_retreat.asm b/src/engine/duel/ai/decks/general_no_retreat.asm new file mode 100644 index 0000000..20d84e3 --- /dev/null +++ b/src/engine/duel/ai/decks/general_no_retreat.asm @@ -0,0 +1,140 @@ +; acts just like a general deck AI except never retreats +AIActionTable_GeneralNoRetreat: ; 148dc (5:48dc) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 148e8 (5:48e8) + call AIDoTurn_GeneralNoRetreat + ret + +.start_duel ; 148ec (5:48ec) + call InitAIDuelVars + call AIPlayInitialBasicCards + ret + +.forced_switch ; 148f3 (5:48f3) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 148f7 (5:48f7) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 148fb (5:48fb) + call AIPickPrizeCards + ret + +AIDoTurn_GeneralNoRetreat: ; 148ff (5:48ff) +; initialize variables + call InitAITurnVars + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + farcall HandleAIAntiMewtwoDeckStrategy + jp nc, .try_attack +; handle Pkmn Powers + farcall HandleAIGoGoRainDanceEnergy + farcall HandleAIDamageSwap + farcall HandleAIPkmnPowers + ret c ; return if turn ended + farcall HandleAICowardice +; process Trainer cards +; phase 2 through 4. + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_03 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards +; play Pokemon from hand + call AIDecidePlayPokemonCard + ret c ; return if turn ended +; process Trainer cards +; phase 5 through 12. + ld a, AI_TRAINER_CARD_PHASE_05 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_06 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_08 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_11 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_12 + call AIProcessHandTrainerCards +; play Energy card if possible + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_1 + call AIProcessAndTryToPlayEnergy +.skip_energy_attach_1 +; play Pokemon from hand again + call AIDecidePlayPokemonCard +; handle Pkmn Powers again + farcall HandleAIDamageSwap + farcall HandleAIPkmnPowers + ret c ; return if turn ended + farcall HandleAIGoGoRainDanceEnergy + ld a, AI_ENERGY_TRANS_ATTACK + farcall HandleAIEnergyTrans +; process Trainer cards phases 13 and 15 + ld a, AI_TRAINER_CARD_PHASE_13 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_15 + call AIProcessHandTrainerCards +; if used Professor Oak, process new hand +; if not, then proceed to attack. + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_PROFESSOR_OAK + jr z, .try_attack + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_03 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_05 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_06 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_08 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_11 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_12 + call AIProcessHandTrainerCards + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_2 + call AIProcessAndTryToPlayEnergy +.skip_energy_attach_2 + call AIDecidePlayPokemonCard + farcall HandleAIDamageSwap + farcall HandleAIPkmnPowers + ret c ; return if turn ended + farcall HandleAIGoGoRainDanceEnergy + ld a, AI_TRAINER_CARD_PHASE_13 + call AIProcessHandTrainerCards + ; skip AI_TRAINER_CARD_PHASE_15 +.try_attack +; attack if possible, if not, +; finish turn without attacking. + call AIProcessAndTryToUseAttack + ret c ; return if turn ended + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret diff --git a/src/engine/duel/ai/decks/go_go_rain_dance.asm b/src/engine/duel/ai/decks/go_go_rain_dance.asm new file mode 100644 index 0000000..23547e2 --- /dev/null +++ b/src/engine/duel/ai/decks/go_go_rain_dance.asm @@ -0,0 +1,79 @@ +AIActionTable_GoGoRainDance: ; 14f8f (5:4f8f) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 14f9b (5:4f9b) + call AIMainTurnLogic + ret + +.start_duel ; 14f9f (5:4f9f) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 14fb0 (5:4fb0) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14fb4 (5:4fb4) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 14fb8 (5:4fb8) + call AIPickPrizeCards + ret + +.list_arena ; 14fbc (5:4fbc) + db LAPRAS + db HORSEA + db GOLDEEN + db SQUIRTLE + db $00 + +.list_bench ; 14fc1 (5:4fc1) + db SQUIRTLE + db HORSEA + db GOLDEEN + db LAPRAS + db $00 + +.list_retreat ; 14fc6 (5:4fc6) + ai_retreat SQUIRTLE, -3 + ai_retreat WARTORTLE, -2 + ai_retreat HORSEA, -1 + db $00 + +.list_energy ; 14fcd (5:4fcd) + ai_energy SQUIRTLE, 2, +0 + ai_energy WARTORTLE, 3, +0 + ai_energy BLASTOISE, 5, +0 + ai_energy GOLDEEN, 1, +0 + ai_energy SEAKING, 2, +0 + ai_energy HORSEA, 2, +0 + ai_energy SEADRA, 3, +0 + ai_energy LAPRAS, 3, +0 + db $00 + +.list_prize ; 14fe6 (5:4fe6) + db GAMBLER + db ENERGY_RETRIEVAL + db SUPER_ENERGY_RETRIEVAL + db BLASTOISE + db $00 + +.store_list_pointers ; 14feb (5:4feb) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/im_ronald.asm b/src/engine/duel/ai/decks/im_ronald.asm new file mode 100644 index 0000000..b002d83 --- /dev/null +++ b/src/engine/duel/ai/decks/im_ronald.asm @@ -0,0 +1,80 @@ +AIActionTable_ImRonald: ; 152bd (5:52bd) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 152c9 (5:52c9) + call AIMainTurnLogic + ret + +.start_duel ; 152cd (5:52cd) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 152de (5:52de) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 152e2 (5:52e2) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 152e6 (5:52e6) + call AIPickPrizeCards + ret + +.list_arena ; 152ea (5:52ea) + db LAPRAS + db SEEL + db CHARMANDER + db CUBONE + db SQUIRTLE + db GROWLITHE + db $00 + +.list_bench ; 152f1 (5:52f1) + db CHARMANDER + db SQUIRTLE + db SEEL + db CUBONE + db GROWLITHE + db LAPRAS + db $00 + +.list_retreat ; 152f8 (5:52f8) + db $00 + +.list_energy ; 152f9 (5:52f9) + ai_energy CHARMANDER, 3, +0 + ai_energy CHARMELEON, 5, +0 + ai_energy GROWLITHE, 2, +0 + ai_energy ARCANINE2, 4, +0 + ai_energy SQUIRTLE, 2, +0 + ai_energy WARTORTLE, 3, +0 + ai_energy SEEL, 3, +0 + ai_energy DEWGONG, 4, +0 + ai_energy LAPRAS, 3, +0 + ai_energy CUBONE, 3, +0 + ai_energy MAROWAK1, 3, +0 + db $00 + +.list_prize ; 1531b (5:531b) + db LAPRAS + db $00 + +.store_list_pointers ; 1531d (5:531d) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/invincible_ronald.asm b/src/engine/duel/ai/decks/invincible_ronald.asm new file mode 100644 index 0000000..463560b --- /dev/null +++ b/src/engine/duel/ai/decks/invincible_ronald.asm @@ -0,0 +1,78 @@ +AIActionTable_InvincibleRonald: ; 153e8 (5:53e8) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 153f4 (5:53f4) + call AIMainTurnLogic + ret + +.start_duel ; 153f8 (5:53f8) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 15409 (5:5409) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 1540d (5:540d) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 15411 (5:5411) + call AIPickPrizeCards + ret + +.list_arena ; 15415 (5:5415) + db KANGASKHAN + db MAGMAR2 + db CHANSEY + db GEODUDE + db SCYTHER + db GRIMER + db $00 + +.list_bench ; 1541c (5:541c) + db GRIMER + db SCYTHER + db GEODUDE + db CHANSEY + db MAGMAR2 + db KANGASKHAN + db $00 + +.list_retreat ; 15423 (5:5423) + ai_retreat GRIMER, -1 + db $00 + +.list_energy ; 15426 (5:5426) + ai_energy GRIMER, 1, -1 + ai_energy MUK, 3, -1 + ai_energy SCYTHER, 4, +1 + ai_energy MAGMAR2, 2, +0 + ai_energy GEODUDE, 2, +0 + ai_energy GRAVELER, 3, +0 + ai_energy CHANSEY, 4, +0 + ai_energy KANGASKHAN, 4, -1 + db $00 + +.list_prize ; 1543f (5:543f) + db GAMBLER + db $00 + +.store_list_pointers ; 15441 (5:5441) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/legendary_articuno.asm b/src/engine/duel/ai/decks/legendary_articuno.asm new file mode 100644 index 0000000..6409330 --- /dev/null +++ b/src/engine/duel/ai/decks/legendary_articuno.asm @@ -0,0 +1,209 @@ +AIActionTable_LegendaryArticuno: ; 14c0b (5:4c0b) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 14c17 (5:4c17) + call AIDoTurn_LegendaryArticuno + ret + +.start_duel ; 14c1b (5:4c1b) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 14c2c (5:4c2c) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14c30 (5:4c30) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 14c34 (5:4c34) + call AIPickPrizeCards + ret + +.list_arena ; 14c38 (5:4c38) + db CHANSEY + db LAPRAS + db DITTO + db SEEL + db ARTICUNO1 + db ARTICUNO2 + db $00 + +.list_bench ; 14c3f (5:4c3f) + db ARTICUNO1 + db SEEL + db LAPRAS + db CHANSEY + db DITTO + db $00 + +.list_retreat ; 14c45 (5:4c45) + ai_retreat SEEL, -3 + ai_retreat DITTO, -3 + db $00 + +.list_energy ; 14c4a (5:4c4a) + ai_energy SEEL, 3, +1 + ai_energy DEWGONG, 4, +0 + ai_energy LAPRAS, 3, +0 + ai_energy ARTICUNO1, 4, +1 + ai_energy ARTICUNO2, 3, +0 + ai_energy CHANSEY, 0, -8 + ai_energy DITTO, 3, +0 + db $00 + +.list_prize ; 14c60 (5:4c60) + db GAMBLER + db ARTICUNO2 + db $00 + +.store_list_pointers ; 14c63 (5:4c63) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret + +; this routine handles how Legendary Articuno +; prioritizes playing energy cards to each Pokémon. +; first, it makes sure that all Lapras have at least +; 3 energy cards before moving on to Articuno, +; and then to Dewgong and Seel +ScoreLegendaryArticunoCards: ; 14c91 (5:4c91) + call SwapTurn + call CountPrizes + call SwapTurn + cp 3 + ret c + +; player prizes >= 3 +; if Lapras has more than half HP and +; can use second attack, check next for Articuno +; otherwise, check if Articuno or Dewgong +; have more than half HP and can use second attack +; and if so, the next Pokémon to check is Lapras + ld a, LAPRAS + call CheckForBenchIDAtHalfHPAndCanUseSecondAttack + jr c, .articuno + ld a, ARTICUNO1 + call CheckForBenchIDAtHalfHPAndCanUseSecondAttack + jr c, .lapras + ld a, DEWGONG + call CheckForBenchIDAtHalfHPAndCanUseSecondAttack + jr c, .lapras + jr .articuno + +; the following routines check for certain card IDs in bench +; and call RaiseAIScoreToAllMatchingIDsInBench if these are found. +; for Lapras, an additional check is made to its +; attached energy count, which skips calling the routine +; if this count is >= 3 +.lapras + ld a, LAPRAS + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank5 + jr nc, .articuno + ld e, a + call CountNumberOfEnergyCardsAttached + cp 3 + jr nc, .articuno + ld a, LAPRAS + call RaiseAIScoreToAllMatchingIDsInBench + ret + +.articuno + ld a, ARTICUNO1 + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank5 + jr nc, .dewgong + ld a, ARTICUNO1 + call RaiseAIScoreToAllMatchingIDsInBench + ret + +.dewgong + ld a, DEWGONG + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank5 + jr nc, .seel + ld a, DEWGONG + call RaiseAIScoreToAllMatchingIDsInBench + ret + +.seel + ld a, SEEL + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank5 + ret nc + ld a, SEEL + call RaiseAIScoreToAllMatchingIDsInBench + ret + +AIDoTurn_LegendaryArticuno: ; 14cf7 (5:4cf7) +; initialize variables + call InitAITurnVars + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + farcall HandleAIAntiMewtwoDeckStrategy + jp nc, .try_attack +; process Trainer cards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards +; play Pokemon from hand + call AIDecidePlayPokemonCard + ret c ; return if turn ended + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards +; play Energy card if possible + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_1 + call AIProcessAndTryToPlayEnergy +.skip_energy_attach_1 +; play Pokemon from hand again + call AIDecidePlayPokemonCard +; process Trainer cards phases 13 and 15 + ld a, AI_TRAINER_CARD_PHASE_13 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_15 + call AIProcessHandTrainerCards +; if used Professor Oak, process new hand + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_PROFESSOR_OAK + jr z, .try_attack + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + call AIDecidePlayPokemonCard + ret c ; return if turn ended + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_2 + call AIProcessAndTryToPlayEnergy +.skip_energy_attach_2 + call AIDecidePlayPokemonCard +.try_attack +; attack if possible, if not, +; finish turn without attacking. + call AIProcessAndTryToUseAttack + ret c ; return if turn ended + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret diff --git a/src/engine/duel/ai/decks/legendary_dragonite.asm b/src/engine/duel/ai/decks/legendary_dragonite.asm new file mode 100644 index 0000000..597f72c --- /dev/null +++ b/src/engine/duel/ai/decks/legendary_dragonite.asm @@ -0,0 +1,166 @@ +AIActionTable_LegendaryDragonite: ; 14d60 (05:4d60) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 14d6c (5:4d6c) + call AIDoTurn_LegendaryDragonite + ret + +.start_duel ; 14d70 (5:4d70) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 14d81 (5:4d81) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14d85 (5:4d85) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 14d89 (5:4d89) + call AIPickPrizeCards + ret + +.list_arena ; 14d8d (5:4d8d) + db KANGASKHAN + db LAPRAS + db CHARMANDER + db DRATINI + db MAGIKARP + db $00 + +.list_bench ; 14d93 (5:4d93) + db CHARMANDER + db MAGIKARP + db DRATINI + db LAPRAS + db KANGASKHAN + db $00 + +.list_retreat ; 14d99 (5:4d99) + ai_retreat CHARMANDER, -1 + ai_retreat MAGIKARP, -5 + db $00 + +.list_energy ; 14d9e (5:4d9e) + ai_energy CHARMANDER, 3, +1 + ai_energy CHARMELEON, 4, +1 + ai_energy CHARIZARD, 5, +0 + ai_energy MAGIKARP, 3, +1 + ai_energy GYARADOS, 4, -1 + ai_energy DRATINI, 2, +0 + ai_energy DRAGONAIR, 4, +0 + ai_energy DRAGONITE1, 3, -1 + ai_energy KANGASKHAN, 2, -2 + ai_energy LAPRAS, 3, +0 + db $00 + +.list_prize ; 14dbd (5:4dbd) + db GAMBLER + db DRAGONITE1 + db KANGASKHAN + db $00 + +.store_list_pointers ; 14dc1 (5:4dc1) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret + +AIDoTurn_LegendaryDragonite: ; 14def (5:4def) +; initialize variables + call InitAITurnVars + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + farcall HandleAIAntiMewtwoDeckStrategy + jp nc, .try_attack +; process Trainer cards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards +; play Pokemon from hand + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_11 + call AIProcessHandTrainerCards +; play Energy card if possible + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_1 + +; if Arena card is Kangaskhan and doesn't +; have Energy cards attached, try attaching from hand. +; otherwise run normal AI energy attach routine. + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, KANGASKHAN + cp e + jr nz, .attach_normally + call CreateEnergyCardListFromHand + jr c, .skip_energy_attach_1 + ld e, PLAY_AREA_ARENA + call CountNumberOfEnergyCardsAttached + or a + jr nz, .attach_normally + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call AITryToPlayEnergyCard + jr c, .skip_energy_attach_1 +.attach_normally + call AIProcessAndTryToPlayEnergy + +.skip_energy_attach_1 +; play Pokemon from hand again + call AIDecidePlayPokemonCard + ld a, AI_TRAINER_CARD_PHASE_15 + call AIProcessHandTrainerCards +; if used Professor Oak, process new hand +; if not, then proceed to attack. + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_PROFESSOR_OAK + jr z, .try_attack + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_11 + call AIProcessHandTrainerCards + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach_2 + call AIProcessAndTryToPlayEnergy +.skip_energy_attach_2 + call AIDecidePlayPokemonCard +.try_attack +; attack if possible, if not, +; finish turn without attacking. + call AIProcessAndTryToUseAttack + ret c ; return if turn ended + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret diff --git a/src/engine/duel/ai/decks/legendary_moltres.asm b/src/engine/duel/ai/decks/legendary_moltres.asm new file mode 100644 index 0000000..c2a3882 --- /dev/null +++ b/src/engine/duel/ai/decks/legendary_moltres.asm @@ -0,0 +1,176 @@ +AIActionTable_LegendaryMoltres: ; 149e8 (05:49e8) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 149f4 (5:49f4) + call AIDoTurn_LegendaryMoltres + ret + +.start_duel ; 149f8 (5:49f8) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc ; Play Area set up was successful + call AIPlayInitialBasicCards + ret + +.forced_switch ; 14a09 (5:4a09) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14a0d (5:4a0d) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 14a11 (5:4a11) + call AIPickPrizeCards + ret + +.list_arena ; 14a15 (5:4a15) + db MAGMAR2 + db GROWLITHE + db VULPIX + db MAGMAR1 + db MOLTRES1 + db MOLTRES2 + db $00 + +.list_bench ; 14a1c (5:4a1c) + db MOLTRES1 + db VULPIX + db GROWLITHE + db MAGMAR2 + db MAGMAR1 + db $00 + +.list_play_hand ; 14a22 (5:4a22) + db MOLTRES2 + db MOLTRES1 + db VULPIX + db GROWLITHE + db MAGMAR2 + db MAGMAR1 + db $00 + +.list_retreat ; 14a29 (5:4a29) + ai_retreat GROWLITHE, -5 + ai_retreat VULPIX, -5 + db $00 + +.list_energy ; 14a2e (5:4a2e) + ai_energy VULPIX, 3, +0 + ai_energy NINETALES2, 3, +1 + ai_energy GROWLITHE, 3, +1 + ai_energy ARCANINE2, 4, +1 + ai_energy MAGMAR1, 4, -1 + ai_energy MAGMAR2, 1, -1 + ai_energy MOLTRES2, 3, +2 + ai_energy MOLTRES1, 4, +2 + db $00 + +.list_prize ; 14a47 (5:4a47) + db ENERGY_REMOVAL + db MOLTRES2 + db $00 + +.store_list_pointers ; 14a4a (5:4a4a) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_play_hand + store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret + +AIDoTurn_LegendaryMoltres: ; 14a81 (5:4a81) +; initialize variables + call InitAITurnVars + farcall HandleAIAntiMewtwoDeckStrategy + jp nc, .try_attack +; process Trainer cards +; phase 2 through 4. + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards + +; check if AI can play Moltres2 +; from hand and if so, play it. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr nc, .skip_moltres ; skip if bench is full + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE - 9 + jr nc, .skip_moltres ; skip if cards in deck <= 9 + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .skip_moltres ; skip if Muk in play + ld a, MOLTRES2 + call LookForCardIDInHandList_Bank5 + jr nc, .skip_moltres ; skip if no Moltres2 in hand + ldh [hTemp_ffa0], a + ld a, OPPACTION_PLAY_BASIC_PKMN + bank1call AIMakeDecision + +.skip_moltres +; play Pokemon from hand + call AIDecidePlayPokemonCard + ret c ; return if turn ended +; process Trainer cards + ld a, AI_TRAINER_CARD_PHASE_05 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_11 + call AIProcessHandTrainerCards +; play Energy card if possible + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_attach_energy + +; if Magmar2 is the Arena card and has no energy attached, +; try attaching an energy card to it from the hand. +; otherwise, run normal AI energy attach routine. + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, MAGMAR2 + cp e + jr nz, .attach_normally + ; Magmar2 is the Arena card + call CreateEnergyCardListFromHand + jr c, .skip_attach_energy + ld e, PLAY_AREA_ARENA + call CountNumberOfEnergyCardsAttached + or a + jr nz, .attach_normally + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + call AITryToPlayEnergyCard + jr c, .skip_attach_energy + +.attach_normally +; play Energy card if possible + call AIProcessAndTryToPlayEnergy +.skip_attach_energy +; try playing Pokemon cards from hand again + call AIDecidePlayPokemonCard + ld a, AI_TRAINER_CARD_PHASE_13 + call AIProcessHandTrainerCards + +.try_attack +; attack if possible, if not, +; finish turn without attacking. + call AIProcessAndTryToUseAttack + ret c + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret diff --git a/src/engine/duel/ai/decks/legendary_ronald.asm b/src/engine/duel/ai/decks/legendary_ronald.asm new file mode 100644 index 0000000..3356838 --- /dev/null +++ b/src/engine/duel/ai/decks/legendary_ronald.asm @@ -0,0 +1,203 @@ +AIActionTable_LegendaryRonald: ; 1546f (5:546f) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 1547b (5:547b) + call AIDoTurn_LegendaryRonald + ret + +.start_duel ; 1547f (5:547f) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 15490 (5:5490) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 15494 (5:5494) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 15498 (5:5498) + call AIPickPrizeCards + ret + +.list_arena ; 1549c (5:549c) + db KANGASKHAN + db DRATINI + db EEVEE + db ZAPDOS3 + db ARTICUNO2 + db MOLTRES2 + db $00 + +.list_bench ; 154a3 (5:54a3) + db KANGASKHAN + db DRATINI + db EEVEE + db $00 + +.list_play_hand ; 154a7 (5:54a7) + db MOLTRES2 + db ZAPDOS3 + db KANGASKHAN + db DRATINI + db EEVEE + db ARTICUNO2 + db $00 + +.list_retreat ; 154ae (5:54ae) + ai_retreat EEVEE, -2 + db $00 + +.list_energy ; 154b1 (5:54b1) + ai_energy FLAREON1, 3, +0 + ai_energy MOLTRES2, 3, +0 + ai_energy VAPOREON1, 3, +0 + ai_energy ARTICUNO2, 0, -8 + ai_energy JOLTEON1, 4, +0 + ai_energy ZAPDOS3, 0, -8 + ai_energy KANGASKHAN, 4, -1 + ai_energy EEVEE, 3, +0 + ai_energy DRATINI, 3, +0 + ai_energy DRAGONAIR, 4, +0 + ai_energy DRAGONITE1, 3, +0 + db $00 + +.list_prize ; 154d3 (5:54d3) + db MOLTRES2 + db ARTICUNO2 + db ZAPDOS3 + db DRAGONITE1 + db GAMBLER + db $00 + +.store_list_pointers ; 154d9 (5:54d9) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_play_hand + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret + +AIDoTurn_LegendaryRonald: ; 15507 (5:5507) +; initialize variables + call InitAITurnVars +; process Trainer cards + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards + +; check if AI can play Moltres2 +; from hand and if so, play it. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr nc, .skip_moltres_1 ; skip if bench is full + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE - 9 + jr nc, .skip_moltres_1 ; skip if cards in deck <= 9 + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .skip_moltres_1 ; skip if Muk in play + ld a, MOLTRES2 + call LookForCardIDInHandList_Bank5 + jr nc, .skip_moltres_1 ; skip if no Moltres2 in hand + ldh [hTemp_ffa0], a + ld a, OPPACTION_PLAY_BASIC_PKMN + bank1call AIMakeDecision + +.skip_moltres_1 +; play Pokemon from hand + call AIDecidePlayPokemonCard + ret c ; return if turn ended +; process Trainer cards + ld a, AI_TRAINER_CARD_PHASE_05 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards +; play Energy card if possible + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_attach_energy_1 + call AIProcessAndTryToPlayEnergy +.skip_attach_energy_1 +; try playing Pokemon cards from hand again + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_15 +; if used Professor Oak, process new hand +; if not, then proceed to attack. + call AIProcessHandTrainerCards + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_PROFESSOR_OAK + jr z, .try_attack + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_02 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards + +; check if AI can play Moltres2 +; from hand and if so, play it. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr nc, .skip_moltres_2 ; skip if bench is full + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE - 9 + jr nc, .skip_moltres_2 ; skip if cards in deck <= 9 + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .skip_moltres_2 ; skip if Muk in play + ld a, MOLTRES2 + call LookForCardIDInHandList_Bank5 + jr nc, .skip_moltres_2 ; skip if no Moltres2 in hand + ldh [hTemp_ffa0], a + ld a, OPPACTION_PLAY_BASIC_PKMN + bank1call AIMakeDecision + +.skip_moltres_2 + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_05 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_attach_energy_2 + call AIProcessAndTryToPlayEnergy +.skip_attach_energy_2 + call AIDecidePlayPokemonCard + ret c ; return if turn ended +.try_attack +; attack if possible, if not, +; finish turn without attacking. + call AIProcessAndTryToUseAttack + ret c ; return if turn ended + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret diff --git a/src/engine/duel/ai/decks/legendary_zapdos.asm b/src/engine/duel/ai/decks/legendary_zapdos.asm new file mode 100644 index 0000000..cc99f0c --- /dev/null +++ b/src/engine/duel/ai/decks/legendary_zapdos.asm @@ -0,0 +1,153 @@ +AIActionTable_LegendaryZapdos: ; 14b0f (05:4b0f) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 14b1b (5:4b1b) + call AIDoTurn_LegendaryZapdos + ret + +.start_duel ; 14b1f (5:4b1f) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 14b30 (5:4b30) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14b34 (5:4b34) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 14b38 (5:4b38) + call AIPickPrizeCards + ret + +.list_arena ; 14b3c (5:4b3c) + db ELECTABUZZ2 + db VOLTORB + db EEVEE + db ZAPDOS1 + db ZAPDOS2 + db ZAPDOS3 + db $00 + +.list_bench ; 14b43 (5:4b43) + db ZAPDOS2 + db ZAPDOS1 + db EEVEE + db VOLTORB + db ELECTABUZZ2 + db $00 + +.list_retreat ; 14b49 (5:4b49) + ai_retreat EEVEE, -5 + ai_retreat VOLTORB, -5 + ai_retreat ELECTABUZZ2, -5 + db $00 + +.list_energy ; 14b50 (5:4b50) + ai_energy VOLTORB, 1, -1 + ai_energy ELECTRODE1, 3, +0 + ai_energy ELECTABUZZ2, 2, -1 + ai_energy JOLTEON2, 3, +1 + ai_energy ZAPDOS1, 4, +2 + ai_energy ZAPDOS2, 4, +2 + ai_energy ZAPDOS3, 3, +1 + ai_energy EEVEE, 3, +0 + db $00 + +.list_prize ; 14b69 (5:4b69) + db GAMBLER + db ZAPDOS3 + db $00 + +.store_list_pointers ; 14b6c (5:4b6c) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret + +AIDoTurn_LegendaryZapdos: ; 14b9a (5:4b9a) +; initialize variables + call InitAITurnVars + farcall HandleAIAntiMewtwoDeckStrategy + jp nc, .try_attack +; process Trainer cards + ld a, AI_TRAINER_CARD_PHASE_01 + call AIProcessHandTrainerCards + ld a, AI_TRAINER_CARD_PHASE_04 + call AIProcessHandTrainerCards +; play Pokemon from hand + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_07 + call AIProcessHandTrainerCards + call AIProcessRetreat + ld a, AI_TRAINER_CARD_PHASE_10 + call AIProcessHandTrainerCards +; play Energy card if possible. + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .skip_energy_attach + +; if Arena card is Voltorb and there's Electrode1 in hand, +; or if it's Electabuzz, try attaching Energy card +; to the Arena card if it doesn't have any energy attached. +; Otherwise if Energy card is not needed, +; go through normal AI energy attach routine. + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, VOLTORB + cp e + jr nz, .check_electabuzz + ld a, ELECTRODE1 + call LookForCardIDInHandList_Bank5 + jr nc, .attach_normally + jr .voltorb_or_electabuzz +.check_electabuzz + ld a, ELECTABUZZ2 + cp e + jr nz, .attach_normally + +.voltorb_or_electabuzz + call CreateEnergyCardListFromHand + jr c, .skip_energy_attach + ld e, PLAY_AREA_ARENA + call CountNumberOfEnergyCardsAttached + or a + jr nz, .attach_normally + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + call AITryToPlayEnergyCard + jr c, .skip_energy_attach + +.attach_normally + call AIProcessAndTryToPlayEnergy + +.skip_energy_attach +; play Pokemon from hand again + call AIDecidePlayPokemonCard + ret c ; return if turn ended + ld a, AI_TRAINER_CARD_PHASE_13 + call AIProcessHandTrainerCards +.try_attack +; attack if possible, if not, +; finish turn without attacking. + call AIProcessAndTryToUseAttack + ret c ; return if turn ended + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret diff --git a/src/engine/duel/ai/decks/powerful_ronald.asm b/src/engine/duel/ai/decks/powerful_ronald.asm new file mode 100644 index 0000000..096fbea --- /dev/null +++ b/src/engine/duel/ai/decks/powerful_ronald.asm @@ -0,0 +1,92 @@ +AIActionTable_PowerfulRonald: ; 1534b (5:534b) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 15357 (5:5357) + call AIMainTurnLogic + ret + +.start_duel ; 1535b (5:535b) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 1536c (5:536c) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 15370 (5:5370) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 15374 (5:5374) + call AIPickPrizeCards + ret + +.list_arena ; 15378 (5:5378) + db KANGASKHAN + db ELECTABUZZ2 + db HITMONCHAN + db MR_MIME + db LICKITUNG + db HITMONLEE + db TAUROS + db JYNX + db MEWTWO1 + db DODUO + db $00 + +.list_bench ; 15383 (5:5383) + db KANGASKHAN + db HITMONLEE + db HITMONCHAN + db TAUROS + db DODUO + db JYNX + db MEWTWO1 + db ELECTABUZZ2 + db MR_MIME + db LICKITUNG + db $00 + +.list_retreat ; 1538e (5:538e) + ai_retreat KANGASKHAN, -1 + ai_retreat DODUO, -1 + ai_retreat DODRIO, -1 + db $00 + +.list_energy ; 15395 (5:5395) + ai_energy ELECTABUZZ2, 2, +1 + ai_energy HITMONLEE, 3, +1 + ai_energy HITMONCHAN, 3, +1 + ai_energy MR_MIME, 2, +0 + ai_energy JYNX, 3, +0 + ai_energy MEWTWO1, 2, +0 + ai_energy DODUO, 3, -1 + ai_energy DODRIO, 3, -1 + ai_energy LICKITUNG, 2, +0 + ai_energy KANGASKHAN, 4, -1 + ai_energy TAUROS, 3, +0 + db $00 + +.list_prize ; 153b7 (5:53b7) + db GAMBLER + db ENERGY_REMOVAL + db $00 + +.store_list_pointers ; 153ba (5:53ba) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/rock_crusher.asm b/src/engine/duel/ai/decks/rock_crusher.asm new file mode 100644 index 0000000..41a50fa --- /dev/null +++ b/src/engine/duel/ai/decks/rock_crusher.asm @@ -0,0 +1,74 @@ +AIActionTable_RockCrusher: ; 14f0e (5:4f0e) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 14f1a (5:4f1a) + call AIMainTurnLogic + ret + +.start_duel ; 14f1e (5:4f1e) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 14f2f (5:4f2f) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 14f33 (5:4f33) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 14f37 (5:4f37) + call AIPickPrizeCards + ret + +.list_arena ; 14f3b (5:4f3b) + db RHYHORN + db ONIX + db GEODUDE + db DIGLETT + db $00 + +.list_bench ; 14f40 (5:4f40) + db DIGLETT + db GEODUDE + db RHYHORN + db ONIX + db $00 + +.list_retreat ; 14f45 (5:4f45) + ai_retreat DIGLETT, -1 + db $00 + +.list_energy ; 14f48 (5:4f48) + ai_energy DIGLETT, 3, +1 + ai_energy DUGTRIO, 4, +0 + ai_energy GEODUDE, 2, +1 + ai_energy GRAVELER, 3, +0 + ai_energy GOLEM, 4, +0 + ai_energy ONIX, 2, -1 + ai_energy RHYHORN, 3, +0 + db $00 + +.list_prize ; 14f5e (5:4f5e) + db ENERGY_REMOVAL + db RHYHORN + db $00 + +.store_list_pointers ; 14f61 (5:4f61) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/sams_practice.asm b/src/engine/duel/ai/decks/sams_practice.asm new file mode 100644 index 0000000..dddce61 --- /dev/null +++ b/src/engine/duel/ai/decks/sams_practice.asm @@ -0,0 +1,205 @@ +; AI for Sam's practice duel, which handles his scripted actions. +; will act as a normal duelist AI after turn 7. +AIActionTable_SamPractice: ; 147bd (05:47bd) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 147c9 (5:47c9) + call IsAIPracticeScriptedTurn + jr nc, .scripted_1 +; not scripted, use AI main turn logic + call AIMainTurnLogic + ret +.scripted_1 ; use scripted actions instead + call AIPerformScriptedTurn + ret + +.start_duel ; 147d6 (5:47d6) + call SetSamsStartingPlayArea + ret + +.forced_switch ; 147da (5:47da) + call IsAIPracticeScriptedTurn + jr nc, .scripted_2 + call AIDecideBenchPokemonToSwitchTo + ret +.scripted_2 + call PickRandomBenchPokemon + ret + +.ko_switch: ; 147e7 (5:47e7) + call IsAIPracticeScriptedTurn + jr nc, .scripted_3 + call AIDecideBenchPokemonToSwitchTo + ret +.scripted_3 + call GetPlayAreaLocationOfRaticateOrRattata + ret + +.take_prize: ; 147f4 (5:47f4) + call AIPickPrizeCards + ret + +; returns carry if number of turns +; the AI has taken >= 7. +; used to know whether AI Sam is still +; doing scripted turns. +IsAIPracticeScriptedTurn: ; 147f8 (5:47f8) + ld a, [wDuelTurns] + srl a + cp 7 + ccf + ret + +; places one Machop from the hand to the Play Area +; and sets the number of prizes to 2. +SetSamsStartingPlayArea: ; 14801 (5:4801) + call CreateHandCardList + ld hl, wDuelTempList +.loop_hand + ld a, [hli] + ldh [hTempCardIndex_ff98], a + cp $ff + ret z + call LoadCardDataToBuffer1_FromDeckIndex + cp MACHOP + jr nz, .loop_hand + ldh a, [hTempCardIndex_ff98] + call PutHandPokemonCardInPlayArea + ld a, 2 + ld [wDuelInitialPrizes], a + ret + +; outputs in a Play Area location of Raticate or Rattata +; in the Bench. If neither is found, just output PLAY_AREA_BENCH_1. +GetPlayAreaLocationOfRaticateOrRattata: ; 1481f (5:481f) + ld a, RATICATE + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank5 + cp $ff + jr nz, .found + ld a, RATTATA + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank5 + cp $ff + jr nz, .found + ld a, PLAY_AREA_BENCH_1 +.found + ldh [hTempPlayAreaLocation_ff9d], a + ret + +; has AI execute some scripted actions depending on Duel turn. +AIPerformScriptedTurn: ; 1483a (5:483a) + ld a, [wDuelTurns] + srl a + ld hl, .scripted_actions_list + call JumpToFunctionInTable + +; always attack with Arena card's first attack. +; if it's unusable end turn without attacking. + xor a + ldh [hTempPlayAreaLocation_ff9d], a + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + jr c, .unusable + call AITryUseAttack + ret + +.unusable + ld a, OPPACTION_FINISH_NO_ATTACK + bank1call AIMakeDecision + ret + +.scripted_actions_list ; 1485a (05:485a) + dw .turn_1 + dw .turn_2 + dw .turn_3 + dw .turn_4 + dw .turn_5 + dw .turn_6 + dw .turn_7 + +.turn_1 ; 14868 (5:4868) + ld d, MACHOP + ld e, FIGHTING_ENERGY + call AIAttachEnergyInHandToCardInPlayArea + ret + +.turn_2 ; 14870 (5:4870) + ld a, RATTATA + call LookForCardIDInHandList_Bank5 + ldh [hTemp_ffa0], a + ld a, OPPACTION_PLAY_BASIC_PKMN + bank1call AIMakeDecision + ld d, RATTATA + ld e, FIGHTING_ENERGY + call AIAttachEnergyInHandToCardInPlayArea + ret + +.turn_3 ; 14884 (5:4884) + ld a, RATTATA + ld b, PLAY_AREA_ARENA + call LookForCardIDInPlayArea_Bank5 + ldh [hTempPlayAreaLocation_ffa1], a + ld a, RATICATE + call LookForCardIDInHandList_Bank5 + ldh [hTemp_ffa0], a + ld a, OPPACTION_EVOLVE_PKMN + bank1call AIMakeDecision + ld d, RATICATE + ld e, LIGHTNING_ENERGY + call AIAttachEnergyInHandToCardInPlayArea + ret + +.turn_4 ; 148a1 (5:48a1) + ld d, RATICATE + ld e, LIGHTNING_ENERGY + call AIAttachEnergyInHandToCardInPlayArea + ret + +.turn_5 ; 148a9 (5:48a9) + ld a, MACHOP + call LookForCardIDInHandList_Bank5 + ldh [hTemp_ffa0], a + ld a, OPPACTION_PLAY_BASIC_PKMN + bank1call AIMakeDecision + ld d, MACHOP + ld e, FIGHTING_ENERGY + call AIAttachEnergyInHandToCardInBench + +; this is a bug, it's attempting to compare a card ID with a deck index. +; the intention was to change the card to switch to depending on whether +; the first Machop was KO'd at this point in the Duel or not. +; because of the buggy comparison, this will always jump the +; 'inc a' instruction and switch to PLAY_AREA_BENCH_1. +; in a normal Practice Duel following Dr. Mason's instructions, +; this will always lead to the AI correctly switching Raticate with Machop, +; but in case of a "Free" Duel where the first Machop is not KO'd, +; the intention was to switch to PLAY_AREA_BENCH_2 instead. +; but due to 'inc a' always being skipped, it will switch to Raticate. + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + cp MACHOP ; wrong + ld a, PLAY_AREA_BENCH_1 + jr nz, .retreat + inc a ; PLAY_AREA_BENCH_2 + +.retreat + call AITryToRetreat + ret + +.turn_6 ; 148cc (5:48cc) + ld d, MACHOP + ld e, FIGHTING_ENERGY + call AIAttachEnergyInHandToCardInPlayArea + ret + +.turn_7 ; 148d4 (5:48d4) + ld d, MACHOP + ld e, FIGHTING_ENERGY + call AIAttachEnergyInHandToCardInPlayArea + ret diff --git a/src/engine/duel/ai/decks/strange_psyshock.asm b/src/engine/duel/ai/decks/strange_psyshock.asm new file mode 100644 index 0000000..ef378b0 --- /dev/null +++ b/src/engine/duel/ai/decks/strange_psyshock.asm @@ -0,0 +1,81 @@ +AIActionTable_StrangePsyshock: ; 15122 (5:5122) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 1512e (5:512e) + call AIMainTurnLogic + ret + +.start_duel ; 15132 (5:5132) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 15143 (5:5143) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 15147 (5:5147) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 1514b (5:514b) + call AIPickPrizeCards + ret + +.list_arena ; 1514f (5:514f) + db KANGASKHAN + db CHANSEY + db SNORLAX + db MR_MIME + db ABRA + db $00 + +.list_bench ; 15155 (5:5155) + db ABRA + db MR_MIME + db KANGASKHAN + db SNORLAX + db CHANSEY + db $00 + +.list_retreat ; 1515b (5:515b) + ai_retreat ABRA, -3 + ai_retreat SNORLAX, -3 + ai_retreat KANGASKHAN, -1 + ai_retreat CHANSEY, -1 + db $00 + +.list_energy ; 15164 (5:5164) + ai_energy ABRA, 3, +1 + ai_energy KADABRA, 3, +0 + ai_energy ALAKAZAM, 3, +0 + ai_energy MR_MIME, 2, +0 + ai_energy CHANSEY, 2, -2 + ai_energy KANGASKHAN, 4, -2 + ai_energy SNORLAX, 0, -8 + db $00 + +.list_prize ; 1517a (5:517a) + db GAMBLER + db MR_MIME + db ALAKAZAM + db SWITCH + db $00 + +.store_list_pointers ; 1517f (5:517f) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/unreferenced.asm b/src/engine/duel/ai/decks/unreferenced.asm new file mode 100644 index 0000000..8722a27 --- /dev/null +++ b/src/engine/duel/ai/decks/unreferenced.asm @@ -0,0 +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 diff --git a/src/engine/duel/ai/decks/wonders_of_science.asm b/src/engine/duel/ai/decks/wonders_of_science.asm new file mode 100644 index 0000000..706a7e6 --- /dev/null +++ b/src/engine/duel/ai/decks/wonders_of_science.asm @@ -0,0 +1,77 @@ +AIActionTable_WondersOfScience: ; 151ad (5:51ad) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 151b9 (5:51b9) + call AIMainTurnLogic + ret + +.start_duel ; 151bd (5:51bd) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 151ce (5:51ce) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 151d2 (5:51d2) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 151d6 (5:51d6) + call AIPickPrizeCards + ret + +.list_arena ; 151da (5:51da) + db MEWTWO1 + db MEWTWO3 + db MEWTWO2 + db GRIMER + db KOFFING + db PORYGON + db $00 + +.list_bench ; 151e1 (5:51e1) + db GRIMER + db KOFFING + db MEWTWO3 + db MEWTWO2 + db MEWTWO1 + db PORYGON + db $00 + +.list_retreat ; 151e8 (5:51e8) + db $00 + +.list_energy ; 151e9 (5:51e9) + ai_energy GRIMER, 3, +0 + ai_energy MUK, 4, +0 + ai_energy KOFFING, 2, +0 + ai_energy WEEZING, 3, +0 + ai_energy MEWTWO1, 2, -1 + ai_energy MEWTWO3, 2, -1 + ai_energy MEWTWO2, 2, -1 + ai_energy PORYGON, 2, -1 + db $00 + +.list_prize ; 15202 (5:5202) + db MUK + db $00 + +.store_list_pointers ; 15204 (5:5204) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/decks/zapping_selfdestruct.asm b/src/engine/duel/ai/decks/zapping_selfdestruct.asm new file mode 100644 index 0000000..da5e7c6 --- /dev/null +++ b/src/engine/duel/ai/decks/zapping_selfdestruct.asm @@ -0,0 +1,75 @@ +AIActionTable_ZappingSelfdestruct: ; 15019 (5:5019) + dw .do_turn ; unused + dw .do_turn + dw .start_duel + dw .forced_switch + dw .ko_switch + dw .take_prize + +.do_turn ; 15025 (5:5025) + call AIMainTurnLogic + ret + +.start_duel ; 15029 (5:5029) + call InitAIDuelVars + call .store_list_pointers + call SetUpBossStartingHandAndDeck + call TrySetUpBossStartingPlayArea + ret nc + call AIPlayInitialBasicCards + ret + +.forced_switch ; 1503a (5:503a) + call AIDecideBenchPokemonToSwitchTo + ret + +.ko_switch ; 1503e (5:503e) + call AIDecideBenchPokemonToSwitchTo + ret + +.take_prize ; 15042 (5:5042) + call AIPickPrizeCards + ret + +.list_arena ; 15046 (5:5046) + db KANGASKHAN + db ELECTABUZZ2 + db TAUROS + db MAGNEMITE1 + db VOLTORB + db $00 + +.list_bench ; 1504c (5:504c) + db MAGNEMITE1 + db VOLTORB + db ELECTABUZZ2 + db TAUROS + db KANGASKHAN + db $00 + +.list_retreat ; 15052 (5:5052) + ai_retreat VOLTORB, -1 + db $00 + +.list_energy ; 15055 (5:5055) + ai_energy MAGNEMITE1, 3, +1 + ai_energy MAGNETON1, 4, +0 + ai_energy VOLTORB, 3, +1 + ai_energy ELECTRODE1, 3, +0 + ai_energy ELECTABUZZ2, 1, +0 + ai_energy KANGASKHAN, 2, -2 + ai_energy TAUROS, 3, +0 + db $00 + +.list_prize ; 1506b (5:506b) + db KANGASKHAN + db $00 + +.store_list_pointers ; 1506d (5:506d) + store_list_pointer wAICardListAvoidPrize, .list_prize + store_list_pointer wAICardListArenaPriority, .list_arena + store_list_pointer wAICardListBenchPriority, .list_bench + store_list_pointer wAICardListPlayFromHandPriority, .list_bench + ; missing store_list_pointer wAICardListRetreatBonus, .list_retreat + store_list_pointer wAICardListEnergyBonus, .list_energy + ret diff --git a/src/engine/duel/ai/energy.asm b/src/engine/duel/ai/energy.asm new file mode 100644 index 0000000..ce8c037 --- /dev/null +++ b/src/engine/duel/ai/energy.asm @@ -0,0 +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 diff --git a/src/engine/duel/ai/hand_pokemon.asm b/src/engine/duel/ai/hand_pokemon.asm new file mode 100644 index 0000000..27a4176 --- /dev/null +++ b/src/engine/duel/ai/hand_pokemon.asm @@ -0,0 +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 diff --git a/src/engine/duel/ai/init.asm b/src/engine/duel/ai/init.asm new file mode 100644 index 0000000..33132cf --- /dev/null +++ b/src/engine/duel/ai/init.asm @@ -0,0 +1,98 @@ +InitAIDuelVars: ; 15636 (5:5636) + ld a, wAIDuelVarsEnd - wAIDuelVars + ld hl, wAIDuelVars + call ClearMemory_Bank5 + ld a, 5 + ld [wAIPokedexCounter], a + ld a, $ff + ld [wAIPeekedPrizes], 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 [wAITriedAttack], 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/duel/ai/pkmn_powers.asm b/src/engine/duel/ai/pkmn_powers.asm new file mode 100644 index 0000000..8ae629a --- /dev/null +++ b/src/engine/duel/ai/pkmn_powers.asm @@ -0,0 +1,1228 @@ +; handle AI routines for Energy Trans. +; uses AI_ENERGY_TRANS_* constants as input: +; - AI_ENERGY_TRANS_RETREAT: transfers enough Grass Energy cards to +; Arena Pokemon for it to be able to pay the Retreat Cost; +; - AI_ENERGY_TRANS_ATTACK: transfers enough Grass Energy cards to +; Arena Pokemon for it to be able to use its second attack; +; - AI_ENERGY_TRANS_TO_BENCH: transfers all Grass Energy cards from +; Arena Pokemon to Bench in case Arena card will be KO'd. +HandleAIEnergyTrans: ; 2219b (8:619b) + ld [wce06], a + +; choose to randomly return + farcall AIChooseRandomlyNotToDoAction + ret c + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + ret z ; return if no Bench cards + + ld a, VENUSAUR2 + call CountPokemonIDInPlayArea + ret nc ; return if no Venusaur2 found in own Play Area + + ld a, MUK + call CountPokemonIDInBothPlayAreas + ret c ; return if Muk found in any Play Area + + ld a, [wce06] + cp AI_ENERGY_TRANS_RETREAT + jr z, .check_retreat + + cp AI_ENERGY_TRANS_TO_BENCH + jp z, AIEnergyTransTransferEnergyToBench + + ; AI_ENERGY_TRANS_ATTACK + call .CheckEnoughGrassEnergyCardsForAttack + ret nc + jr .TransferEnergyToArena + +.check_retreat + call .CheckEnoughGrassEnergyCardsForRetreatCost + ret nc + +; use Energy Trans to transfer number of Grass energy cards +; equal to input a from the Bench to the Arena card. +.TransferEnergyToArena + ld [wAINumberOfEnergyTransCards], a + +; look for Venusaur2 in Play Area +; so that its PKMN Power can be used. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + ld b, a +.loop_play_area + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff9f], a + call GetCardIDFromDeckIndex + ld a, e + cp VENUSAUR2 + jr z, .use_pkmn_power + + ld a, b + or a + ret z ; return when finished Play Area loop + + dec b + jr .loop_play_area + +; use Energy Trans Pkmn Power +.use_pkmn_power + ld a, b + ldh [hTemp_ffa0], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + + xor a ; PLAY_AREA_ARENA + ldh [hAIEnergyTransPlayAreaLocation], a + ld a, [wAINumberOfEnergyTransCards] + ld d, a + +; look for Grass energy cards that +; are currently attached to a Bench card. + ld e, 0 +.loop_deck_locations + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + and %00011111 + cp CARD_LOCATION_BENCH_1 + jr c, .next_card + + and %00001111 + ldh [hTempPlayAreaLocation_ffa1], a + + ld a, e + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp GRASS_ENERGY + jr nz, .next_card + + ; store the deck index of energy card + ld a, e + ldh [hAIEnergyTransEnergyCard], a + + push de + ld d, 30 +.small_delay_loop + call DoFrame + dec d + jr nz, .small_delay_loop + + ld a, OPPACTION_6B15 + bank1call AIMakeDecision + pop de + dec d + jr z, .done_transfer + +.next_card + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop_deck_locations + +; transfer is done, perform delay +; and return to main scene. +.done_transfer + ld d, 60 +.big_delay_loop + call DoFrame + dec d + jr nz, .big_delay_loop + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + ret + +; checks if the Arena card needs energy for its second attack, +; and if it does, return carry if transferring Grass energy from Bench +; would be enough to use it. Outputs number of energy cards needed in a. +.CheckEnoughGrassEnergyCardsForAttack ; 22246 (8:6246) + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp EXEGGUTOR + jr z, .is_exeggutor + + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + farcall CheckEnergyNeededForAttack + jr nc, .attack_false ; return if no energy needed + +; check if colorless energy is needed... + ld a, c + or a + jr nz, .count_if_enough + +; ...otherwise check if basic energy card is needed +; and it's grass energy. + ld a, b + or a + jr z, .attack_false + ld a, e + cp GRASS_ENERGY + jr nz, .attack_false + ld c, b + jr .count_if_enough + +.attack_false + or a + ret + +.count_if_enough +; if there's enough Grass energy cards in Bench +; to satisfy the attack energy cost, return carry. + push bc + call .CountGrassEnergyInBench + pop bc + cp c + jr c, .attack_false + ld a, c + scf + ret + +.is_exeggutor +; in case it's Exeggutor in Arena, return carry +; if there are any Grass energy cards in Bench. + call .CountGrassEnergyInBench + or a + jr z, .attack_false + + scf + ret + +; outputs in a the number of Grass energy cards +; currently attached to Bench cards. +.CountGrassEnergyInBench ; 22286 (8:6286) + lb de, 0, 0 +.count_loop + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + and %00011111 + cp CARD_LOCATION_BENCH_1 + jr c, .count_next + +; is in bench + ld a, e + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp GRASS_ENERGY + jr nz, .count_next + inc d +.count_next + inc e + ld a, DECK_SIZE + cp e + jr nz, .count_loop + ld a, d + ret + +; returns carry if there are enough Grass energy cards in Bench +; to satisfy the retreat cost of the Arena card. +; if so, output the number of energy cards still needed in a. +.CheckEnoughGrassEnergyCardsForRetreatCost ; 222a9 (8:62a9) + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + ld b, a + ld e, PLAY_AREA_ARENA + farcall CountNumberOfEnergyCardsAttached + cp b + jr nc, .retreat_false ; return if enough to retreat + +; see if there's enough Grass energy cards +; in the Bench to satisfy retreat cost + ld c, a + ld a, b + sub c + ld c, a + push bc + call .CountGrassEnergyInBench + pop bc + cp c + jr c, .retreat_false ; return if less cards than needed + +; output number of cards needed to retreat + ld a, c + scf + ret +.retreat_false + or a + ret + +; AI logic to determine whether to use Energy Trans Pkmn Power +; to transfer energy cards attached from the Arena Pokemon to +; some card in the Bench. +AIEnergyTransTransferEnergyToBench: ; 222ca (8:62ca) + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfDefendingPokemonCanKnockOut + ret nc ; return if Defending can't KO + +; processes attacks and see if any attack would be used by AI. +; if so, return. + farcall AIProcessButDontUseAttack + ret c + +; return if Arena card has no Grass energy cards attached. + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + GRASS] + or a + ret z + +; if no energy card attachment is needed, return. + farcall AIProcessButDontPlayEnergy_SkipEvolutionAndArena + ret nc + +; AI decided that an energy card is needed +; so look for Venusaur2 in Play Area +; so that its PKMN Power can be used. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + ld b, a +.loop_play_area + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff9f], a + ld [wAIVenusaur2DeckIndex], a + call GetCardIDFromDeckIndex + ld a, e + cp VENUSAUR2 + jr z, .use_pkmn_power + + ld a, b + or a + ret z ; return when Play Area loop is ended + + dec b + jr .loop_play_area + +; use Energy Trans Pkmn Power +.use_pkmn_power + ld a, b + ldh [hTemp_ffa0], a + ld [wAIVenusaur2PlayAreaLocation], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + +; loop for each energy cards that are going to be transferred. +.loop_energy + xor a + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wAIVenusaur2PlayAreaLocation] + ldh [hTemp_ffa0], a + + ; returns when Arena card has no Grass energy cards attached. + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + GRASS] + or a + jr z, .done_transfer + +; look for Grass energy cards that +; are currently attached to Arena card. + ld e, 0 +.loop_deck_locations + ld a, DUELVARS_CARD_LOCATIONS + add e + call GetTurnDuelistVariable + cp CARD_LOCATION_ARENA + jr nz, .next_card + + ld a, e + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp GRASS_ENERGY + jr nz, .next_card + + ; store the deck index of energy card + ld a, e + ldh [hAIEnergyTransEnergyCard], a + jr .transfer + +.next_card + inc e + ld a, DECK_SIZE + cp e + jr nz, .loop_deck_locations + jr .done_transfer + +.transfer +; get the Bench card location to transfer Grass energy card to. + farcall AIProcessButDontPlayEnergy_SkipEvolutionAndArena + jr nc, .done_transfer + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hAIEnergyTransPlayAreaLocation], a + + ld d, 30 +.small_delay_loop + call DoFrame + dec d + jr nz, .small_delay_loop + + ld a, [wAIVenusaur2DeckIndex] + ldh [hTempCardIndex_ff9f], a + ld d, a + ld e, FIRST_ATTACK_OR_PKMN_POWER + call CopyAttackDataAndDamage_FromDeckIndex + ld a, OPPACTION_6B15 + bank1call AIMakeDecision + jr .loop_energy + +; transfer is done, perform delay +; and return to main scene. +.done_transfer + ld d, 60 +.big_delay_loop + call DoFrame + dec d + jr nz, .big_delay_loop + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + ret + +; handles AI logic for using some Pkmn Powers. +; Pkmn Powers handled here are: +; - Heal; +; - Shift; +; - Peek; +; - Strange Behavior; +; - Curse. +; returns carry if turn ended. +HandleAIPkmnPowers: ; 2237f (8:637f) + ld a, MUK + call CountPokemonIDInBothPlayAreas + ccf + ret nc ; return no carry if Muk is in play + + farcall AIChooseRandomlyNotToDoAction + ccf + ret nc ; return no carry if AI randomly decides to + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, PLAY_AREA_ARENA + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + jr nz, .next_2 + +.loop_play_area + ld a, DUELVARS_ARENA_CARD + add c + call GetTurnDuelistVariable + ld [wce08], a + + push af + push bc + ld d, a + ld a, c + ldh [hTempPlayAreaLocation_ff9d], a + ld e, FIRST_ATTACK_OR_PKMN_POWER + call CopyAttackDataAndDamage_FromDeckIndex + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr z, .execute_effect + pop bc + jr .next_3 + +.execute_effect + ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2 + bank1call TryExecuteEffectCommandFunction + pop bc + jr c, .next_3 + +; TryExecuteEffectCommandFunction was successful, +; so check what Pkmn Power this is through card's ID. + pop af + call GetCardIDFromDeckIndex + ld a, e + push bc + +; check heal + cp VILEPLUME + jr nz, .check_shift + call HandleAIHeal + jr .next_1 +.check_shift + cp VENOMOTH + jr nz, .check_peek + call HandleAIShift + jr .next_1 +.check_peek + cp MANKEY + jr nz, .check_strange_behavior + call HandleAIPeek + jr .next_1 +.check_strange_behavior + cp SLOWBRO + jr nz, .check_curse + call HandleAIStrangeBehavior + jr .next_1 +.check_curse + cp GENGAR + jr nz, .next_1 + call z, HandleAICurse + jr c, .done + +.next_1 + pop bc +.next_2 + inc c + ld a, c + cp b + jr nz, .loop_play_area + ret + +.next_3 + pop af + jr .next_2 + +.done + pop bc + ret + +; checks whether AI uses Heal on Pokemon in Play Area. +; input: +; c = Play Area location (PLAY_AREA_*) of Vileplume. +HandleAIHeal: ; 22402 (8:6402) + ld a, c + ldh [hTemp_ffa0], a + call .CheckHealTarget + ret nc ; return if no target to heal + push af + ld a, [wce08] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + pop af + ldh [hPlayAreaEffectTarget], a + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + ret + +; finds a target suitable for AI to use Heal on. +; only heals Arena card if the Defending Pokemon +; cannot KO it after Heal is used. +; returns carry if target was found and outputs +; in a the Play Area location of that card. +.CheckHealTarget ; 22422 (8:6422) +; check if Arena card has any damage counters, +; if not, check Bench instead. + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + or a + jr z, .check_bench + + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfDefendingPokemonCanKnockOut + jr nc, .set_carry ; return carry if can't KO + ld d, a + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld h, a + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ; this seems useless since it was already + ; checked that Arena card has damage, + ; so card damage is at least 10. + cp 10 + 1 + jr c, .check_remaining + ld a, 10 + ; a = min(10, CardDamage) + +; checks if Defending Pokemon can still KO +; if Heal is used on this card. +; if Heal prevents KO, return carry. +.check_remaining + ld l, a + ld a, h ; load remaining HP + add l ; add 1 counter to account for heal + sub d ; subtract damage of strongest opponent attack + jr c, .check_bench + jr z, .check_bench + +.set_carry + xor a ; PLAY_AREA_ARENA + scf + ret + +; check Bench for Pokemon with damage counters +; and find the one with the most damage. +.check_bench + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + lb bc, 0, 0 + ld e, PLAY_AREA_BENCH_1 +.loop_bench + ld a, e + cp d + jr z, .done + push bc + call GetCardDamageAndMaxHP + pop bc + cp b + jr c, .next_bench + jr z, .next_bench + ld b, a ; store this damage + ld c, e ; store this Play Area location +.next_bench + inc e + jr .loop_bench + +; check if a Pokemon with damage counters was found +; in the Bench and, if so, return carry. +.done + ld a, c + or a + jr z, .not_found +; found + scf + ret +.not_found + or a + ret + +; checks whether AI uses Shift. +; input: +; c = Play Area location (PLAY_AREA_*) of Venomoth +HandleAIShift: ; 22476 (8:6476) + ld a, c + or a + ret nz ; return if Venomoth is not Arena card + + ldh [hTemp_ffa0], a + call GetArenaCardColor + call TranslateColorToWR + ld b, a + call SwapTurn + call GetArenaCardWeakness + ld [wAIDefendingPokemonWeakness], a + call SwapTurn + or a + ret z ; return if Defending Pokemon has no weakness + and b + ret nz ; return if Venomoth is already Defending card's weakness type + +; check whether there's a card in play with +; the same color as the Player's card weakness + call .CheckWhetherTurnDuelistHasColor + jr c, .found + call SwapTurn + call .CheckWhetherTurnDuelistHasColor + call SwapTurn + ret nc ; return if no color found + +.found + ld a, [wce08] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + +; converts WR_* to appropriate color + ld a, [wAIDefendingPokemonWeakness] + ld b, 0 +.loop_color + bit 7, a + jr nz, .done + inc b + rlca + jr .loop_color + +; use Pkmn Power effect +.done + ld a, b + ldh [hAIPkmnPowerEffectParam], a + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + ret + +; returns carry if turn Duelist has a Pokemon +; with same color as wAIDefendingPokemonWeakness. +.CheckWhetherTurnDuelistHasColor ; 224c6 (8:64c6) + ld a, [wAIDefendingPokemonWeakness] + ld b, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable +.loop_play_area + ld a, [hli] + cp $ff + jr z, .false + push bc + call GetCardIDFromDeckIndex + call GetCardType + ; in case this is a Mysterious Fossil or Clefairy Doll card, + ; AI might read the type of the card incorrectly here. + ; uncomment the following lines to account for this + ; cp TYPE_TRAINER + ; jr nz, .not_trainer + ; pop bc + ; jr .loop_play_area +; .not_trainer + call TranslateColorToWR + pop bc + and b + jr z, .loop_play_area +; true + scf + ret +.false + or a + ret + +; checks whether AI uses Peek. +; input: +; c = Play Area location (PLAY_AREA_*) of Mankey. +HandleAIPeek: ; 224e6 (8:64e6) + ld a, c + ldh [hTemp_ffa0], a + ld a, 50 + call Random + cp 3 + ret nc ; return 47 out of 50 times + +; choose what to use Peek on at random + ld a, 3 + call Random + or a + jr z, .check_ai_prizes + cp 2 + jr c, .check_player_hand + +; check Player's Deck + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetNonTurnDuelistVariable + cp DECK_SIZE - 1 + ret nc ; return if Player has one or no cards in Deck + ld a, AI_PEEK_TARGET_DECK + jr .use_peek + +.check_ai_prizes + ld a, DUELVARS_PRIZES + call GetTurnDuelistVariable + ld hl, wAIPeekedPrizes + and [hl] + ld [hl], a + or a + ret z ; return if no prizes + + ld c, a + ld b, $1 + ld d, 0 +.loop_prizes + ld a, c + and b + jr nz, .found_prize + sla b + inc d + jr .loop_prizes +.found_prize +; remove this prize's flag from the prize list +; and use Peek on first one in list (lowest bit set) + ld a, c + sub b + ld [hl], a + ld a, AI_PEEK_TARGET_PRIZE + add d + jr .use_peek + +.check_player_hand + call SwapTurn + call CreateHandCardList + call SwapTurn + or a + ret z ; return if no cards in Hand +; shuffle list and pick the first entry to Peek + ld hl, wDuelTempList + call CountCardsInDuelTempList + call ShuffleCards + ld a, [wDuelTempList] + or AI_PEEK_TARGET_HAND + +.use_peek + push af + ld a, [wce08] + ldh [hTempCardIndex_ff9f], 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 + ret + +; checks whether AI uses Strange Behavior. +; input: +; c = Play Area location (PLAY_AREA_*) of Slowbro. +HandleAIStrangeBehavior: ; 2255d (8:655d) + ld a, c + or a + ret z ; return if Slowbro is Arena card + + ldh [hTemp_ffa0], a + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + or a + ret z ; return if Arena card has no damage counters + + ld [wce06], a + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + sub 10 + ret z ; return if Slowbro has only 10 HP remaining + +; if Slowbro can't receive all damage counters, +; only transfer remaining HP - 10 damage + ld hl, wce06 + cp [hl] + jr c, .use_strange_behavior + ld a, [hl] ; can receive all damage counters + +.use_strange_behavior + push af + ld a, [wce08] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + xor a + ldh [hAIPkmnPowerEffectParam], a + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + pop af + +; loop counters chosen to transfer and use Pkmn Power + call ConvertHPToCounters + ld e, a +.loop_counters + ld d, 30 +.small_delay_loop + call DoFrame + dec d + jr nz, .small_delay_loop + push de + ld a, OPPACTION_6B15 + bank1call AIMakeDecision + pop de + dec e + jr nz, .loop_counters + +; return to main scene + ld d, 60 +.big_delay_loop + call DoFrame + dec d + jr nz, .big_delay_loop + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + ret + +; checks whether AI uses Curse. +; input: +; c = Play Area location (PLAY_AREA_*) of Gengar. +HandleAICurse: ; 225b5 (8:65b5) + ld a, c + ldh [hTemp_ffa0], a + +; loop Player's Play Area and checks their damage. +; finds the card with lowest remaining HP and +; stores its HP and its Play Area location + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + lb bc, 0, $ff + ld h, PLAY_AREA_ARENA + call SwapTurn +.loop_play_area_1 + push bc + call GetCardDamageAndMaxHP + pop bc + or a + jr z, .next_1 + + inc b + ld a, e + add DUELVARS_ARENA_CARD_HP + push hl + call GetTurnDuelistVariable + pop hl + cp c + jr nc, .next_1 + ; lower HP than one stored + ld c, a ; store this HP + ld h, e ; store this Play Area location + +.next_1 + inc e + ld a, e + cp d + jr nz, .loop_play_area_1 ; reached end of Play Area + + ld a, 1 + cp b + jr nc, .failed ; return if less than 2 cards with damage + +; card in Play Area with lowest HP remaining was found. +; look for another card to take damage counter from. + ld a, h + ldh [hTempRetreatCostCards], a + ld b, a + ld a, 10 + cp c + jr z, .hp_10_remaining + ; if has more than 10 HP remaining, + ; skip Arena card in choosing which + ; card to take damage counter from. + ld e, PLAY_AREA_BENCH_1 + jr .second_card + +.hp_10_remaining + ; if Curse can KO, then include + ; Player's Arena card to take + ; damage counter from. + ld e, PLAY_AREA_ARENA + +.second_card + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a +.loop_play_area_2 + ld a, e + cp b + jr z, .next_2 ; skip same Pokemon card + push bc + call GetCardDamageAndMaxHP + pop bc + jr nz, .use_curse ; has damage counters, choose this card +.next_2 + inc e + ld a, e + cp d + jr nz, .loop_play_area_2 + +.failed + call SwapTurn + or a + ret + +.use_curse + ld a, e + ldh [hAIPkmnPowerEffectParam], a + call SwapTurn + ld a, [wce08] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + ret + +; handles AI logic for Cowardice +HandleAICowardice: ; 2262d (8:662d) + ld a, MUK + call CountPokemonIDInBothPlayAreas + ret c ; return if there's Muk in play + + farcall AIChooseRandomlyNotToDoAction + ret c ; randomly return + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 1 + ret z ; return if only one Pokemon in Play Area + + ld b, a + ld c, PLAY_AREA_ARENA + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + jr nz, .next +.loop + ld a, DUELVARS_ARENA_CARD + add c + call GetTurnDuelistVariable + ld [wce08], a + call GetCardIDFromDeckIndex + ld a, e + push bc + cp TENTACOOL + call z, .CheckWhetherToUseCowardice + pop bc + jr nc, .next + + dec b ; subtract 1 from number of Pokemon in Play Area + ld a, 1 + cp b + ret z ; return if no longer has Bench Pokemon + ld c, PLAY_AREA_ARENA ; reset back to Arena + jr .loop + +.next + inc c + ld a, c + cp b + jr nz, .loop + ret + +; checks whether AI uses Cowardice. +; return carry if Pkmn Power was used. +; input: +; c = Play Area location (PLAY_AREA_*) of Tentacool. +.CheckWhetherToUseCowardice ; 22671 (8:6671) + ld a, c + ldh [hTemp_ffa0], a + ld e, a + call GetCardDamageAndMaxHP +.asm_22678 + or a + ret z ; return if has no damage counters + + ldh a, [hTemp_ffa0] + or a + jr nz, .is_benched + + ; this part is buggy if AIDecideBenchPokemonToSwitchTo returns carry + ; but since this was already checked beforehand, this never happens. + ; so jr c, .asm_22678 can be safely removed. + farcall AIDecideBenchPokemonToSwitchTo + jr c, .asm_22678 ; bug, this jumps in the middle of damage checking + jr .use_cowardice +.is_benched + ld a, $ff +.use_cowardice + push af + ld a, [wce08] + ldh [hTempCardIndex_ff9f], 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 + scf + ret + +; AI logic for Damage Swap to transfer damage from Arena card +; to a card in Bench with more than 10 HP remaining +; and with no energy cards attached. +HandleAIDamageSwap: ; 226a3 (8:66a3) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + ret z ; return if no Bench Pokemon + + farcall AIChooseRandomlyNotToDoAction + ret c + + ld a, ALAKAZAM + call CountPokemonIDInPlayArea + ret nc ; return if no Alakazam + ld a, MUK + call CountPokemonIDInBothPlayAreas + ret c ; return if there's Muk in play + +; only take damage off certain cards in Arena + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp ALAKAZAM + jr z, .ok + cp KADABRA + jr z, .ok + cp ABRA + jr z, .ok + cp MR_MIME + ret nz + +.ok + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + or a + ret z ; return if no damage + + call ConvertHPToCounters + ld [wce06], a + ld a, ALAKAZAM + ld b, PLAY_AREA_BENCH_1 + farcall LookForCardIDInPlayArea_Bank5 + jr c, .is_in_bench + +; Alakazam is Arena card + xor a +.is_in_bench + ld [wce08], a + call .CheckForDamageSwapTargetInBench + ret c ; return if not found + +; use Damage Swap + ld a, [wce08] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff9f], a + ld a, [wce08] + ldh [hTemp_ffa0], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + + ld a, [wce06] + ld e, a +.loop_damage + ld d, 30 +.small_delay_loop + call DoFrame + dec d + jr nz, .small_delay_loop + + push de + call .CheckForDamageSwapTargetInBench + jr c, .no_more_target + + ldh [hTempRetreatCostCards], a + xor a ; PLAY_AREA_ARENA + ldh [hAIPkmnPowerEffectParam], a + ld a, OPPACTION_6B15 + bank1call AIMakeDecision + pop de + dec e + jr nz, .loop_damage + +.done +; return to main scene + ld d, 60 +.big_delay_loop + call DoFrame + dec d + jr nz, .big_delay_loop + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + ret + +.no_more_target + pop de + jr .done + +; looks for a target in the bench to receive damage counters. +; returns carry if one is found, and outputs remaining HP in a. +.CheckForDamageSwapTargetInBench ; 2273c (8:673c) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, PLAY_AREA_BENCH_1 + lb de, $ff, $ff + +; look for candidates in bench to get the damage counters +; only target specific card IDs. +.loop_bench + ld a, c + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp CHANSEY + jr z, .found_candidate + cp KANGASKHAN + jr z, .found_candidate + cp SNORLAX + jr z, .found_candidate + cp MR_MIME + jr z, .found_candidate + +.next_play_area + inc c + ld a, c + cp b + jr nz, .loop_bench + +; done + ld a, e + cp $ff + jr nz, .no_carry + ld a, d + cp $ff + jr z, .set_carry +.no_carry + or a + ret + +.found_candidate +; found a potential candidate to receive damage counters + ld a, DUELVARS_ARENA_CARD_HP + add c + call GetTurnDuelistVariable + cp 20 + jr c, .next_play_area ; ignore cards with only 10 HP left + + ld d, c ; store damage + push de + push bc + ld e, c + farcall CountNumberOfEnergyCardsAttached + pop bc + pop de + or a + jr nz, .next_play_area ; ignore cards with attached energy + ld e, c ; store deck index + jr .next_play_area + +.set_carry + scf + ret + +; handles AI logic for attaching energy cards +; in Go Go Rain Dance deck. +HandleAIGoGoRainDanceEnergy: ; 22790 (8:6790) + ld a, [wOpponentDeckID] + cp GO_GO_RAIN_DANCE_DECK_ID + ret nz ; return if not Go Go Rain Dance deck + + ld a, BLASTOISE + call CountPokemonIDInPlayArea + ret nc ; return if no Blastoise + ld a, MUK + call CountPokemonIDInBothPlayAreas + ret c ; return if there's Muk in play + +; play all the energy cards that is needed. +.loop + farcall AIProcessAndTryToPlayEnergy + jr c, .loop + ret diff --git a/src/engine/duel/ai/retreat.asm b/src/engine/duel/ai/retreat.asm new file mode 100644 index 0000000..768a48b --- /dev/null +++ b/src/engine/duel/ai/retreat.asm @@ -0,0 +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 wAITriedAttack == 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, [wAITriedAttack] + 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/duel/ai/special_attacks.asm b/src/engine/duel/ai/special_attacks.asm new file mode 100644 index 0000000..770324e --- /dev/null +++ b/src/engine/duel/ai/special_attacks.asm @@ -0,0 +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 diff --git a/src/engine/duel/ai/trainer_cards.asm b/src/engine/duel/ai/trainer_cards.asm new file mode 100644 index 0000000..4bee001 --- /dev/null +++ b/src/engine/duel/ai/trainer_cards.asm @@ -0,0 +1,6073 @@ +INCLUDE "data/duel/ai_trainer_card_logic.asm" + +_AIProcessHandTrainerCards: ; 200e5 (8:40e5) + ld [wAITrainerCardPhase], a +; create hand list in wDuelTempList and wTempHandCardList. + call CreateHandCardList + ld hl, wDuelTempList + ld de, wTempHandCardList + call CopyBuffer + ld hl, wTempHandCardList + +.loop_hand + ld a, [hli] + ld [wAITrainerCardToPlay], a + cp $ff + ret z + + push hl + ld a, [wAITrainerCardPhase] + ld d, a + ld hl, AITrainerCardLogic +.loop_data + xor a + ld [wCurrentAIFlags], a + ld a, [hli] + cp $ff + jp z, .pop_hl + +; compare input to first byte in data and continue if equal. + cp d + jp nz, .inc_hl_by_5 + + ld a, [hli] + ld [wce17], a + ld a, [wAITrainerCardToPlay] + call LoadCardDataToBuffer1_FromDeckIndex + + cp SWITCH + jr nz, .skip_switch_check + + ld b, a + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_SWITCH + jr nz, .inc_hl_by_4 + ld a, b + +.skip_switch_check +; compare hand card to second byte in data and continue if equal. + ld b, a + ld a, [wce17] + cp b + jr nz, .inc_hl_by_4 + +; found Trainer card + push hl + push de + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + +; if Headache effects prevent playing card +; move on to the next item in list. + bank1call CheckCantUseTrainerDueToHeadache + jp c, .next_in_data + + call LoadNonPokemonCardEffectCommands + ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1 + call TryExecuteEffectCommandFunction + jp c, .next_in_data + +; AI can randomly choose not to play card. + farcall AIChooseRandomlyNotToDoAction + jr c, .next_in_data + +; call routine to decide whether to play Trainer card + pop de + pop hl + push hl + call CallIndirect + pop hl + jr nc, .inc_hl_by_4 + +; routine returned carry, which means +; this card should be played. + inc hl + inc hl + ld [wAITrainerCardParameter], a + +; show Play Trainer Card screen + push de + push hl + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_PLAY_TRAINER + bank1call AIMakeDecision + pop hl + pop de + jr c, .inc_hl_by_2 + +; execute the effects of the Trainer card + push hl + call CallIndirect + pop hl + + inc hl + inc hl + ld a, [wPreviousAIFlags] + ld b, a + ld a, [wCurrentAIFlags] + or b + ld [wPreviousAIFlags], a + pop hl + and AI_FLAG_MODIFIED_HAND + jp z, .loop_hand + +; the hand was modified during the Trainer effect +; so it needs to be re-listed again and +; looped from the top. + call CreateHandCardList + ld hl, wDuelTempList + ld de, wTempHandCardList + call CopyBuffer + ld hl, wTempHandCardList +; clear the AI_FLAG_MODIFIED_HAND flag + ld a, [wPreviousAIFlags] + and ~AI_FLAG_MODIFIED_HAND + ld [wPreviousAIFlags], a + jp .loop_hand + +.inc_hl_by_5 + inc hl +.inc_hl_by_4 + inc hl + inc hl +.inc_hl_by_2 + inc hl + inc hl + jp .loop_data + +.next_in_data + pop de + pop hl + inc hl + inc hl + inc hl + inc hl + jp .loop_data + +.pop_hl + pop hl + jp .loop_hand + +; makes AI use Potion card. +AIPlay_Potion: ; 201b5 (8:41b5) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld e, a + call GetCardDamageAndMaxHP + cp 20 + jr c, .play_card + ld a, 20 +.play_card + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; if AI doesn't decide to retreat this card, +; check if defending Pokémon can KO active card +; next turn after using Potion. +; if it cannot, return carry. +; also take into account whether attack is high recoil. +AIDecide_Potion1: ; 201d1 (8:41d1) + farcall AIDecideWhetherToRetreat + jr c, .no_carry + call AICheckIfAttackIsHighRecoil + jr c, .no_carry + xor a ; active card + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfDefendingPokemonCanKnockOut + jr nc, .no_carry + ld d, a + + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld h, a + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + cp 20 + 1 ; if damage <= 20 + jr c, .calculate_hp + ld a, 20 ; amount of Potion HP healing + +; if damage done by defending Pokémon next turn will still +; KO this card after healing, return no carry. +.calculate_hp + ld l, a + ld a, h + add l + sub d + jr c, .no_carry + jr z, .no_carry + +; return carry. + xor a + scf + ret +.no_carry + or a + ret + +; finds a card in Play Area to use Potion on. +; output: +; a = card to use Potion on; +; carry set if Potion should be used. +AIDecide_Potion2: ; 20204 (8:4204) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfDefendingPokemonCanKnockOut + jr nc, .start_from_active +; can KO + ld d, a + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld h, a + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + cp 20 + 1 ; if damage <= 20 + jr c, .calculate_hp + ld a, 20 +; return if using healing prevents KO. +.calculate_hp + ld l, a + ld a, h + add l + sub d + jr c, .count_prizes + jr z, .count_prizes + or a + ret + +; using Potion on active card does not prevent a KO. +; if player is at last prize, start loop with active card. +; otherwise start loop at first bench Pokémon. +.count_prizes + call SwapTurn + call CountPrizes + call SwapTurn + dec a + jr z, .start_from_active + ld e, PLAY_AREA_BENCH_1 + jr .loop + +; find Play Area Pokémon with more than 10 damage. +; skip Pokémon if it has a BOOST_IF_TAKEN_DAMAGE attack. +.start_from_active + ld e, PLAY_AREA_ARENA +.loop + ld a, DUELVARS_ARENA_CARD + add e + call GetTurnDuelistVariable + cp $ff + ret z + call .check_boost_if_taken_damage + jr c, .has_boost_damage + call GetCardDamageAndMaxHP + cp 20 ; if damage >= 20 + jr nc, .found +.has_boost_damage + inc e + jr .loop + +; a card was found, now to check if it's active or benched. +.found + ld a, e + or a + jr z, .active_card + +; bench card + push de + call SwapTurn + call CountPrizes + call SwapTurn + dec a + or a + jr z, .check_random + ld a, 10 + call Random + cp 3 +; 7/10 chance of returning carry. +.check_random + pop de + jr c, .no_carry + ld a, e + scf + ret + +; return carry for active card if not High Recoil. +.active_card + push de + call AICheckIfAttackIsHighRecoil + pop de + jr c, .no_carry + ld a, e + scf + ret +.no_carry + or a + ret + +; return carry if either of the attacks are usable +; and have the BOOST_IF_TAKEN_DAMAGE effect. +.check_boost_if_taken_damage ; 2027e (8:427e) + push de + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + farcall CheckIfSelectedAttackIsUnusable + jr c, .second_attack + ld a, ATTACK_FLAG3_ADDRESS | BOOST_IF_TAKEN_DAMAGE_F + call CheckLoadedAttackFlag + jr c, .set_carry +.second_attack + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + farcall CheckIfSelectedAttackIsUnusable + jr c, .false + ld a, ATTACK_FLAG3_ADDRESS | BOOST_IF_TAKEN_DAMAGE_F + call CheckLoadedAttackFlag + jr c, .set_carry +.false + pop de + or a + ret +.set_carry + pop de + scf + ret + +; makes AI use Super Potion card. +AIPlay_SuperPotion: ; 202a8 (8:42a8) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTempPlayAreaLocation_ffa1], a + call AIPickEnergyCardToDiscard + ldh [hTemp_ffa0], a + ld a, [wAITrainerCardParameter] + ld e, a + call GetCardDamageAndMaxHP + cp 40 + jr c, .play_card + ld a, 40 +.play_card + ldh [hTempRetreatCostCards], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; if AI doesn't decide to retreat this card and card has +; any energy cards attached, check if defending Pokémon can KO +; active card next turn after using Super Potion. +; if it cannot, return carry. +; also take into account whether attack is high recoil. +AIDecide_SuperPotion1: ; 202cc (8:42cc) + farcall AIDecideWhetherToRetreat + jr c, .no_carry + call AICheckIfAttackIsHighRecoil + jr c, .no_carry + xor a + ldh [hTempPlayAreaLocation_ff9d], a + ld e, a + call .check_attached_energy + ret nc + farcall CheckIfDefendingPokemonCanKnockOut + jr nc, .no_carry + + ld d, a + ld d, a + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld h, a + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + cp 40 + 1 ; if damage < 40 + jr c, .calculate_hp + ld a, 40 +.calculate_hp + ld l, a + ld a, h + add l + sub d + jr c, .no_carry + jr z, .no_carry + +; return carry + ld a, e + scf + ret +.no_carry + or a + ret + +; returns carry if card has energies attached. +.check_attached_energy ; 20305 (8:4305) + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + ret z + scf + ret + +; finds a card in Play Area to use Super Potion on. +; output: +; a = card to use Super Potion on; +; carry set if Super Potion should be used. +AIDecide_SuperPotion2: ; 2030f (8:430f) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfDefendingPokemonCanKnockOut + jr nc, .start_from_active +; can KO + ld d, a + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld h, a + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + cp 40 + 1 ; if damage < 40 + jr c, .calculate_hp + ld a, 40 +; return if using healing prevents KO. +.calculate_hp + ld l, a + ld a, h + add l + sub d + jr c, .count_prizes + jr z, .count_prizes + or a + ret + +; using Super Potion on active card does not prevent a KO. +; if player is at last prize, start loop with active card. +; otherwise start loop at first bench Pokémon. +.count_prizes + call SwapTurn + call CountPrizes + call SwapTurn + dec a + jr z, .start_from_active + ld e, PLAY_AREA_BENCH_1 + jr .loop + +; find Play Area Pokémon with more than 30 damage. +; skip Pokémon if it doesn't have any energy attached, +; has a BOOST_IF_TAKEN_DAMAGE attack, +; or if discarding makes any attack of its attacks unusable. +.start_from_active + ld e, PLAY_AREA_ARENA +.loop + ld a, DUELVARS_ARENA_CARD + add e + call GetTurnDuelistVariable + cp $ff + ret z + ld d, a + call .check_attached_energy + jr nc, .next + call .check_boost_if_taken_damage + jr c, .next + call .check_energy_cost + jr c, .next + call GetCardDamageAndMaxHP + cp 40 ; if damage >= 40 + jr nc, .found +.next + inc e + jr .loop + +; a card was found, now to check if it's active or benched. +.found + ld a, e + or a + jr z, .active_card + +; bench card + push de + call SwapTurn + call CountPrizes + call SwapTurn + dec a + or a + jr z, .check_random + ld a, 10 + call Random + cp 3 +; 7/10 chance of returning carry. +.check_random + pop de + jr c, .no_carry + ld a, e + scf + ret + +; return carry for active card if not Hgh Recoil. +.active_card + push de + call AICheckIfAttackIsHighRecoil + pop de + jr c, .no_carry + ld a, e + scf + ret +.no_carry + or a + ret + +; returns carry if card has energies attached. +.check_attached_energy ; 20394 (8:4394) + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + ret z + scf + ret + +; return carry if either of the attacks are usable +; and have the BOOST_IF_TAKEN_DAMAGE effect. +.check_boost_if_taken_damage ; 2039e (8:439e) + push de + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + farcall CheckIfSelectedAttackIsUnusable + jr c, .second_attack_1 + ld a, ATTACK_FLAG3_ADDRESS | BOOST_IF_TAKEN_DAMAGE_F + call CheckLoadedAttackFlag + jr c, .true_1 +.second_attack_1 + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + farcall CheckIfSelectedAttackIsUnusable + jr c, .false_1 + ld a, ATTACK_FLAG3_ADDRESS | BOOST_IF_TAKEN_DAMAGE_F + call CheckLoadedAttackFlag + jr c, .true_1 +.false_1 + pop de + or a + ret +.true_1 + pop de + scf + ret + +; returns carry if discarding energy card renders any attack unusable, +; given that they have enough energy to be used before discarding. +.check_energy_cost ; 203c8 (8:43c8) + push de + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckEnergyNeededForAttack + jr c, .second_attack_2 + farcall CheckEnergyNeededForAttackAfterDiscard + jr c, .true_2 + +.second_attack_2 + pop de + push de + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckEnergyNeededForAttack + jr c, .false_2 + farcall CheckEnergyNeededForAttackAfterDiscard + jr c, .true_2 + +.false_2 + pop de + or a + ret +.true_2 + pop de + scf + ret + +AIPlay_Defender: ; 203f8 (8:43f8) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + xor a + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; returns carry if using Defender can prevent a KO +; by the defending Pokémon. +; this takes into account both attacks and whether they're useable. +AIDecide_Defender1: ; 20406 (8:4406) + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .cannot_ko + farcall CheckIfSelectedAttackIsUnusable + jr nc, .no_carry + farcall LookForEnergyNeededForAttackInHand + jr c, .no_carry + +.cannot_ko +; check if any of the defending Pokémon's attacks deal +; damage exactly equal to current HP, and if so, +; only continue if that attack is useable. + farcall CheckIfAnyDefendingPokemonAttackDealsSameDamageAsHP + jr nc, .no_carry + call SwapTurn + farcall CheckIfSelectedAttackIsUnusable + call SwapTurn + jr c, .no_carry + + ld a, [wSelectedAttack] + farcall EstimateDamage_FromDefendingPokemon + ld a, [wDamage] + ld [wce06], a + ld d, a + +; load in a the attack that was not selected, +; and check if it is useable. + ld a, [wSelectedAttack] + ld b, a + ld a, $01 + sub b + ld [wSelectedAttack], a + push de + call SwapTurn + farcall CheckIfSelectedAttackIsUnusable + call SwapTurn + pop de + jr c, .switch_back + +; the other attack is useable. +; compare its damage to the selected attack. + ld a, [wSelectedAttack] + push de + farcall EstimateDamage_FromDefendingPokemon + pop de + ld a, [wDamage] + cp d + jr nc, .subtract + +; in case the non-selected attack is useable +; and deals less damage than the selected attack, +; switch back to the other attack. +.switch_back + ld a, [wSelectedAttack] + ld b, a + ld a, $01 + sub b + ld [wSelectedAttack], a + ld a, [wce06] + ld [wDamage], a + +; now the selected attack is the one that deals +; the most damage of the two (and is useable). +; if subtracting damage by using Defender +; still prevents a KO, return carry. +.subtract + ld a, [wDamage] + sub 20 + ld d, a + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + sub d + jr c, .no_carry + jr z, .no_carry + scf + ret +.no_carry + or a + ret + +; return carry if using Defender prevents Pokémon +; from being knocked out by an attack with recoil. +AIDecide_Defender2: ; 20486 (8:4486) + ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F + call CheckLoadedAttackFlag + jr c, .recoil + ld a, ATTACK_FLAG1_ADDRESS | LOW_RECOIL_F + call CheckLoadedAttackFlag + jr c, .recoil + or a + ret + +.recoil + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wSelectedAttack] + or a + jr nz, .second_attack +; first attack + ld a, [wLoadedCard2Atk1EffectParam] + jr .check_weak +.second_attack + ld a, [wLoadedCard2Atk2EffectParam] + +; double recoil damage if card is weak to its own color. +.check_weak + ld d, a + push de + call GetArenaCardColor + call TranslateColorToWR + ld b, a + call GetArenaCardWeakness + and b + pop de + jr z, .check_resist + sla d + +; subtract 30 from recoil damage if card resists its own color. +; if this yields a negative number, return no carry. +.check_resist + push de + call GetArenaCardColor + call TranslateColorToWR + ld b, a + call GetArenaCardResistance + and b + pop de + jr z, .subtract + ld a, d + sub 30 + jr c, .no_carry + ld d, a + +; subtract damage prevented by Defender. +; if damage still knocks out card, return no carry. +; if damage does not knock out, return carry. +.subtract + ld a, d + or a + jr z, .no_carry + sub 20 + ld d, a + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + sub d + jr c, .no_carry + jr z, .no_carry + scf + ret +.no_carry + or a + ret + +AIPlay_Pluspower: ; 204e8 (8:44e8) + ld a, [wCurrentAIFlags] + or AI_FLAG_USED_PLUSPOWER + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardParameter] + ld [wAIPluspowerAttack], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; returns carry if using a Pluspower can KO defending Pokémon +; if active card cannot KO without the boost. +; outputs in a the attack to use. +AIDecide_Pluspower1: ; 20501 (8:4501) +; this is mistakenly duplicated + xor a + ldh [hTempPlayAreaLocation_ff9d], a + xor a + ldh [hTempPlayAreaLocation_ff9d], a + +; continue if no attack can knock out. +; if there's an attack that can, only continue +; if it's unusable and there's no card in hand +; to fulfill its energy cost. + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .cannot_ko + farcall CheckIfSelectedAttackIsUnusable + jr nc, .no_carry + farcall LookForEnergyNeededForAttackInHand + jr c, .no_carry + +; cannot use an attack that knocks out. +.cannot_ko +; get active Pokémon's info. + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempTurnDuelistCardID], a + +; get defending Pokémon's info and check +; its No Damage or Effect substatus. +; if substatus is active, return. + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempNonTurnDuelistCardID], a + bank1call HandleNoDamageOrEffectSubstatus + call SwapTurn + jr c, .no_carry + +; check both attacks and decide which one +; can KO with Pluspower boost. +; if neither can KO, return no carry. + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + call .check_ko_with_pluspower + jr c, .kos_with_pluspower_1 + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + call .check_ko_with_pluspower + jr c, .kos_with_pluspower_2 + +.no_carry + or a + ret + +; first attack can KO with Pluspower. +.kos_with_pluspower_1 + call .check_mr_mime + jr nc, .no_carry + xor a ; FIRST_ATTACK_OR_PKMN_POWER + scf + ret +; second attack can KO with Pluspower. +.kos_with_pluspower_2 + call .check_mr_mime + jr nc, .no_carry + ld a, SECOND_ATTACK + scf + ret + +; return carry if attack is useable and KOs +; defending Pokémon with Pluspower boost. +.check_ko_with_pluspower ; 20562 (8:4562) + farcall CheckIfSelectedAttackIsUnusable + jr c, .unusable + ld a, [wSelectedAttack] + farcall EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld b, a + ld hl, wDamage + sub [hl] + jr c, .no_carry + jr z, .no_carry + ld a, [hl] + add 10 ; add Pluspower boost + ld c, a + ld a, b + sub c + ret c ; return carry if damage > HP left + ret nz ; does not KO + scf + ret ; KOs with Pluspower boost +.unusable + or a + ret + +; returns carry if Pluspower boost does +; not exceed 30 damage when facing Mr. Mime. +.check_mr_mime ; 20589 (8:4589) + ld a, [wDamage] + add 10 ; add Pluspower boost + cp 30 ; no danger in preventing damage + ret c + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MR_MIME + ret z +; damage is >= 30 but not Mr. Mime + scf + ret + +; returns carry 7/10 of the time +; if selected attack is useable, can't KO without Pluspower boost +; can damage Mr. Mime even with Pluspower boost +; and has a minimum damage > 0. +; outputs in a the attack to use. +AIDecide_Pluspower2: ; 205a5 (8:45a5) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call .check_can_ko + jr nc, .no_carry + call .check_random + jr nc, .no_carry + call .check_mr_mime + jr nc, .no_carry + scf + ret +.no_carry + or a + ret + +; returns carry if Pluspower boost does +; not exceed 30 damage when facing Mr. Mime. +.check_mr_mime ; 205bb (8:45bb) + ld a, [wDamage] + add 10 ; add Pluspower boost + cp 30 ; no danger in preventing damage + ret c + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MR_MIME + ret z +; damage is >= 30 but not Mr. Mime + scf + ret + +; return carry if attack is useable but cannot KO. +.check_can_ko ; 205d7 (8:45d7) + farcall CheckIfSelectedAttackIsUnusable + jr c, .unusable + ld a, [wSelectedAttack] + farcall EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld b, a + ld hl, wDamage + sub [hl] + jr c, .no_carry + jr z, .no_carry +; can't KO. + scf + ret +.unusable + or a + ret + +; return carry 7/10 of the time if +; attack is useable and minimum damage > 0. +.check_random ; 205f6 (8:45f6) + farcall CheckIfSelectedAttackIsUnusable + jr c, .unusable + ld a, [wSelectedAttack] + farcall EstimateDamage_VersusDefendingCard + ld a, [wAIMinDamage] + cp 10 + jr c, .unusable + ld a, 10 + call Random + cp 3 + ret + +AIPlay_Switch: ; 20612 (8:4612) + ld a, [wCurrentAIFlags] + or AI_FLAG_USED_SWITCH + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + xor a + ld [wcdb4], a + ret + +; returns carry if the active card has less energy cards +; than the retreat cost and if AI can't play an energy +; card from the hand to fulfill the cost +AIDecide_Switch: ; 2062e (8:462e) +; check if AI can already play an energy card from hand to retreat + ld a, [wAIPlayEnergyCardForRetreat] + or a + jr z, .check_cost_amount + +; can't play energy card from hand to retreat +; compare number of energy cards attached to retreat cost + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + push af + ld e, PLAY_AREA_ARENA + farcall CountNumberOfEnergyCardsAttached + ld b, a + pop af + sub b + ; jump if cards attached > retreat cost + jr c, .check_cost_amount + cp 2 + ; jump if retreat cost is 2 more energy cards + ; than the number of cards attached + jr nc, .switch + +.check_cost_amount + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + cp 3 + ; jump if retreat cost >= 3 + jr nc, .switch + + push af + ld e, PLAY_AREA_ARENA + farcall CountNumberOfEnergyCardsAttached + pop bc + cp b + ; jump if energy cards attached < retreat cost + jr c, .switch + ret + +.switch + farcall AIDecideBenchPokemonToSwitchTo + ccf + ret + +AIPlay_GustOfWind: ; 20666 (8:4666) + ld a, [wCurrentAIFlags] + or AI_FLAG_USED_GUST_OF_WIND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_GustOfWind: ; 2067e (8:467e) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + dec a + or a + ret z ; no bench cards + +; if used Gust Of Wind already, +; do not use it again. + ld a, [wPreviousAIFlags] + and AI_FLAG_USED_GUST_OF_WIND + ret nz + + farcall CheckIfActivePokemonCanUseAnyNonResidualAttack + ret nc ; no non-residual attack can be used + + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .check_id ; if can't KO + farcall CheckIfSelectedAttackIsUnusable + jr nc, .no_carry ; if KO attack is useable + farcall LookForEnergyNeededForAttackInHand + jr c, .no_carry ; if energy card is in hand + +.check_id + ; skip if current active card is MEW3 or MEWTWO1 + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp MEW3 + jr z, .no_carry + cp MEWTWO1 + jr z, .no_carry + + call .FindBenchCardToKnockOut + ret c + + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + call .CheckIfNoAttackDealsDamage + jr c, .check_bench_energy + + ; skip if current arena card's color is + ; the defending card's weakness + call GetArenaCardColor + call TranslateColorToWR + ld b, a + call SwapTurn + call GetArenaCardWeakness + call SwapTurn + and b + jr nz, .no_carry + +; check weakness + call .FindBenchCardWithWeakness + ret nc ; no bench card weak to arena card + scf + ret ; found bench card weak to arena card + +.no_carry + or a + ret + +; being here means AI's arena card cannot damage player's arena card + +; first check if there is a card in player's bench that +; has no attached energy cards and that the AI can damage +.check_bench_energy + ; return carry if there's a bench card with weakness + call .FindBenchCardWithWeakness + ret c + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA +; loop through bench and check attached energy cards +.loop_1 + inc e + dec d + jr z, .check_bench_hp + call SwapTurn + call GetPlayAreaCardAttachedEnergies + call SwapTurn + ld a, [wTotalAttachedEnergies] + or a + jr nz, .loop_1 ; skip if has energy attached + call .CheckIfCanDamageBenchedCard + jr nc, .loop_1 + ld a, e + scf + ret + +.check_bench_hp + ld a, $ff + ld [wce06], a + xor a + ld [wce08], a + ld e, a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ld d, a + +; find bench card with least amount of available HP +.loop_2 + inc e + dec d + jr z, .check_found + ld a, e + add DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld b, a + ld a, [wce06] + inc b + cp b + jr c, .loop_2 + call .CheckIfCanDamageBenchedCard + jr nc, .loop_2 + dec b + ld a, b + ld [wce06], a + ld a, e + ld [wce08], a + jr .loop_2 + +.check_found + ld a, [wce08] + or a + jr z, .no_carry +; a card was found + +.set_carry + scf + ret + +.check_can_damage + push bc + push hl + xor a ; PLAY_AREA_ARENA + farcall CheckIfCanDamageDefendingPokemon + pop hl + pop bc + jr nc, .loop_3 + ld a, c + scf + ret + +; returns carry if any of the player's +; benched cards is weak to color in b +; and has a way to damage it +.FindBenchCardWithWeakness ; 2074d (8:474d) + ld a, DUELVARS_BENCH + call GetNonTurnDuelistVariable + ld c, PLAY_AREA_ARENA +.loop_3 + inc c + ld a, [hli] + cp $ff + jr z, .no_carry + call SwapTurn + call LoadCardDataToBuffer1_FromDeckIndex + call SwapTurn + ld a, [wLoadedCard1Weakness] + and b + jr nz, .check_can_damage + jr .loop_3 + +; returns carry if neither attack can deal damage +.CheckIfNoAttackDealsDamage ; 2076b (8:476b) + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + call .CheckIfAttackDealsNoDamage + jr c, .second_attack + ret +.second_attack + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + call .CheckIfAttackDealsNoDamage + jr c, .true + ret +.true + scf + ret + +; returns carry if attack is Pokemon Power +; or otherwise doesn't deal any damage +.CheckIfAttackDealsNoDamage ; 20782 (8:4782) + ld a, [wSelectedAttack] + ld e, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + ld a, [wLoadedAttackCategory] + + ; skip if attack is a Power or has 0 damage + cp POKEMON_POWER + jr z, .no_damage + ld a, [wDamage] + or a + ret z + + ; check damage against defending card + ld a, [wSelectedAttack] + farcall EstimateDamage_VersusDefendingCard + ld a, [wAIMaxDamage] + or a + ret nz + +.no_damage + scf + ret + +; returns carry if there is a player's bench card that +; the opponent's current active card can KO +.FindBenchCardToKnockOut ; 207a9 (8:47a9) + ld a, DUELVARS_BENCH + call GetNonTurnDuelistVariable + ld e, PLAY_AREA_BENCH_1 + +.loop_4 + ld a, [hli] + cp $ff + ret z + +; overwrite the player's active card and its HP +; with the current bench card that is being checked + push hl + push de + ld b, a + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + push af + ld [hl], b + ld a, e + add DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld b, a + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + push af + ld [hl], b + + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + call .CheckIfAnyAttackKnocksOut + jr nc, .next + farcall CheckIfSelectedAttackIsUnusable + jr nc, .found + farcall LookForEnergyNeededForAttackInHand + jr c, .found + +; the following two local routines can be condensed into one +; since they both revert the player's arena card +.next + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + pop af + ld [hl], a + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + pop af + ld [hl], a + pop de + inc e + pop hl + jr .loop_4 + +; revert player's arena card and set carry +.found + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + pop af + ld [hl], a + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + pop af + ld [hl], a + pop de + ld a, e + pop hl + scf + ret + +; returns carry if any of arena card's attacks +; KOs player card in location stored in e +.CheckIfAnyAttackKnocksOut ; 20806 (8:4806) + xor a ; FIRST_ATTACK_OR_PKMN_POWER + call .CheckIfAttackKnocksOut + ret c + ld a, SECOND_ATTACK + +; returns carry if attack KOs player card +; in location stored in e +.CheckIfAttackKnocksOut + push de + farcall EstimateDamage_VersusDefendingCard + pop de + ld a, DUELVARS_ARENA_CARD_HP + add e + call GetNonTurnDuelistVariable + ld hl, wDamage + sub [hl] + ret c + ret nz + scf + ret + +; returns carry if opponent's arena card can damage +; this benched card if it were switched with +; the player's arena card +.CheckIfCanDamageBenchedCard ; 20821 (8:4821) + push bc + push de + push hl + + ; overwrite arena card data + ld a, e + add DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + ld b, a + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + push af + ld [hl], b + + ; overwrite arena card HP + ld a, e + add DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld b, a + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + push af + ld [hl], b + + xor a ; PLAY_AREA_ARENA + farcall CheckIfCanDamageDefendingPokemon + jr c, .can_damage + +; the following two local routines can be condensed into one +; since they both revert the player's arena card + +; can't damage + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + pop af + ld [hl], a + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + pop af + ld [hl], a + pop hl + pop de + pop bc + or a + ret + +.can_damage + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + pop af + ld [hl], a + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + pop af + ld [hl], a + pop hl + pop de + pop bc + scf + ret + +AIPlay_Bill: ; 2086d (8:486d) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; return carry if cards in deck > 9 +AIDecide_Bill: ; 20878 (8:4878) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE - 9 + ret + +AIPlay_EnergyRemoval: ; 20880 (8:4880) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, [wce1a] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; picks an energy card in the player's Play Area to remove +AIDecide_EnergyRemoval: ; 20895 (8:4895) +; check if the current active card can KO player's card +; if it's possible to KO, then do not consider the player's +; active card to remove its attached energy + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .cannot_ko + farcall CheckIfSelectedAttackIsUnusable + jr nc, .can_ko + farcall LookForEnergyNeededForAttackInHand + jr nc, .cannot_ko + +.can_ko + ; start checking from the bench + ld a, PLAY_AREA_BENCH_1 + ld [wce0f], a + jr .check_bench_energy +.cannot_ko + ; start checking from the arena card + xor a ; PLAY_AREA_ARENA + ld [wce0f], a + +; loop each card and check if it has enough energy to use any attack +; if it does, then proceed to pick an energy card to remove +.check_bench_energy + call SwapTurn + ld a, [wce0f] + ld e, a +.loop_1 + ld a, DUELVARS_ARENA_CARD + add e + call GetTurnDuelistVariable + cp $ff + jr z, .default + + ld d, a + call .CheckIfCardHasEnergyAttached + jr nc, .next_1 + call .CheckIfNotEnoughEnergyToAttack + jr nc, .pick_energy ; jump if enough energy to attack +.next_1 + inc e + jr .loop_1 + +.pick_energy +; a play area card was picked to remove energy +; store the picked energy card to remove in wce1a +; and set carry + ld a, e + push af + call PickAttachedEnergyCardToRemove + ld [wce1a], a + pop af + call SwapTurn + scf + ret + +; if no card in player's Play Area was found with enough energy +; to attack, just pick an energy card from player's active card +; (in case the AI cannot KO it this turn) +.default + ld a, [wce0f] + or a + jr nz, .check_bench_damage ; not active card + call .CheckIfCardHasEnergyAttached + jr c, .pick_energy + +; lastly, check what attack on player's Play Area is highest damaging +; and pick an energy card attached to that Pokemon to remove +.check_bench_damage + xor a + ld [wce06], a + ld [wce08], a + + ld e, PLAY_AREA_BENCH_1 +.loop_2 + ld a, DUELVARS_ARENA_CARD + add e + call GetTurnDuelistVariable + cp $ff + jr z, .found_damage + + ld d, a + call .CheckIfCardHasEnergyAttached + jr nc, .next_2 + call .FindHighestDamagingAttack +.next_2 + inc e + jr .loop_2 + +.found_damage + ld a, [wce08] + or a + jr z, .no_carry ; skip if none found + ld e, a + jr .pick_energy +.no_carry + call SwapTurn + or a + ret + +; returns carry if this card has any energy cards attached +.CheckIfCardHasEnergyAttached ; 2091a (8:491a) + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + ret z + scf + ret + +; returns carry if this card does not +; have enough energy for either of its attacks +.CheckIfNotEnoughEnergyToAttack ; 20924 (8:4924) + push de + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckEnergyNeededForAttack + jr nc, .enough_energy + pop de + + push de + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckEnergyNeededForAttack + jr nc, .check_surplus + pop de + +; neither attack has enough energy + scf + ret + +.enough_energy + pop de + or a + ret + +; first attack doesn't have enough energy (or is just a Pokemon Power) +; but second attack has enough energy to be used +; check if there's surplus energy for attack and, if so, return carry +.check_surplus + farcall CheckIfNoSurplusEnergyForAttack + pop de + ccf + ret + +; stores in wce06 the highest damaging attack +; for the card in play area location in e +; and stores this card's location in wce08 +.FindHighestDamagingAttack ; 2094f (8:494f) + push de + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + + xor a ; FIRST_ATTACK_OR_PKMN_POWER + farcall EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr z, .skip_1 + ld e, a + ld a, [wce06] + cp e + jr nc, .skip_1 + ld a, e + ld [wce06], a ; store this damage value + pop de + ld a, e + ld [wce08], a ; store this location + jr .second_attack + +.skip_1 + pop de + +.second_attack + push de + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + + ld a, SECOND_ATTACK + farcall EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr z, .skip_2 + ld e, a + ld a, [wce06] + cp e + jr nc, .skip_2 + ld a, e + ld [wce06], a ; store this damage value + pop de + ld a, e + ld [wce08], a ; store this location + ret +.skip_2 + pop de + ret + +AIPlay_SuperEnergyRemoval: ; 20994 (8:4994) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, [wce1a] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wce1b] + ldh [hTempRetreatCostCards], a + ld a, [wce1c] + ldh [hTempRetreatCostCards + 1], a + ld a, [wce1d] + ldh [hTempRetreatCostCards + 2], a + ld a, $ff + ldh [hTempRetreatCostCards + 3], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; picks two energy cards in the player's Play Area to remove +AIDecide_SuperEnergyRemoval: ; 209bc (8:49bc) + ld e, PLAY_AREA_BENCH_1 +.loop_1 +; first find an Arena card with a color energy card +; to discard for card effect +; return immediately if no Arena cards + ld a, DUELVARS_ARENA_CARD + add e + call GetTurnDuelistVariable + cp $ff + jr z, .exit + + ld d, a + push de + call .LookForNonDoubleColorless + pop de + jr c, .not_double_colorless + inc e + jr .loop_1 + +; returns carry if an energy card other than double colorless +; is found attached to the card in play area location e +.LookForNonDoubleColorless + ld a, e + call CreateArenaOrBenchEnergyCardList + ld hl, wDuelTempList +.loop_2 + ld a, [hli] + cp $ff + ret z + call LoadCardDataToBuffer1_FromDeckIndex + cp DOUBLE_COLORLESS_ENERGY + ; any basic energy card + ; will set carry flag here + jr nc, .loop_2 + ret + +.exit + or a + ret + +; card in Play Area location e was found with +; a basic energy card +.not_double_colorless + ld a, e + ld [wce0f], a + +; check if the current active card can KO player's card +; if it's possible to KO, then do not consider the player's +; active card to remove its attached energy + xor a ; PLAY_AREA_ARENA + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .cannot_ko + farcall CheckIfSelectedAttackIsUnusable + jr nc, .can_ko + farcall LookForEnergyNeededForAttackInHand + jr nc, .cannot_ko + +.can_ko + ; start checking from the bench + call SwapTurn + ld e, PLAY_AREA_BENCH_1 + jr .loop_3 +.cannot_ko + ; start checking from the arena card + call SwapTurn + ld e, PLAY_AREA_ARENA + +; loop each card and check if it has enough energy to use any attack +; if it does, then proceed to pick energy cards to remove +.loop_3 + ld a, DUELVARS_ARENA_CARD + add e + call GetTurnDuelistVariable + cp $ff + jr z, .no_carry + + ld d, a + call .CheckIfFewerThanTwoEnergyCards + jr c, .next_1 + call .CheckIfNotEnoughEnergyToAttack + jr nc, .found_card ; jump if enough energy to attack +.next_1 + inc e + jr .loop_3 + +.found_card +; a play area card was picked to remove energy +; if this is not the Arena Card, then check +; entire bench to pick the highest damage + ld a, e + or a + jr nz, .check_bench_damage + +; store the picked energy card to remove in wce1a +; and set carry +.pick_energy + ld [wce1b], a + call PickTwoAttachedEnergyCards + ld [wce1c], a + ld a, b + ld [wce1d], a + call SwapTurn + ld a, [wce0f] + push af + call AIPickEnergyCardToDiscard + ld [wce1a], a + pop af + scf + ret + +; check what attack on player's Play Area is highest damaging +; and pick an energy card attached to that Pokemon to remove +.check_bench_damage + xor a + ld [wce06], a + ld [wce08], a + + ld e, PLAY_AREA_BENCH_1 +.loop_4 + ld a, DUELVARS_ARENA_CARD + add e + call GetTurnDuelistVariable + cp $ff + jr z, .found_damage + + ld d, a + call .CheckIfFewerThanTwoEnergyCards + jr c, .next_2 + call .CheckIfNotEnoughEnergyToAttack + jr c, .next_2 + call .FindHighestDamagingAttack +.next_2 + inc e + jr .loop_4 + +.found_damage + ld a, [wce08] + or a + jr z, .no_carry + jr .pick_energy +.no_carry + call SwapTurn + or a + ret + +; returns carry if the number of energy cards attached +; is fewer than 2, or if all energy combined yields +; fewer than 2 energy +.CheckIfFewerThanTwoEnergyCards ; 20a77 (8:4a77) + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + cp 2 + ret c ; return if fewer than 2 attached cards + +; count all energy attached +; i.e. colored energy card = 1 +; and double colorless energy card = 2 + xor a + ld b, NUM_COLORED_TYPES + ld hl, wAttachedEnergies +.loop_5 + add [hl] + inc hl + dec b + jr nz, .loop_5 + ld b, [hl] + srl b + add b + cp 2 + ret + +; returns carry if this card does not +; have enough energy for either of its attacks +.CheckIfNotEnoughEnergyToAttack ; 20a92 (8:4a92) + push de + xor a ; FIRST_ATTACK_OR_PKMN_POWER + ld [wSelectedAttack], a + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckEnergyNeededForAttack + jr nc, .enough_energy + pop de + + push de + ld a, SECOND_ATTACK + ld [wSelectedAttack], a + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + farcall CheckEnergyNeededForAttack + jr nc, .check_surplus + pop de + +; neither attack has enough energy + scf + ret + +.enough_energy + pop de + or a + ret + +; first attack doesn't have enough energy (or is just a Pokemon Power) +; but second attack has enough energy to be used +; check if there's surplus energy for attack and, if so, +; return carry if this surplus energy is at least 2 +.check_surplus + farcall CheckIfNoSurplusEnergyForAttack + cp 2 + jr c, .enough_energy + pop de + scf + ret + +; stores in wce06 the highest damaging attack +; for the card in play area location in e +; and stores this card's location in wce08 +.FindHighestDamagingAttack ; 20ac1 (8:4ac1) + push de + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + + xor a ; FIRST_ATTACK_OR_PKMN_POWER + farcall EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr z, .skip_1 + ld e, a + ld a, [wce06] + cp e + jr nc, .skip_1 + ld a, e + ld [wce06], a ; store this damage value + pop de + ld a, e + ld [wce08], a ; store this location + jr .second_attack + +.skip_1 + pop de + +.second_attack + push de + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + + ld a, SECOND_ATTACK + farcall EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr z, .skip_2 + ld e, a + ld a, [wce06] + cp e + jr nc, .skip_2 + ld a, e + ld [wce06], a ; store this damage value + pop de + ld a, e + ld [wce08], a ; store this location + ret +.skip_2 + pop de + ret + +AIPlay_PokemonBreeder: ; 20b06 (8:4b06) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wce1a] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_PokemonBreeder: ; 20b1b (8:4b1b) + call IsPrehistoricPowerActive + jp c, .done + + ld a, 7 + ld hl, wce08 + call ClearMemory_Bank8 + + xor a + ld [wce06], a + call CreateHandCardList + ld hl, wDuelTempList + +.loop_hand_1 + ld a, [hli] + cp $ff + jr z, .not_found_in_hand + +; check if card in hand is any of the following +; stage 2 Pokemon cards + ld d, a + call LoadCardDataToBuffer1_FromDeckIndex + cp VENUSAUR1 + jr z, .found + cp VENUSAUR2 + jr z, .found + cp BLASTOISE + jr z, .found + cp VILEPLUME + jr z, .found + cp ALAKAZAM + jr z, .found + cp GENGAR + jr nz, .loop_hand_1 + +.found + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + push hl + call GetTurnDuelistVariable + pop hl + ld c, a + ld e, PLAY_AREA_ARENA + +; check Play Area for card that can evolve into +; the picked stage 2 Pokemon +.loop_play_area_1 + push hl + push bc + push de + call CheckIfCanEvolveInto_BasicToStage2 + pop de + call nc, .can_evolve + pop bc + pop hl + inc e + dec c + jr nz, .loop_play_area_1 + jr .loop_hand_1 + +.can_evolve + ld a, DUELVARS_ARENA_CARD_HP + add e + call GetTurnDuelistVariable + call ConvertHPToCounters + swap a + ld b, a + +; count number of energy cards attached and keep +; the lowest 4 bits (capped at $0f) + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + cp $10 + jr c, .not_maxed_out + ld a, %00001111 +.not_maxed_out + or b + +; 4 high bits of a = HP counters Pokemon still has +; 4 low bits of a = number of energy cards attached + +; store this score in wce08 + PLAY_AREA* + ld hl, wce08 + ld c, e + ld b, $00 + add hl, bc + ld [hl], a + +; store the deck index of stage 2 Pokemon in wce0f + PLAY_AREA* + ld hl, wce0f + add hl, bc + ld [hl], d + +; increase wce06 by one + ld hl, wce06 + inc [hl] + ret + +.not_found_in_hand + ld a, [wce06] + or a + jr z, .check_evolution_and_dragonite + +; an evolution has been found before + xor a + ld [wce06], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld e, $00 + ld d, $00 + +; find highest score in wce08 +.loop_score_1 + ld hl, wce08 + add hl, de + ld a, [wce06] + cp [hl] + jr nc, .not_higher + +; store this score to wce06 + ld a, [hl] + ld [wce06], a +; store this PLay Area location to wce07 + ld a, e + ld [wce07], a + +.not_higher + inc e + dec c + jr nz, .loop_score_1 + +; store the deck index of the stage 2 card +; that has been decided in wce1a, +; return the Play Area location of card +; to evolve in a and return carry + ld a, [wce07] + ld e, a + ld hl, wce0f + add hl, de + ld a, [hl] + ld [wce1a], a + ld a, [wce07] + scf + ret + +.check_evolution_and_dragonite + ld a, 7 + ld hl, wce08 + call ClearMemory_Bank8 + + xor a + ld [wce06], a + call CreateHandCardList + ld hl, wDuelTempList + push hl + +.loop_hand_2 + pop hl + ld a, [hli] + cp $ff + jr z, .check_evolution_found + + push hl + ld d, a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld e, PLAY_AREA_ARENA + +.loop_play_area_2 +; check if evolution is possible + push bc + push de + call CheckIfCanEvolveInto_BasicToStage2 + pop de + call nc, .HandleDragonite1Evolution + call nc, .can_evolve + +; not possible to evolve or returned carry +; when handling Dragonite1 evolution + pop bc + inc e + dec c + jr nz, .loop_play_area_2 + jr .loop_hand_2 + +.check_evolution_found + ld a, [wce06] + or a + jr nz, .evolution_was_found +; no evolution was found before + or a + ret + +.evolution_was_found + xor a + ld [wce06], a + ld a, $ff + ld [wce07], a + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld e, $00 + ld d, $00 + +; find highest score in wce08 with at least +; 2 energy cards attached +.loop_score_2 + ld hl, wce08 + add hl, de + ld a, [wce06] + cp [hl] + jr nc, .next_score + +; take the lower 4 bits (total energy cards) +; and skip if less than 2 + ld a, [hl] + ld b, a + and %00001111 + cp 2 + jr c, .next_score + +; has at least 2 energy cards +; store the score in wce06 + ld a, b + ld [wce06], a +; store this PLay Area location to wce07 + ld a, e + ld [wce07], a + +.next_score + inc e + dec c + jr nz, .loop_score_2 + + ld a, [wce07] + cp $ff + jr z, .done + +; a card to evolve was found +; store the deck index of the stage 2 card +; that has been decided in wce1a, +; return the Play Area location of card +; to evolve in a and return carry + ld e, a + ld hl, wce0f + add hl, de + ld a, [hl] + ld [wce1a], a + ld a, [wce07] + scf + ret + +.done + or a + ret + +; return carry if card is evolving to Dragonite1 and if +; - the card that is evolving is not Arena card and +; number of damage counters in Play Area is under 8; +; - the card that is evolving is Arena card and has under 5 +; damage counters or has less than 3 energy cards attached. +.HandleDragonite1Evolution ; 20c5c (8:4c5c) + push af + push bc + push de + push hl + push de + +; check card ID + ld a, d + call GetCardIDFromDeckIndex + ld a, e + pop de + cp DRAGONITE1 + jr nz, .no_carry + +; check card Play Area location + ld a, e + or a + jr z, .active_card_dragonite + +; the card that is evolving is not active card + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, 0 + +; count damage counters in Play Area +.loop_play_area_damage + dec b + ld e, b + push bc + call GetCardDamageAndMaxHP + pop bc + call ConvertHPToCounters + add c + ld c, a + + ld a, b + or a + jr nz, .loop_play_area_damage + +; compare number of total damage counters +; with 7, if less or equal to that, set carry + ld a, 7 + cp c + jr c, .no_carry + jr .set_carry + +.active_card_dragonite +; the card that is evolving is active card +; compare number of this card's damage counters +; with 5, if less than that, set carry + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + cp 5 + jr c, .set_carry + +; compare number of this card's attached energy cards +; with 3, if less than that, set carry + ld e, PLAY_AREA_ARENA + farcall CountNumberOfEnergyCardsAttached + cp 3 + jr c, .set_carry + jr .no_carry + +.no_carry + pop hl + pop de + pop bc + pop af + ret + +.set_carry + pop hl + pop de + pop bc + pop af + scf + ret + +AIPlay_ProfessorOak: ; 20cae (8:4cae) + ld a, [wCurrentAIFlags] + or AI_FLAG_USED_PROFESSOR_OAK | AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; sets carry if AI determines a score of playing +; Professor Oak is over a certain threshold. +AIDecide_ProfessorOak: ; 20cc1 (8:4cc1) +; return if cards in deck <= 6 + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE - 6 + ret nc + + ld a, [wOpponentDeckID] + cp LEGENDARY_ARTICUNO_DECK_ID + jp z, .HandleLegendaryArticunoDeck + cp EXCAVATION_DECK_ID + jp z, .HandleExcavationDeck + cp WONDERS_OF_SCIENCE_DECK_ID + jp z, .HandleWondersOfScienceDeck + +; return if cards in deck <= 14 +.check_cards_deck + ld a, [hl] + cp DECK_SIZE - 14 + ret nc + +; initialize score + ld a, $1e + ld [wce06], a + +; check number of cards in hand +.check_cards_hand + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + cp 4 + jr nc, .more_than_3_cards + +; less than 4 cards in hand + ld a, [wce06] + add $32 + ld [wce06], a + jr .check_energy_cards + +.more_than_3_cards + cp 9 + jr c, .check_energy_cards + +; more than 8 cards + ld a, [wce06] + sub $1e + ld [wce06], a + +.check_energy_cards + farcall CreateEnergyCardListFromHand + jr nc, .handle_blastoise + +; no energy cards in hand + ld a, [wce06] + add $28 + ld [wce06], a + +.handle_blastoise + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .check_hand + +; no Muk in Play Area + ld a, BLASTOISE + call CountPokemonIDInPlayArea + jr nc, .check_hand + +; at least one Blastoise in AI Play Area + ld a, WATER_ENERGY + farcall LookForCardIDInHand + jr nc, .check_hand + +; no Water energy in hand + ld a, [wce06] + add $0a + ld [wce06], a + +; this part seems buggy +; the AI loops through all the cards in hand and checks +; if any of them is not a Pokemon card and has Basic stage. +; it seems like the intention was that if there was +; any Basic Pokemon still in hand, the AI would add to the score. +.check_hand + call CreateHandCardList + ld hl, wDuelTempList +.loop_hand + ld a, [hli] + cp $ff + jr z, .check_evolution + + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr c, .loop_hand ; bug, should be jr nc + + ld a, [wLoadedCard1Stage] + or a + jr nz, .loop_hand + + ld a, [wce06] + add $0a + ld [wce06], a + +.check_evolution + xor a + ld [wce0f], a + ld [wce0f + 1], a + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +.loop_play_area + push de + call .LookForEvolution + pop de + jr nc, .not_in_hand + +; there's a card in hand that can evolve + ld a, $01 + ld [wce0f], a + +.not_in_hand +; check if a card that can evolve was found at all +; if not, go to the next card in the Play Area + ld a, [wce08] + cp $01 + jr nz, .next_play_area + +; if it was found, set wce0f + 1 to $01 + ld a, $01 + ld [wce0f + 1], a + +.next_play_area + inc e + dec d + jr nz, .loop_play_area + +; if a card was found that evolves... + ld a, [wce0f + 1] + or a + jr z, .check_score + +; ...but that card is not in the hand... + ld a, [wce0f] + or a + jr nz, .check_score + +; ...add to the score + ld a, [wce06] + add $0a + ld [wce06], a + +; only return carry if score > $3c +.check_score + ld a, [wce06] + ld b, $3c + cp b + jr nc, .set_carry + or a + ret + +.set_carry + scf + ret + +; return carry if there's a card in the hand that +; can evolve the card in Play Area location in e. +; sets wce08 to $01 if any card is found that can +; evolve regardless of card location. +.LookForEvolution ; 20d9d (8:4d9d) + xor a + ld [wce08], a + ld d, 0 + +; loop through the whole deck to check if there's +; a card that can evolve this Pokemon. +.loop_deck_evolution + push de + call CheckIfCanEvolveInto + pop de + jr nc, .can_evolve +.evolution_not_in_hand + inc d + ld a, DECK_SIZE + cp d + jr nz, .loop_deck_evolution + + or a + ret + +; a card was found that can evolve, set wce08 to $01 +; and if the card is in the hand, return carry. +; otherwise resume looping through deck. +.can_evolve + ld a, $01 + ld [wce08], a + ld a, DUELVARS_CARD_LOCATIONS + add d + call GetTurnDuelistVariable + cp CARD_LOCATION_HAND + jr nz, .evolution_not_in_hand + + scf + ret + +; handles Legendary Articuno Deck AI logic. +.HandleLegendaryArticunoDeck ; 20dc3 (8:4dc3) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 3 + jr nc, .check_playable_cards + +; has less than 3 Pokemon in Play Area. + push af + call CreateHandCardList + pop af + ld d, a + ld e, PLAY_AREA_ARENA + +; if no cards in hand evolve cards in Play Area, +; returns carry. +.loop_play_area_articuno + ld a, DUELVARS_ARENA_CARD + add e + + push de + call GetTurnDuelistVariable + farcall CheckForEvolutionInList + pop de + jr c, .check_playable_cards + + inc e + ld a, d + cp e + jr nz, .loop_play_area_articuno + +.set_carry_articuno + scf + ret + +; if there are more than 3 energy cards in hand, +; return no carry, otherwise check for playable cards. +.check_playable_cards + call CountOppEnergyCardsInHand + cp 4 + jr nc, .no_carry_articuno + +; remove both Professor Oak cards from list +; before checking for playable cards + call CreateHandCardList + ld hl, wDuelTempList + ld e, PROFESSOR_OAK + farcall RemoveCardIDInList + ld e, PROFESSOR_OAK + farcall RemoveCardIDInList + +; look in hand for cards that can be played. +; if a card that cannot be played is found, return no carry. +; otherwise return carry. +.loop_hand_articuno + ld a, [hli] + cp $ff + jr z, .set_carry_articuno + push hl + farcall CheckIfCardCanBePlayed + pop hl + jr c, .loop_hand_articuno + +.no_carry_articuno + or a + ret + +; handles Excavation deck AI logic. +; sets score depending on whether there's no +; Mysterious Fossil in play and in hand. +.HandleExcavationDeck ; 20e11 (8:4e11) +; return no carry if cards in deck < 15 + ld a, [hl] + cp 46 + ret nc + +; look for Mysterious Fossil + ld a, MYSTERIOUS_FOSSIL + call LookForCardIDInHandAndPlayArea + jr c, .found_mysterious_fossil + ld a, $50 + ld [wce06], a + jp .check_cards_hand +.found_mysterious_fossil + ld a, $1e + ld [wce06], a + jp .check_cards_hand + +; handles Wonders of Science AI logic. +; if there's either Grimer or Muk in hand, +; do not play Professor Oak. +.HandleWondersOfScienceDeck ; 20e2c (8:4e2c) + ld a, GRIMER + call LookForCardIDInHandList_Bank8 + jr c, .found_grimer_or_muk + ld a, MUK + call LookForCardIDInHandList_Bank8 + jr c, .found_grimer_or_muk + + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + jp .check_cards_deck + +.found_grimer_or_muk + or a + ret + +AIPlay_EnergyRetrieval: ; 20e44 (8:4e44) + ld a, [wCurrentAIFlags] + or AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, [wce1a] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wce1b] + ldh [hTempRetreatCostCards], a + cp $ff + jr z, .asm_20e68 + ld a, $ff + ldh [$ffa3], a +.asm_20e68 + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; checks whether AI can play Energy Retrieval and +; picks the energy cards from the discard pile, +; and duplicate cards in hand to discard. +AIDecide_EnergyRetrieval: ; 20e6e (8:4e6e) +; return no carry if no cards in hand + farcall CreateEnergyCardListFromHand + jp nc, .no_carry + +; handle Go Go Rain Dance deck +; return no carry if there's no Muk card in play and +; if there's no Blastoise card in Play Area +; if there's a Muk in play, continue as normal + ld a, [wOpponentDeckID] + cp GO_GO_RAIN_DANCE_DECK_ID + jr nz, .start + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .start + ld a, BLASTOISE + call CountPokemonIDInPlayArea + jp nc, .no_carry + +.start +; find duplicate cards in hand + call CreateHandCardList + ld hl, wDuelTempList + call FindDuplicateCards + jp c, .no_carry + + ld [wce06], a + ld a, CARD_LOCATION_DISCARD_PILE + call FindBasicEnergyCardsInLocation + jp c, .no_carry + +; some basic energy cards were found in Discard Pile + ld a, $ff + ld [wce1a], a + ld [wce1b], a + ld [wce1c], a + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +; first check if there are useful energy cards in the list +; and choose them for retrieval first +.loop_play_area + ld a, DUELVARS_ARENA_CARD + add e + push de + +; load this card's ID in wTempCardID +; and this card's Type in wTempCardType + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + pop de + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + +; loop the energy cards in the Discard Pile +; and check if they are useful for this Pokemon + ld hl, wDuelTempList +.loop_energy_cards_1 + ld a, [hli] + cp $ff + jr z, .next_play_area + + ld b, a + push hl + farcall CheckIfEnergyIsUseful + pop hl + jr nc, .loop_energy_cards_1 + + ld a, [wce1a] + cp $ff + jr nz, .second_energy_1 + +; check if there were already chosen cards, +; if this is the second chosen card, return carry + +; first energy card found + ld a, b + ld [wce1a], a + call RemoveCardFromList + jr .next_play_area +.second_energy_1 + ld a, b + ld [wce1b], a + jr .set_carry + +.next_play_area + inc e + dec d + jr nz, .loop_play_area + +; next, if there are still energy cards left to choose, +; loop through the energy cards again and select +; them in order. + ld hl, wDuelTempList +.loop_energy_cards_2 + ld a, [hli] + cp $ff + jr z, .check_chosen + ld b, a + ld a, [wce1a] + cp $ff + jr nz, .second_energy_2 + ld a, b + ld [wce1a], a + call RemoveCardFromList + jr .loop_energy_cards_2 + +.second_energy_2 + ld a, b + ld [wce1b], a + jr .set_carry + +; will set carry if at least one has been chosen +.check_chosen + ld a, [wce1a] + cp $ff + jr nz, .set_carry +.no_carry + or a + ret + +.set_carry + ld a, [wce06] + scf + ret + +; remove an element from the list +; and shortens it accordingly +; input: +; hl = pointer to element after the one to remove +RemoveCardFromList: ; 20f27 (8:4f27) + push de + ld d, h + ld e, l + dec hl + push hl +.loop_remove + ld a, [de] + ld [hli], a + cp $ff + jr z, .done_remove + inc de + jr .loop_remove +.done_remove + pop hl + pop de + ret + +; finds duplicates in card list in hl. +; if a duplicate of Pokemon cards are found, return in +; a the deck index of the second one. +; otherwise, if a duplicate of non-Pokemon cards are found +; return in a the deck index of the second one. +; if no duplicates found, return carry. +; input: +; hl = list to look in +; output: +; a = deck index of duplicate card +FindDuplicateCards: ; 20f38 (8:4f38) + ld a, $ff + ld [wce0f], a + ld [wce0f + 1], a + push hl + +.loop_outer +; get ID of current card + pop hl + ld a, [hli] + cp $ff + jr z, .check_found + call GetCardIDFromDeckIndex + ld b, e + push hl + +; loop the rest of the list to find +; another card with the same ID +.loop_inner + ld a, [hli] + cp $ff + jr z, .loop_outer + ld c, a + call GetCardIDFromDeckIndex + ld a, e + cp b + jr nz, .loop_inner + +; found two cards with same ID + push bc + call GetCardType + pop bc + cp TYPE_ENERGY + jr c, .not_energy + +; they are energy or trainer cards +; loads wce0f+1 with this card deck index + ld a, c + ld [wce0f + 1], a + jr .loop_outer + +.not_energy +; they are Pokemon cards +; loads wce0f with this card deck index + ld a, c + ld [wce0f], a + jr .loop_outer + +.check_found + ld a, [wce0f] + cp $ff + jr nz, .no_carry + ld a, [wce0f + 1] + cp $ff + jr nz, .no_carry + +; only set carry if duplicate cards were not found + scf + ret + +.no_carry +; two cards with the same ID were found +; of either Pokemon or Non-Pokemon cards + or a + ret + +AIPlay_SuperEnergyRetrieval: ; 20f80 (8:4f80) + ld a, [wCurrentAIFlags] + or AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, [wce1a] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wce1b] + ldh [hTempRetreatCostCards], a + ld a, [wce1c] + ldh [$ffa3], a + cp $ff + jr z, .asm_20fbb + ld a, [wce1d] + ldh [$ffa4], a + cp $ff + jr z, .asm_20fbb + ld a, [wce1e] + ldh [$ffa5], a + cp $ff + jr z, .asm_20fbb + ld a, $ff + ldh [$ffa6], a +.asm_20fbb + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_SuperEnergyRetrieval: ; 20fc1 (8:4fc1) +; return no carry if no cards in hand + farcall CreateEnergyCardListFromHand + jp nc, .no_carry + +; handle Go Go Rain Dance deck +; return no carry if there's no Muk card in play and +; if there's no Blastoise card in Play Area +; if there's a Muk in play, continue as normal + ld a, [wOpponentDeckID] + cp GO_GO_RAIN_DANCE_DECK_ID + jr nz, .start + ld a, MUK + call CountPokemonIDInBothPlayAreas + jr c, .start + ld a, BLASTOISE + call CountPokemonIDInPlayArea + jp nc, .no_carry + +.start +; find duplicate cards in hand + call CreateHandCardList + ld hl, wDuelTempList + call FindDuplicateCards + jp c, .no_carry + +; remove the duplicate card in hand +; and run the hand check again + ld [wce06], a + ld hl, wDuelTempList + call FindAndRemoveCardFromList + call FindDuplicateCards + jp c, .no_carry + + ld [wce08], a + ld a, CARD_LOCATION_DISCARD_PILE + call FindBasicEnergyCardsInLocation + jp c, .no_carry + +; some basic energy cards were found in Discard Pile + ld a, $ff + ld [wce1b], a + ld [wce1c], a + ld [wce1d], a + ld [wce1e], a + ld [wce1f], a + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +; first check if there are useful energy cards in the list +; and choose them for retrieval first +.loop_play_area + ld a, DUELVARS_ARENA_CARD + add e + push de + +; load this card's ID in wTempCardID +; and this card's Type in wTempCardType + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + pop de + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + +; loop the energy cards in the Discard Pile +; and check if they are useful for this Pokemon + ld hl, wDuelTempList +.loop_energy_cards_1 + ld a, [hli] + cp $ff + jr z, .next_play_area + + ld b, a + push hl + farcall CheckIfEnergyIsUseful + pop hl + jr nc, .loop_energy_cards_1 + +; first energy + ld a, [wce1b] + cp $ff + jr nz, .second_energy_1 + ld a, b + ld [wce1b], a + call RemoveCardFromList + jr .next_play_area + +.second_energy_1 + ld a, [wce1c] + cp $ff + jr nz, .third_energy_1 + ld a, b + ld [wce1c], a + call RemoveCardFromList + jr .next_play_area + +.third_energy_1 + ld a, [wce1d] + cp $ff + jr nz, .fourth_energy_1 + ld a, b + ld [wce1d], a + call RemoveCardFromList + jr .next_play_area + +.fourth_energy_1 + ld a, b + ld [wce1e], a + jr .set_carry + +.next_play_area + inc e + dec d + jr nz, .loop_play_area + +; next, if there are still energy cards left to choose, +; loop through the energy cards again and select +; them in order. + ld hl, wDuelTempList +.loop_energy_cards_2 + ld a, [hli] + cp $ff + jr z, .check_chosen + ld b, a + ld a, [wce1b] + cp $ff + jr nz, .second_energy_2 + ld a, b + +; first energy + ld [wce1b], a + call RemoveCardFromList + jr .loop_energy_cards_2 + +.second_energy_2 + ld a, [wce1c] + cp $ff + jr nz, .third_energy_2 + ld a, b + ld [wce1c], a + call RemoveCardFromList + jr .loop_energy_cards_2 + +.third_energy_2 + ld a, [wce1d] + cp $ff + jr nz, .fourth_energy + ld a, b + ld [wce1d], a + call RemoveCardFromList + jr .loop_energy_cards_2 + +.fourth_energy + ld a, b + ld [wce1e], a + jr .set_carry + +; will set carry if at least one has been chosen +.check_chosen + ld a, [wce1b] + cp $ff + jr nz, .set_carry + +.no_carry + or a + ret +.set_carry + ld a, [wce08] + ld [wce1a], a + ld a, [wce06] + scf + ret + +; finds the card with deck index a in list hl, +; and removes it from the list. +; the card HAS to exist in the list, since this +; routine does not check for the terminating byte $ff! +; input: +; a = card deck index to look +; hl = pointer to list of cards +FindAndRemoveCardFromList: ; 210d5 (8:50d5) + push hl + ld b, a +.loop_duplicate + ld a, [hli] + cp b + jr nz, .loop_duplicate + call RemoveCardFromList + pop hl + ret + +AIPlay_PokemonCenter: ; 210e0 (8:50e0) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_PokemonCenter: ; 210eb (8:50eb) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + +; return if active Pokemon can KO player's card. + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .start + farcall CheckIfSelectedAttackIsUnusable + jr nc, .no_carry + farcall LookForEnergyNeededForAttackInHand + jr c, .no_carry + +.start + xor a + ld [wce06], a + ld [wce08], a + ld [wce0f], a + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +.loop_play_area + ld a, DUELVARS_ARENA_CARD + add e + push de + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld a, e ; useless instruction + pop de + +; get this Pokemon's current HP in number of counters +; and add it to the total. + ld a, [wLoadedCard1HP] + call ConvertHPToCounters + ld b, a + ld a, [wce06] + add b + ld [wce06], a + +; get this Pokemon's current damage counters +; and add it to the total. + call GetCardDamageAndMaxHP + call ConvertHPToCounters + ld b, a + ld a, [wce08] + add b + ld [wce08], a + +; get this Pokemon's number of attached energy cards +; and add it to the total. +; if there's overflow, return no carry. + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + ld b, a + ld a, [wce0f] + add b + jr c, .no_carry + ld [wce0f], a + + inc e + dec d + jr nz, .loop_play_area + +; if (number of damage counters / 2) < (total energy cards attached) +; return no carry. + ld a, [wce08] + srl a + ld hl, wce0f + cp [hl] + jr c, .no_carry + +; if (number of HP counters * 6 / 10) >= (number of damage counters) +; return no carry. + ld a, [wce06] + ld l, a + ld h, 6 + call HtimesL + call CalculateWordTensDigit + ld a, l + ld hl, wce08 + cp [hl] + jr nc, .no_carry + + scf + ret + +.no_carry + or a + ret + +AIPlay_ImposterProfessorOak: ; 21170 (8:5170) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; sets carry depending on player's number of cards +; in deck in in hand. +AIDecide_ImposterProfessorOak: ; 2117b (8:517b) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetNonTurnDuelistVariable + cp DECK_SIZE - 14 + jr c, .more_than_14_cards + +; if player has less than 14 cards in deck, only +; set carry if number of cards in their hands < 6 + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetNonTurnDuelistVariable + cp 6 + jr c, .set_carry +.no_carry + or a + ret + +; if player has more than 14 cards in deck, only +; set carry if number of cards in their hands >= 9 +.more_than_14_cards + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetNonTurnDuelistVariable + cp 9 + jr c, .no_carry +.set_carry + scf + ret + +AIPlay_EnergySearch: ; 2119a (8:519a) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; AI checks for playing Energy Search +AIDecide_EnergySearch: ; 211aa (8:51aa) + farcall CreateEnergyCardListFromHand + jr c, .start + call .CheckForUsefulEnergyCards + jr c, .start + +; there are energy cards in hand and at least +; one of them is useful to a card in Play Area +.no_carry + or a + ret + +.start + ld a, [wOpponentDeckID] + cp HEATED_BATTLE_DECK_ID + jr z, .heated_battle + cp WONDERS_OF_SCIENCE_DECK_ID + jr z, .wonders_of_science + +; if no energy cards in deck, return no carry + ld a, CARD_LOCATION_DECK + call FindBasicEnergyCardsInLocation + jr c, .no_carry + +; if any of the energy cards in deck is useful +; return carry right away... + call .CheckForUsefulEnergyCards + jr c, .no_useful + scf + ret + +; ...otherwise save the list in a before return carry. +.no_useful + ld a, [wDuelTempList] + scf + ret + +; Heated Battle deck only searches for Fire and Lightning +; if they are found to be useful to some card in Play Area +.heated_battle + ld a, CARD_LOCATION_DECK + call FindBasicEnergyCardsInLocation + jr c, .no_carry + call .CheckUsefulFireOrLightningEnergy + jr c, .no_carry + scf + ret + +; this subroutine has a bug. +; it was supposed to use the .CheckUsefulGrassEnergy subroutine +; but uses .CheckUsefulFireOrLightningEnergy instead. +.wonders_of_science + ld a, CARD_LOCATION_DECK + call FindBasicEnergyCardsInLocation + jr c, .no_carry + call .CheckUsefulFireOrLightningEnergy + jr c, .no_carry + scf + ret + +; return carry if cards in wDuelTempList are not +; useful to any of the Play Area Pokemon +.CheckForUsefulEnergyCards ; 211f1 (8:51f1) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +.loop_play_area_1 + ld a, DUELVARS_ARENA_CARD + add e + push de + call GetTurnDuelistVariable + +; store ID and type of card + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + pop de + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + +; look in list for a useful energy, +; is any is found return no carry. + ld hl, wDuelTempList +.loop_energy_1 + ld a, [hli] + cp $ff + jr z, .none_found + ld b, a + push hl + farcall CheckIfEnergyIsUseful + pop hl + jr nc, .loop_energy_1 + + ld a, b + or a + ret + +.none_found + inc e + ld a, e + cp d + jr nz, .loop_play_area_1 + + scf + ret + +; checks whether there are useful energies +; only for Fire and Lightning type Pokemon cards +; in Play Area. If none found, return carry. +.CheckUsefulFireOrLightningEnergy ; 2122e (8:522e) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +.loop_play_area_2 + ld a, DUELVARS_ARENA_CARD + add e + push de + call GetTurnDuelistVariable + +; get card's ID and Type + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + pop de + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + +; only do check if the Pokemon's type +; is either Fire or Lightning + cp TYPE_ENERGY_FIRE + jr z, .fire_or_lightning + cp TYPE_ENERGY_LIGHTNING + jr nz, .next_play_area + +; loop each energy card in list +.fire_or_lightning + ld [wTempCardType], a + ld hl, wDuelTempList +.loop_energy_2 + ld a, [hli] + cp $ff + jr z, .next_play_area + +; if this energy card is useful, +; return no carry. + ld b, a + push hl + farcall CheckIfEnergyIsUseful + pop hl + jr nc, .loop_energy_2 + + ld a, b + or a + ret + +.next_play_area + inc e + ld a, e + cp d + jr nz, .loop_play_area_2 + +; no card was found to be useful +; for Fire/Lightning type Pokemon card. + scf + ret + +; checks whether there are useful energies +; only for Grass type Pokemon cards +; in Play Area. If none found, return carry. +.CheckUsefulGrassEnergy ; 21273 (8:5273) +; unreferenced + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +.loop_play_area_3 + ld a, DUELVARS_ARENA_CARD + add e + push de + call GetTurnDuelistVariable + +; get card's ID and Type + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + pop de + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + +; only do check if the Pokemon's type is Grass + cp TYPE_ENERGY_GRASS + jr nz, .next_play_area_3 + +; loop each energy card in list + ld [wTempCardType], a + ld hl, wDuelTempList +.loop_energy_3 + ld a, [hli] + cp $ff + jr z, .next_play_area_3 + +; if this energy card is useful, +; return no carry. + ld b, a + push hl + farcall CheckIfEnergyIsUseful + pop hl + jr nc, .loop_energy_3 + + ld a, b + or a + ret + +.next_play_area_3 + inc e + ld a, e + cp d + jr nz, .loop_play_area_3 + +; no card was found to be useful +; for Grass type Pokemon card. + scf + ret + +AIPlay_Pokedex: ; 212b4 (8:52b4) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wce1a] + ldh [hTemp_ffa0], a + ld a, [wce1b] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wce1c] + ldh [hTempRetreatCostCards], a + ld a, [wce1d] + ldh [$ffa3], a + ld a, [wce1e] + ldh [$ffa4], a + ld a, $ff + ldh [$ffa5], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_Pokedex: ; 212dc (8:52dc) + ld a, [wAIPokedexCounter] + cp 5 + 1 + jr c, .no_carry ; return if counter hasn't reached 6 yet + +; return no carry if number of cards in deck <= 4 + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE - 4 + jr nc, .no_carry + +; has a 3 in 10 chance of actually playing card + ld a, 10 + call Random + cp 3 + jr c, .pick_cards + +.no_carry + or a + ret + +.pick_cards +; the following comparison is disregarded +; the Wonders of Science deck was probably intended +; to use PickPokedexCards_Unreferenced instead + ld a, [wOpponentDeckID] + cp WONDERS_OF_SCIENCE_DECK_ID + jp PickPokedexCards ; bug, should be jp nz + +; picks order of the cards in deck from the effects of Pokedex. +; prioritizes Pokemon cards, then Trainer cards, then energy cards. +; stores the resulting order in wce1a. +PickPokedexCards_Unreferenced: ; 212ff (8:52ff) +; unreferenced + xor a + ld [wAIPokedexCounter], a ; reset counter + + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + add DUELVARS_DECK_CARDS + ld l, a + lb de, $00, $00 + ld b, 5 + +; run through 5 of the remaining cards in deck +.next_card + ld a, [hli] + ld c, a + call .GetCardType + +; load this card's deck index and type in memory +; wce08 = card types +; wce0f = card deck indices + push hl + ld hl, wce08 + add hl, de + ld [hl], a + ld hl, wce0f + add hl, de + ld [hl], c + pop hl + + inc e + dec b + jr nz, .next_card + +; terminate the wce08 list + ld a, $ff + ld [wce08 + 5], a + + ld de, wce1a + +; find Pokemon + ld hl, wce08 + ld c, -1 + ld b, $00 + +; run through the stored cards +; and look for any Pokemon cards. +.loop_pokemon + inc c + ld a, [hli] + cp $ff + jr z, .find_trainers + cp TYPE_ENERGY + jr nc, .loop_pokemon +; found a Pokemon card +; store it in wce1a list + push hl + ld hl, wce0f + add hl, bc + ld a, [hl] + pop hl + ld [de], a + inc de + jr .loop_pokemon + +; run through the stored cards +; and look for any Trainer cards. +.find_trainers + ld hl, wce08 + ld c, -1 + ld b, $00 + +.loop_trainers + inc c + ld a, [hli] + cp $ff + jr z, .find_energy + cp TYPE_TRAINER + jr nz, .loop_trainers +; found a Trainer card +; store it in wce1a list + push hl + ld hl, wce0f + add hl, bc + ld a, [hl] + pop hl + ld [de], a + inc de + jr .loop_trainers + +.find_energy + ld hl, wce08 + ld c, -1 + ld b, $00 + +; run through the stored cards +; and look for any energy cards. +.loop_energy + inc c + ld a, [hli] + cp $ff + jr z, .done + and TYPE_ENERGY + jr z, .loop_energy +; found an energy card +; store it in wce1a list + push hl + ld hl, wce0f + add hl, bc + ld a, [hl] + pop hl + ld [de], a + inc de + jr .loop_energy + +.done + scf + ret + +.GetCardType ; 21383 (8:5383) + push bc + push de + call GetCardIDFromDeckIndex + call GetCardType + pop de + pop bc + ret + +; picks order of the cards in deck from the effects of Pokedex. +; prioritizes energy cards, then Pokemon cards, then Trainer cards. +; stores the resulting order in wce1a. +PickPokedexCards: ; 2138e (8:538e) + xor a + ld [wAIPokedexCounter], a ; reset counter ; reset counter + + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + add DUELVARS_DECK_CARDS + ld l, a + lb de, $00, $00 + ld b, 5 + +; run through 5 of the remaining cards in deck +.next_card + ld a, [hli] + ld c, a + call .GetCardType + +; load this card's deck index and type in memory +; wce08 = card types +; wce0f = card deck indices + push hl + ld hl, wce08 + add hl, de + ld [hl], a + ld hl, wce0f + add hl, de + ld [hl], c + pop hl + + inc e + dec b + jr nz, .next_card + +; terminate the wce08 list + ld a, $ff + ld [wce08 + 5], a + + ld de, wce1a + +; find energy + ld hl, wce08 + ld c, -1 + ld b, $00 + +; run through the stored cards +; and look for any energy cards. +.loop_energy + inc c + ld a, [hli] + cp $ff + jr z, .find_pokemon + and TYPE_ENERGY + jr z, .loop_energy +; found an energy card +; store it in wce1a list + push hl + ld hl, wce0f + add hl, bc + ld a, [hl] + pop hl + ld [de], a + inc de + jr .loop_energy + +.find_pokemon + ld hl, wce08 + ld c, -1 + ld b, $00 + +; run through the stored cards +; and look for any Pokemon cards. +.loop_pokemon + inc c + ld a, [hli] + cp $ff + jr z, .find_trainers + cp TYPE_ENERGY + jr nc, .loop_pokemon +; found a Pokemon card +; store it in wce1a list + push hl + ld hl, wce0f + add hl, bc + ld a, [hl] + pop hl + ld [de], a + inc de + jr .loop_pokemon + +; run through the stored cards +; and look for any Trainer cards. +.find_trainers + ld hl, wce08 + ld c, -1 + ld b, $00 + +.loop_trainers + inc c + ld a, [hli] + cp $ff + jr z, .done + cp TYPE_TRAINER + jr nz, .loop_trainers +; found a Trainer card +; store it in wce1a list + push hl + ld hl, wce0f + add hl, bc + ld a, [hl] + pop hl + ld [de], a + inc de + jr .loop_trainers + +.done + scf + ret + +.GetCardType ; 21412 (8:5412) + push bc + push de + call GetCardIDFromDeckIndex + call GetCardType + pop de + pop bc + ret + +AIPlay_FullHeal: ; 2141d (8:541d) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_FullHeal: ; 21428 (8:5428) + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + +; skip if no status on arena card + or a ; NO_STATUS + jr z, .no_carry + + and CNF_SLP_PRZ + cp PARALYZED + jr z, .paralyzed + cp ASLEEP + jr z, .asleep + cp CONFUSED + jr z, .confused + ; if either PSN or DBLPSN, fallthrough + +.set_carry + scf + ret + +.asleep +; set carry if any of the following +; cards are in the Play Area. + ld a, GASTLY1 + ld b, PLAY_AREA_ARENA + call LookForCardIDInPlayArea_Bank8 + jr c, .set_carry + ld a, GASTLY2 + ld b, PLAY_AREA_ARENA + call LookForCardIDInPlayArea_Bank8 + jr c, .set_carry + ld a, HAUNTER2 + ld b, PLAY_AREA_ARENA + call LookForCardIDInPlayArea_Bank8 + jr c, .set_carry + +; otherwise fallthrough + +.paralyzed +; if Scoop Up is in hand and decided to be played, skip. + ld a, SCOOP_UP + call LookForCardIDInHandList_Bank8 + jr nc, .no_scoop_up_prz + call AIDecide_ScoopUp + jr c, .no_carry + +.no_scoop_up_prz +; if card can damage defending Pokemon... + xor a ; PLAY_AREA_ARENA + farcall CheckIfCanDamageDefendingPokemon + jr nc, .no_carry +; ...and can play an energy card to retreat, set carry. + ld a, [wAIPlayEnergyCardForRetreat] + or a + jr nz, .set_carry + +; if not, check whether it's a card it would rather retreat, +; and if it isn't, set carry. + farcall AIDecideWhetherToRetreat + jr nc, .set_carry + +.no_carry + or a + ret + +.confused +; if Scoop Up is in hand and decided to be played, skip. + ld a, SCOOP_UP + call LookForCardIDInHandList_Bank8 + jr nc, .no_scoop_up_cnf + call AIDecide_ScoopUp + jr c, .no_carry + +.no_scoop_up_cnf +; if card can damage defending Pokemon... + xor a ; PLAY_AREA_ARENA + farcall CheckIfCanDamageDefendingPokemon + jr nc, .no_carry +; ...and can play an energy card to retreat, set carry. + ld a, [wAIPlayEnergyCardForRetreat] + or a + jr nz, .set_carry +; if not, return no carry. + jr .no_carry + +AIPlay_MrFuji: ; 21497 (8:5497) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; AI logic for playing Mr Fuji +AIDecide_MrFuji: ; 214a7 (8:54a7) + ld a, $ff + ld [wce06], a + ld [wce08], a + +; if just one Pokemon in Play Area, skip. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 1 + ret z + + dec a + ld d, a + ld e, PLAY_AREA_BENCH_1 + +; find a Pokemon in the bench that has damage counters. +.loop_bench + ld a, DUELVARS_ARENA_CARD + add e + push de + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + pop de + + ld a, [wLoadedCard1HP] + ld b, a + + ; skip if zero damage counters + call GetCardDamageAndMaxHP + call ConvertHPToCounters + or a + jr z, .next + +; a = damage counters +; b = hp left + call CalculateBDividedByA_Bank8 + cp 20 + jr nc, .next + +; here, HP left in counters is less than twice +; the number of damage counters, that is: +; HP < 1/3 max HP + +; if value is less than the one found before, store this one. + ld hl, wce08 + cp [hl] + jr nc, .next + ld [hl], a + ld a, e + ld [wce06], a +.next + inc e + dec d + jr nz, .loop_bench + + ld a, [wce06] + cp $ff + ret z + + scf + ret + +AIPlay_ScoopUp: ; 214f1 (8:54f1) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, [wce1a] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_ScoopUp: ; 21506 (8:5506) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + +; if only one Pokemon in Play Area, skip. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + jr c, .no_carry + +; handle some decks differently + ld a, [wOpponentDeckID] + cp LEGENDARY_ARTICUNO_DECK_ID + jr z, .HandleLegendaryArticuno + cp LEGENDARY_RONALD_DECK_ID + jp z, .HandleLegendaryRonald + +; if can't KO defending Pokemon, check if defending Pokemon +; can KO this card. If so, then continue. +; If not, return no carry. + +; if it can KO the defending Pokemon this turn, +; return no carry. + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .cannot_ko + farcall CheckIfSelectedAttackIsUnusable + jr nc, .no_carry + farcall LookForEnergyNeededForAttackInHand + jr c, .no_carry + +.cannot_ko + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + cp PARALYZED + jr z, .cannot_retreat + cp ASLEEP + jr z, .cannot_retreat + +; doesn't have a status that prevents retreat. +; so check if it has enough energy to retreat. +; if not, return no carry. + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + ld b, a + ld e, PLAY_AREA_ARENA + farcall CountNumberOfEnergyCardsAttached + cp b + jr c, .cannot_retreat + +.no_carry + or a + ret + +.cannot_retreat +; store damage and total HP left + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1HP] + call ConvertHPToCounters + ld d, a + +; skip if card has no damage counters. + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + or a + jr z, .no_carry + +; if (total damage / total HP counters) < 7 +; return carry. +; (this corresponds to damage counters +; being under 70% of the max HP) + ld b, a + ld a, d + call CalculateBDividedByA_Bank8 + cp 7 + jr c, .no_carry + +; store Pokemon to switch to in wce1a and set carry. +.decide_switch + farcall AIDecideBenchPokemonToSwitchTo + jr c, .no_carry + ld [wce1a], a + xor a + scf + ret + +; this deck will use Scoop Up on a benched Articuno2. +; it checks if the defending Pokemon is a Snorlax, +; but interestingly does not check for Muk in both Play Areas. +; will also use Scoop Up on +.HandleLegendaryArticuno +; if less than 3 Play Area Pokemon cards, skip. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 3 + jr c, .no_carry + +; look for Articuno2 in bench + ld a, ARTICUNO2 + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank8 + jr c, .articuno_bench + +; check Arena card + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp ARTICUNO2 + jr z, .articuno_or_chansey + cp CHANSEY + jr nz, .no_carry + +; here either Articuno2 or Chansey +; is the Arena Card. +.articuno_or_chansey +; if can't KO defending Pokemon, check if defending Pokemon +; can KO this card. If so, then continue. +; If not, return no carry. + +; if it can KO the defending Pokemon this turn, +; return no carry. + farcall CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .check_ko + farcall CheckIfSelectedAttackIsUnusable + jr nc, .no_carry + farcall LookForEnergyNeededForAttackInHand + jr c, .no_carry +.check_ko + farcall CheckIfDefendingPokemonCanKnockOut + jr nc, .no_carry + jr .decide_switch + +.articuno_bench +; skip if the defending card is Snorlax + push af + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp SNORLAX + pop bc + jr z, .no_carry + +; check attached energy cards. +; if it has any, return no carry. + ld a, b +.check_attached_energy + ld e, a + push af + farcall CountNumberOfEnergyCardsAttached + or a + pop bc + ld a, b + jr z, .no_energy + jp .no_carry + +.no_energy +; has decided to Scoop Up benched card, +; store $ff as the Pokemon card to switch to +; because there's no need to switch. + push af + ld a, $ff + ld [wce1a], a + pop af + scf + ret + +; this deck will use Scoop Up on a benched Articuno2, Zapdos3 or Moltres2. +; interestingly, does not check for Muk in both Play Areas. +.HandleLegendaryRonald ; 215e7 (8:55e7) +; if less than 3 Play Area Pokemon cards, skip. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 3 + jp c, .no_carry + + ld a, ARTICUNO2 + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank8 + jr c, .articuno_bench + ld a, ZAPDOS3 + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank8 + jr c, .check_attached_energy + ld a, MOLTRES2 + ld b, PLAY_AREA_BENCH_1 + call LookForCardIDInPlayArea_Bank8 + jr c, .check_attached_energy + jp .no_carry + +AIPlay_Maintenance: ; 2160f (8:560f) + ld a, [wCurrentAIFlags] + or AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wce1a] + ldh [hTemp_ffa0], a + ld a, [wce1b] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; AI logic for playing Maintenance +AIDecide_Maintenance: ; 2162c (8:562c) +; Imakuni? has his own thing + ld a, [wOpponentDeckID] + cp IMAKUNI_DECK_ID + jr z, .imakuni + +; skip if number of cars in hand < 4. + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + cp 4 + jr c, .no_carry + +; list out all the hand cards and remove +; wAITrainerCardToPlay from list.Then find any duplicate cards. + call CreateHandCardList + ld hl, wDuelTempList + ld a, [wAITrainerCardToPlay] + call FindAndRemoveCardFromList +; if duplicates are not found, return no carry. + call FindDuplicateCards + jp c, .no_carry + +; store the first duplicate card and remove it from the list. +; run duplicate check again. + ld [wce1a], a + ld hl, wDuelTempList + call FindAndRemoveCardFromList +; if duplicates are not found, return no carry. + call FindDuplicateCards + jp c, .no_carry + +; store the second duplicate card and return carry. + ld [wce1b], a + scf + ret + +.no_carry + or a + ret + +.imakuni +; has a 2 in 10 chance of not skipping. + ld a, 10 + call Random + cp 2 + jr nc, .no_carry + +; skip if number of cards in hand < 3. + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + cp 3 + jr c, .no_carry + +; shuffle hand cards + call CreateHandCardList + ld hl, wDuelTempList + call CountCardsInDuelTempList + call ShuffleCards + +; go through each card and find +; cards that are different from wAITrainerCardToPlay. +; if found, add those cards to wce1a and wce1a+1. + ld a, [wAITrainerCardToPlay] + ld b, a + ld c, 2 + ld de, wce1a + +.loop + ld a, [hli] + cp $ff + jr z, .no_carry + cp b + jr z, .loop + ld [de], a + inc de + dec c + jr nz, .loop + +; two cards were found, return carry. + scf + ret + +AIPlay_Recycle: ; 2169a (8:569a) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ldtx de, TrainerCardSuccessCheckText + bank1call TossCoin + jr nc, .asm_216ae + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + jr .asm_216b2 +.asm_216ae + ld a, $ff + ldh [hTemp_ffa0], a +.asm_216b2 + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; lists cards to look for in the Discard Pile. +; has priorities for Ghost Deck, and a "default" priority list +; (which is the Fire Charge deck, since it's the only other +; deck that runs a Recycle card in it.) +AIDecide_Recycle: ; 216b8 (8:56b8) +; no use checking if no cards in Discard Pile + call CreateDiscardPileCardList + jr c, .no_carry + + ld a, $ff + ld [wce08], a + ld [wce08 + 1], a + ld [wce08 + 2], a + ld [wce08 + 3], a + ld [wce08 + 4], a + +; handle Ghost deck differently + ld hl, wDuelTempList + ld a, [wOpponentDeckID] + cp GHOST_DECK_ID + jr z, .loop_2 + +; priority list for Fire Charge deck +.loop_1 + ld a, [hli] + cp $ff + jr z, .done + + ld b, a + call LoadCardDataToBuffer1_FromDeckIndex + +; double colorless + cp DOUBLE_COLORLESS_ENERGY + jr nz, .chansey + ld a, b + ld [wce08], a + jr .loop_1 + +.chansey + cp CHANSEY + jr nz, .tauros + ld a, b + ld [wce08 + 1], a + jr .loop_1 + +.tauros + cp TAUROS + jr nz, .jigglypuff + ld a, b + ld [wce08 + 2], a + jr .loop_1 + +.jigglypuff + cp JIGGLYPUFF1 + jr nz, .loop_1 + ld a, b + ld [wce08 + 3], a + jr .loop_1 + +; loop through wce08 and set carry +; on the first that was found in Discard Pile. +; if none were found, return no carry. +.done + ld hl, wce08 + ld b, 5 +.loop_found + ld a, [hli] + cp $ff + jr nz, .set_carry + dec b + jr nz, .loop_found +.no_carry + or a + ret +.set_carry + scf + ret + +; priority list for Ghost deck +.loop_2 + ld a, [hli] + cp $ff + jr z, .done + + ld b, a + call LoadCardDataToBuffer1_FromDeckIndex + +; gastly2 + cp GASTLY2 + jr nz, .gastly1 + ld a, b + ld [wce08], a + jr .loop_2 + +.gastly1 + cp GASTLY1 + jr nz, .zubat + ld a, b + ld [wce08 + 1], a + jr .loop_2 + +.zubat + cp ZUBAT + jr nz, .ditto + ld a, b + ld [wce08 + 2], a + jr .loop_2 + +.ditto + cp DITTO + jr nz, .meowth + ld a, b + ld [wce08 + 3], a + jr .loop_2 + +.meowth + cp MEOWTH2 + jr nz, .loop_2 + ld a, b + ld [wce08 + 4], a + jr .loop_2 + +AIPlay_Lass: ; 21755 (8:5755) + ld a, [wCurrentAIFlags] + or AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_Lass: ; 21768 (8:5768) +; skip if player has less than 7 cards in hand + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetNonTurnDuelistVariable + cp 7 + jr c, .no_carry + +; look for Trainer cards in hand (except for Lass) +; if any is found, return no carry. +; otherwise, return carry. + call CreateHandCardList + ld hl, wDuelTempList +.loop + ld a, [hli] + cp $ff + jr z, .set_carry + ld b, a + call LoadCardDataToBuffer1_FromDeckIndex + cp LASS + jr z, .loop + ld a, [wLoadedCard1Type] + cp TYPE_TRAINER + jr nz, .loop +.no_carry + or a + ret +.set_carry + scf + ret + +AIPlay_ItemFinder: ; 2178f (8:578f) + ld a, [wCurrentAIFlags] + or AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wce1a] + ldh [hTemp_ffa0], a + ld a, [wce1b] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, [wAITrainerCardParameter] + ldh [hTempRetreatCostCards], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; checks whether there's Energy Removal in Discard Pile. +; if so, find duplicate cards in hand to discard +; that are not Mr Mime and Pokemon Trader cards. +; this logic is suitable only for Strange Psyshock deck. +AIDecide_ItemFinder: ; 217b1 (8:57b1) +; skip if no Discard Pile. + call CreateDiscardPileCardList + jr c, .no_carry + +; look for Energy Removal in Discard Pile + ld hl, wDuelTempList +.loop_discard_pile + ld a, [hli] + cp $ff + jr z, .no_carry + ld b, a + call LoadCardDataToBuffer1_FromDeckIndex + cp ENERGY_REMOVAL + jr nz, .loop_discard_pile +; found, store this deck index + ld a, b + ld [wce06], a + +; before looking for cards to discard in hand, +; remove any Mr Mime and Pokemon Trader cards. +; this way these are guaranteed to not be discarded. + call CreateHandCardList + ld hl, wDuelTempList +.loop_hand + ld a, [hli] + cp $ff + jr z, .choose_discard + ld b, a + call LoadCardDataToBuffer1_FromDeckIndex + cp MR_MIME + jr nz, .pkmn_trader + call RemoveCardFromList + jr .loop_hand +.pkmn_trader + cp POKEMON_TRADER + jr nz, .loop_hand + call RemoveCardFromList + jr .loop_hand + +; choose cards to discard from hand. +.choose_discard + ld hl, wDuelTempList + +; do not discard wAITrainerCardToPlay + ld a, [wAITrainerCardToPlay] + call FindAndRemoveCardFromList +; find any duplicates, if not found, return no carry. + call FindDuplicateCards + jp c, .no_carry + +; store the duplicate found in wce1a and +; remove it from the hand list. + ld [wce1a], a + ld hl, wDuelTempList + call FindAndRemoveCardFromList +; find duplicates again, if not found, return no carry. + call FindDuplicateCards + jp c, .no_carry + +; store the duplicate found in wce1b. +; output the card to be recovered from the Discard Pile. + ld [wce1b], a + ld a, [wce06] + scf + ret + +.no_carry + or a + ret + +AIPlay_Imakuni: ; 21813 (8:5813) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; only sets carry if Active card is not confused. +AIDecide_Imakuni: ; 2181e (8:581e) + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + cp CONFUSED + jr z, .confused + scf + ret +.confused + or a + ret + +AIPlay_Gambler: ; 2182d (8:582d) + ld a, [wCurrentAIFlags] + or AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wOpponentDeckID] + cp IMAKUNI_DECK_ID + jr z, .asm_2186a + ld hl, wRNG1 + ld a, [hli] + ld [wce06], a + ld a, [hli] + ld [wce08], a + ld a, [hl] + ld [wce0f], a + ld a, $50 + ld [hld], a + ld [hld], a + ld [hl], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ld hl, wRNG1 + ld a, [wce06] + ld [hli], a + ld a, [wce08] + ld [hli], a + ld a, [wce0f] + ld [hl], a + ret +.asm_2186a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; checks whether to play Gambler. +; aside from Imakuni?, all other opponents only +; play this card if Player is running Mewtwo1-only deck. +AIDecide_Gambler: ; 21875 (8:5875) +; Imakuni? has his own routine + ld a, [wOpponentDeckID] + cp IMAKUNI_DECK_ID + jr z, .imakuni + +; check if flag is set for Player using Mewtwo1 only deck + ld a, [wAIBarrierFlagCounter] + and AI_MEWTWO_MILL + jr z, .no_carry + +; set carry if number of cards in deck <= 4. +; this is done to counteract the deck out strategy +; of Mewtwo1 deck, by replenishing the deck with hand cards. + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE - 4 + jr nc, .set_carry +.no_carry + or a + ret + +.imakuni +; has a 2 in 10 chance of returning carry + ld a, 10 + call Random + cp 2 + jr nc, .no_carry +.set_carry + scf + ret + +AIPlay_Revive: ; 21899 (8:5899) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; checks certain cards in Discard Pile to use Revive on. +; suitable for Muscle For Brains deck only. +AIDecide_Revive: ; 218a9 (8:58a9) +; skip if no cards in Discard Pile + call CreateDiscardPileCardList + jr c, .no_carry + +; skip if number of Pokemon cards in Play Area >= 4 + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 4 + jr nc, .no_carry + +; look in Discard Pile for specific cards. + ld hl, wDuelTempList +.loop_discard_pile + ld a, [hli] + cp $ff + jr z, .no_carry + ld b, a + call LoadCardDataToBuffer1_FromDeckIndex + +; these checks have a bug. +; it works fine for Hitmonchan and Hitmonlee, +; but in case it's a Tauros card, the routine will fallthrough +; into the Kangaskhan check. since it will never be equal to Kangaskhan, +; it will fallthrough into the set carry branch. +; in case it's a Kangaskhan card, the check will fail in the Tauros check +; and jump back into the loop. so just by accident the Tauros check works, +; but Kangaskhan will never be correctly checked because of this. + cp HITMONCHAN + jr z, .set_carry + cp HITMONLEE + jr z, .set_carry + cp TAUROS + jr nz, .loop_discard_pile ; bug, these two lines should be swapped + cp KANGASKHAN + jr z, .set_carry ; bug, these two lines should be swapped + +.set_carry + ld a, b + scf + ret +.no_carry + or a + ret + +AIPlay_PokemonFlute: ; 218d8 (8:58d8) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_PokemonFlute: ; 218e8 (8:58e8) +; if player has no Discard Pile, skip. + call SwapTurn + call CreateDiscardPileCardList + call SwapTurn + jr c, .no_carry + +; if player's Play Area is already full, skip. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr nc, .no_carry + + ld a, [wOpponentDeckID] + cp IMAKUNI_DECK_ID + jr z, .imakuni + + ld a, $ff + ld [wce06], a + ld [wce08], a + +; find Basic stage Pokemon with lowest HP in Discard Pile + ld hl, wDuelTempList +.loop_1 + ld a, [hli] + cp $ff + jr z, .done + + ld b, a + call SwapTurn + call LoadCardDataToBuffer1_FromDeckIndex + call SwapTurn +; skip this card if it's not Pokemon card + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr nc, .loop_1 +; skip this card if it's not Basic Stage + ld a, [wLoadedCard1Stage] + or a ; BASIC + jr nz, .loop_1 + +; compare this HP with one stored + ld a, [wLoadedCard1HP] + push hl + ld hl, wce06 + cp [hl] + pop hl + jr nc, .loop_1 +; if lower, store this one + ld [wce06], a + ld a, b + ld [wce08], a + jr .loop_1 + +.done +; if lowest HP found >= 50, return no carry + ld a, [wce06] + cp 50 + jr nc, .no_carry +; otherwise output its deck index in a and set carry. + ld a, [wce08] + scf + ret +.no_carry + or a + ret + +.imakuni +; has 2 in 10 chance of not skipping + ld a, 10 + call Random + cp 2 + jr nc, .no_carry + +; look for any Basic Pokemon card + ld hl, wDuelTempList +.loop_2 + ld a, [hli] + cp $ff + jr z, .no_carry + ld b, a + call SwapTurn + call LoadCardDataToBuffer1_FromDeckIndex + call SwapTurn + ld a, [wLoadedCard1Type] + cp TYPE_ENERGY + jr nc, .loop_2 + ld a, [wLoadedCard1Stage] + or a ; BASIC + jr nz, .loop_2 + +; a Basic stage Pokemon was found, return carry + ld a, b + scf + ret + +AIPlay_ClefairyDollOrMysteriousFossil: ; 21977 (8:5977) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; AI logic for playing Clefairy Doll +AIDecide_ClefairyDollOrMysteriousFossil: ; 21982 (8:5982) +; if has max number of Play Area Pokemon, skip + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr nc, .no_carry + +; store number of Play Area Pokemon cards + ld [wce06], a + +; if the Arena card is Wigglytuff, return carry + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp WIGGLYTUFF + jr z, .set_carry + +; if number of Play Area Pokemon >= 4, return no carry + ld a, [wce06] + cp 4 + jr nc, .no_carry + +.set_carry + scf + ret +.no_carry + or a + ret + +AIPlay_Pokeball: ; 219a6 (8:59a6) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ldtx de, TrainerCardSuccessCheckText + bank1call TossCoin + ldh [hTemp_ffa0], a + jr nc, .asm_219bc + ld a, [wAITrainerCardParameter] + ldh [hTempPlayAreaLocation_ffa1], a + jr .asm_219c0 +.asm_219bc + ld a, $ff + ldh [hTempPlayAreaLocation_ffa1], a +.asm_219c0 + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_Pokeball: ; 219c6 (8:59c6) +; go to the routines associated with deck ID + ld a, [wOpponentDeckID] + cp FIRE_CHARGE_DECK_ID + jr z, .fire_charge + cp HARD_POKEMON_DECK_ID + jr z, .hard_pokemon + cp PIKACHU_DECK_ID + jr z, .pikachu + cp ETCETERA_DECK_ID + jr z, .etcetera + cp LOVELY_NIDORAN_DECK_ID + jp z, .lovely_nidoran + or a + ret + +; this deck runs a deck check for specific +; card IDs in order of decreasing priority +.fire_charge + ld e, CHANSEY + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, TAUROS + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, JIGGLYPUFF1 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ret + +; this deck runs a deck check for specific +; card IDs in order of decreasing priority +.hard_pokemon + ld e, RHYHORN + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, RHYDON + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, ONIX + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ret + +; this deck runs a deck check for specific +; card IDs in order of decreasing priority +.pikachu + ld e, PIKACHU2 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, PIKACHU3 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, PIKACHU4 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, PIKACHU1 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, FLYING_PIKACHU + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ret + +; this deck runs a deck check for specific +; card IDs in order of decreasing priority +; given a specific energy card in hand. +; also it avoids redundancy, so if it already +; has that card ID in the hand, it is skipped. +.etcetera +; fire + ld a, FIRE_ENERGY + call LookForCardIDInHandList_Bank8 + jr nc, .lightning + ld a, CHARMANDER + call LookForCardIDInHandList_Bank8 + jr c, .lightning + ld a, MAGMAR2 + call LookForCardIDInHandList_Bank8 + jr c, .lightning + ld e, CHARMANDER + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, MAGMAR2 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + +.lightning + ld a, LIGHTNING_ENERGY + call LookForCardIDInHandList_Bank8 + jr nc, .fighting + ld a, PIKACHU1 + call LookForCardIDInHandList_Bank8 + jr c, .fighting + ld a, MAGNEMITE1 + call LookForCardIDInHandList_Bank8 + jr c, .fighting + ld e, PIKACHU1 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, MAGNEMITE1 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + +.fighting + ld a, FIGHTING_ENERGY + call LookForCardIDInHandList_Bank8 + jr nc, .psychic + ld a, DIGLETT + call LookForCardIDInHandList_Bank8 + jr c, .psychic + ld a, MACHOP + call LookForCardIDInHandList_Bank8 + jr c, .psychic + ld e, DIGLETT + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, MACHOP + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + +.psychic + ld a, PSYCHIC_ENERGY + call LookForCardIDInHandList_Bank8 + jr nc, .done_etcetera + ld a, GASTLY1 + call LookForCardIDInHandList_Bank8 + jr c, .done_etcetera + ld a, JYNX + call LookForCardIDInHandList_Bank8 + jr c, .done_etcetera + ld e, GASTLY1 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c + ld e, JYNX + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + ret c +.done_etcetera + or a + ret + +; this deck looks for card evolutions if +; its pre-evolution is in hand or in Play Area. +; if none of these are found, it looks for pre-evolutions +; of cards it has in hand. +; it does this for both the NidoranM (first) +; and NidoranF (second) families. +.lovely_nidoran + ld b, NIDORANM + ld a, NIDORINO + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + ret c + ld b, NIDORINO + ld a, NIDOKING + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + ret c + ld a, NIDORANM + ld b, NIDORINO + call LookForCardIDInDeck_GivenCardIDInHand + ret c + ld a, NIDORINO + ld b, NIDOKING + call LookForCardIDInDeck_GivenCardIDInHand + ret c + ld b, NIDORANF + ld a, NIDORINA + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + ret c + ld b, NIDORINA + ld a, NIDOQUEEN + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + ret c + ld a, NIDORANF + ld b, NIDORINA + call LookForCardIDInDeck_GivenCardIDInHand + ret c + ld a, NIDORINA + ld b, NIDOQUEEN + call LookForCardIDInDeck_GivenCardIDInHand + ret c + ret + +AIPlay_ComputerSearch: ; 21b12 (8:5b12) + ld a, [wCurrentAIFlags] + or AI_FLAG_MODIFIED_HAND + ld [wCurrentAIFlags], a + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTempRetreatCostCards], a + ld a, [wce1a] + ldh [hTemp_ffa0], a + ld a, [wce1b] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +; checks what Deck ID AI is playing and handle +; them in their own routine. +AIDecide_ComputerSearch: ; 21b34 (8:5b34) +; skip if number of cards in hand < 3 + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + cp 3 + jr c, .no_carry + + ld a, [wOpponentDeckID] + cp ROCK_CRUSHER_DECK_ID + jr z, AIDecide_ComputerSearch_RockCrusher + cp WONDERS_OF_SCIENCE_DECK_ID + jp z, AIDecide_ComputerSearch_WondersOfScience + cp FIRE_CHARGE_DECK_ID + jp z, AIDecide_ComputerSearch_FireCharge + cp ANGER_DECK_ID + jp z, AIDecide_ComputerSearch_Anger + +.no_carry + or a + ret + +AIDecide_ComputerSearch_RockCrusher: ; 21b55 (8:5b55) +; if number of cards in hand is equal to 3, +; target Professor Oak in deck + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + cp 3 + jr nz, .graveler + + ld e, PROFESSOR_OAK + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jr c, .find_discard_cards_1 + ; no Professor Oak in deck, fallthrough + +.no_carry + or a + ret + +.find_discard_cards_1 + ld [wce06], a + ld a, $ff + ld [wce1a], a + ld [wce1b], a + + call CreateHandCardList + ld hl, wDuelTempList + ld de, wce1a +.loop_hand_1 + ld a, [hli] + cp $ff + jr z, .check_discard_cards + + ld c, a + call LoadCardDataToBuffer1_FromDeckIndex + +; if any of the following cards are in the hand, +; return no carry. + cp PROFESSOR_OAK + jr z, .no_carry + cp FIGHTING_ENERGY + jr z, .no_carry + cp DOUBLE_COLORLESS_ENERGY + jr z, .no_carry + cp DIGLETT + jr z, .no_carry + cp GEODUDE + jr z, .no_carry + cp ONIX + jr z, .no_carry + cp RHYHORN + jr z, .no_carry + +; if it's same as wAITrainerCardToPlay, skip this card. + ld a, [wAITrainerCardToPlay] + ld b, a + ld a, c + cp b + jr z, .loop_hand_1 + +; store this card index in memory + ld [de], a + inc de + jr .loop_hand_1 + +.check_discard_cards +; check if two cards were found +; if so, output in a the deck index +; of Professor Oak card found in deck and set carry. + ld a, [wce1b] + cp $ff + jr z, .no_carry + ld a, [wce06] + scf + ret + +; more than 3 cards in hand, so look for +; specific evolution cards. + +; checks if there is a Graveler card in the deck to target. +; if so, check if there's Geodude in hand or Play Area, +; and if there's no Graveler card in hand, proceed. +; also removes Geodude from hand list so that it is not discarded. +.graveler + ld e, GRAVELER + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jr nc, .golem + ld [wce06], a + ld a, GEODUDE + call LookForCardIDInHandAndPlayArea + jr nc, .golem + ld a, GRAVELER + call LookForCardIDInHandList_Bank8 + jr c, .golem + call CreateHandCardList + ld hl, wDuelTempList + ld e, GEODUDE + farcall RemoveCardIDInList + jr .find_discard_cards_2 + +; checks if there is a Golem card in the deck to target. +; if so, check if there's Graveler in Play Area, +; and if there's no Golem card in hand, proceed. +.golem + ld e, GOLEM + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jr nc, .dugtrio + ld [wce06], a + ld a, GRAVELER + call LookForCardIDInPlayArea_Bank8 + jr nc, .dugtrio + ld a, GOLEM + call LookForCardIDInHandList_Bank8 + jr c, .dugtrio + call CreateHandCardList + ld hl, wDuelTempList + jr .find_discard_cards_2 + +; checks if there is a Dugtrio card in the deck to target. +; if so, check if there's Diglett in Play Area, +; and if there's no Dugtrio card in hand, proceed. +.dugtrio + ld e, DUGTRIO + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jp nc, .no_carry + ld [wce06], a + ld a, DIGLETT + call LookForCardIDInPlayArea_Bank8 + jp nc, .no_carry + ld a, DUGTRIO + call LookForCardIDInHandList_Bank8 + jp c, .no_carry + call CreateHandCardList + ld hl, wDuelTempList + jr .find_discard_cards_2 + +.find_discard_cards_2 + ld a, $ff + ld [wce1a], a + ld [wce1b], a + + ld bc, wce1a + ld d, $00 ; start considering Trainer cards only + +; stores wAITrainerCardToPlay in e so that +; all routines ignore it for the discard effects. + ld a, [wAITrainerCardToPlay] + ld e, a + +; this loop will store in wce1a cards to discard from hand. +; at the start it will only consider Trainer cards, +; then if there are still needed to discard, +; move on to Pokemon cards, and finally to Energy cards. +.loop_hand_2 + call RemoveFromListDifferentCardOfGivenType + jr c, .found + inc d ; move on to next type (Pokemon, then Energy) + ld a, $03 + cp d + jp z, .no_carry ; no more types to look + jr .loop_hand_2 +.found +; store this card in memory, +; and if there's still one more card to search for, +; jump back into the loop. + ld [bc], a + inc bc + ld a, [wce1b] + cp $ff + jr z, .loop_hand_2 + +; output in a Computer Search target and set carry. + ld a, [wce06] + scf + ret + +AIDecide_ComputerSearch_WondersOfScience: ; 21c56 (8:5c56) +; if number of cards in hand < 5, target Professor Oak in deck + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + cp 5 + jr nc, .look_in_hand + +; target Professor Oak for Computer Search + ld e, PROFESSOR_OAK + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jp nc, .look_in_hand ; can be a jr + ld [wce06], a + jr .find_discard_cards + +; Professor Oak not in deck, move on to +; look for other cards instead. +; if Grimer or Muk are not in hand, +; check whether to use Computer Search on them. +.look_in_hand + ld a, GRIMER + call LookForCardIDInHandList_Bank8 + jr nc, .target_grimer + ld a, MUK + call LookForCardIDInHandList_Bank8 + jr nc, .target_muk + +.no_carry + or a + ret + +; first check Grimer +; if in deck, check cards to discard. +.target_grimer + ld e, GRIMER + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jp nc, .no_carry ; can be a jr + ld [wce06], a + jr .find_discard_cards + +; first check Muk +; if in deck, check cards to discard. +.target_muk + ld e, MUK + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jp nc, .no_carry ; can be a jr + ld [wce06], a + +; only discard Trainer cards from hand. +; if there are less than 2 Trainer cards to discard, +; then return with no carry. +; else, store the cards to discard and the +; target card deck index, and return carry. +.find_discard_cards + call CreateHandCardList + ld hl, wDuelTempList + ld d, $00 ; first consider Trainer cards + +; ignore wAITrainerCardToPlay for the discard effects. + ld a, [wAITrainerCardToPlay] + ld e, a + call RemoveFromListDifferentCardOfGivenType + jr nc, .no_carry + ld [wce1a], a + call RemoveFromListDifferentCardOfGivenType + jr nc, .no_carry + ld [wce1b], a + ld a, [wce06] + scf + ret + +AIDecide_ComputerSearch_FireCharge: ; 21cbb (8:5cbb) +; pick target card in deck from highest to lowest priority. +; if not found in hand, go to corresponding branch. + ld a, CHANSEY + call LookForCardIDInHandList_Bank8 + jr nc, .chansey + ld a, TAUROS + call LookForCardIDInHandList_Bank8 + jr nc, .tauros + ld a, JIGGLYPUFF1 + call LookForCardIDInHandList_Bank8 + jr nc, .jigglypuff + ; fallthrough + +.no_carry + or a + ret + +; for each card targeted, check if it's in deck and, +; if not, then return no carry. +; else, look for cards to discard. +.chansey + ld e, CHANSEY + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jp nc, .no_carry + ld [wce06], a + jr .find_discard_cards +.tauros + ld e, TAUROS + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jp nc, .no_carry + ld [wce06], a + jr .find_discard_cards +.jigglypuff + ld e, JIGGLYPUFF1 + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jp nc, .no_carry + ld [wce06], a + +; only discard Trainer cards from hand. +; if there are less than 2 Trainer cards to discard, +; then return with no carry. +; else, store the cards to discard and the +; target card deck index, and return carry. +.find_discard_cards + call CreateHandCardList + ld hl, wDuelTempList + ld d, $00 ; first consider Trainer cards + +; ignore wAITrainerCardToPlay for the discard effects. + ld a, [wAITrainerCardToPlay] + ld e, a + call RemoveFromListDifferentCardOfGivenType + jr nc, .no_carry + ld [wce1a], a + call RemoveFromListDifferentCardOfGivenType + jr nc, .no_carry + ld [wce1b], a + ld a, [wce06] + scf + ret + +AIDecide_ComputerSearch_Anger: ; 21d1e (8:5d1e) +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, RATTATA + ld a, RATICATE + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_discard_cards + ld a, RATTATA + ld b, RATICATE + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_discard_cards + ld b, GROWLITHE + ld a, ARCANINE1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_discard_cards + ld a, GROWLITHE + ld b, ARCANINE1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_discard_cards + ld b, DODUO + ld a, DODRIO + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_discard_cards + ld a, DODUO + ld b, DODRIO + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_discard_cards + ; fallthrough + +.no_carry + or a + ret + +; only discard Trainer cards from hand. +; if there are less than 2 Trainer cards to discard, +; then return with no carry. +; else, store the cards to discard and the +; target card deck index, and return carry. +.find_discard_cards + ld [wce06], a + call CreateHandCardList + ld hl, wDuelTempList + ld d, $00 ; first consider Trainer cards + +; ignore wAITrainerCardToPlay for the discard effects. + ld a, [wAITrainerCardToPlay] + ld e, a + call RemoveFromListDifferentCardOfGivenType + jr nc, .no_carry + ld [wce1a], a + call RemoveFromListDifferentCardOfGivenType + jr nc, .no_carry + ld [wce1b], a + ld a, [wce06] + scf + ret + +AIPlay_PokemonTrader: ; 21d7a (8:5d7a) + ld a, [wAITrainerCardToPlay] + ldh [hTempCardIndex_ff9f], a + ld a, [wAITrainerCardParameter] + ldh [hTemp_ffa0], a + ld a, [wce1a] + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_EXECUTE_TRAINER_EFFECTS + bank1call AIMakeDecision + ret + +AIDecide_PokemonTrader: ; 21d8f (8:5d8f) +; each deck has their own routine for picking +; what Pokemon to look for. + ld a, [wOpponentDeckID] + cp LEGENDARY_MOLTRES_DECK_ID + jr z, AIDecide_PokemonTrader_LegendaryMoltres + cp LEGENDARY_ARTICUNO_DECK_ID + jr z, AIDecide_PokemonTrader_LegendaryArticuno + cp LEGENDARY_DRAGONITE_DECK_ID + jp z, AIDecide_PokemonTrader_LegendaryDragonite + cp LEGENDARY_RONALD_DECK_ID + jp z, AIDecide_PokemonTrader_LegendaryRonald + cp BLISTERING_POKEMON_DECK_ID + jp z, AIDecide_PokemonTrader_BlisteringPokemon + cp SOUND_OF_THE_WAVES_DECK_ID + jp z, AIDecide_PokemonTrader_SoundOfTheWaves + cp POWER_GENERATOR_DECK_ID + jp z, AIDecide_PokemonTrader_PowerGenerator + cp FLOWER_GARDEN_DECK_ID + jp z, AIDecide_PokemonTrader_FlowerGarden + cp STRANGE_POWER_DECK_ID + jp z, AIDecide_PokemonTrader_StrangePower + cp FLAMETHROWER_DECK_ID + jp z, AIDecide_PokemonTrader_Flamethrower + or a + ret + +AIDecide_PokemonTrader_LegendaryMoltres: ; 21dc4 (8:5dc4) +; look for Moltres2 card in deck to trade with a +; card in hand different from Moltres1. + ld a, MOLTRES2 + ld e, MOLTRES1 + call LookForCardIDToTradeWithDifferentHandCard + jr nc, .no_carry +; success + ld [wce1a], a + ld a, e + scf + ret +.no_carry + or a + ret + +AIDecide_PokemonTrader_LegendaryArticuno: ; 21dd5 (8:5dd5) +; if has none of these cards in Hand or Play Area, proceed + ld a, ARTICUNO1 + call LookForCardIDInHandAndPlayArea + jr c, .no_carry + ld a, LAPRAS + call LookForCardIDInHandAndPlayArea + jr c, .no_carry + +; if doesn't have Seel in Hand or Play Area, +; look for it in the deck. +; otherwise, look for Dewgong instead. + ld a, SEEL + call LookForCardIDInHandAndPlayArea + jr c, .dewgong + + ld e, SEEL + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jr nc, .dewgong + ld [wce1a], a + jr .check_hand + +.dewgong + ld a, DEWGONG + call LookForCardIDInHandAndPlayArea + jr c, .no_carry + ld e, DEWGONG + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jr nc, .no_carry + ld [wce1a], a + +; a Seel or Dewgong was found in deck, +; check hand for card to trade for +.check_hand + ld a, CHANSEY + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, DITTO + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, ARTICUNO2 + call CheckIfHasCardIDInHand + jr c, .set_carry + ; doesn't have any of the cards in hand + +.no_carry + or a + ret + +.set_carry + scf + ret + +AIDecide_PokemonTrader_LegendaryDragonite: ; 21e24 (8:5e24) +; if has less than 5 cards of energy +; and of Pokemon in hand/Play Area, +; target a Kangaskhan in deck. + farcall CountOppEnergyCardsInHandAndAttached + cp 5 + jr c, .kangaskhan + call CountPokemonCardsInHandAndInPlayArea + cp 5 + jr c, .kangaskhan + ; total number of energy cards >= 5 + ; total number of Pokemon cards >= 5 + +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, MAGIKARP + ld a, GYARADOS + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, MAGIKARP + ld b, GYARADOS + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld b, DRATINI + ld a, DRAGONAIR + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld b, DRAGONAIR + ld a, DRAGONITE1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, DRATINI + ld b, DRAGONAIR + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld a, DRAGONAIR + ld b, DRAGONITE1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld b, CHARMANDER + ld a, CHARMELEON + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld b, CHARMELEON + ld a, CHARIZARD + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, CHARMANDER + ld b, CHARMELEON + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld a, CHARMELEON + ld b, CHARIZARD + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + jr .no_carry + +.kangaskhan + ld e, KANGASKHAN + ld a, CARD_LOCATION_DECK + call LookForCardIDInLocation + jr nc, .no_carry + +; card was found as target in deck, +; look for card in hand to trade with +.choose_hand + ld [wce1a], a + ld a, DRAGONAIR + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, CHARMELEON + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, GYARADOS + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, MAGIKARP + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, CHARMANDER + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, DRATINI + call CheckIfHasCardIDInHand + jr c, .set_carry + ; non found + +.no_carry + or a + ret +.set_carry + scf + ret + +AIDecide_PokemonTrader_LegendaryRonald: ; 21ec9 (8:5ec9) +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, EEVEE + ld a, FLAREON1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld b, EEVEE + ld a, VAPOREON1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld b, EEVEE + ld a, JOLTEON1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, EEVEE + ld b, FLAREON1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld a, EEVEE + ld b, VAPOREON1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld a, EEVEE + ld b, JOLTEON1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld b, DRATINI + ld a, DRAGONAIR + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld b, DRAGONAIR + ld a, DRAGONITE1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, DRATINI + ld b, DRAGONAIR + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld a, DRAGONAIR + ld b, DRAGONITE1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + jr .no_carry + +; card was found as target in deck, +; look for card in hand to trade with +.choose_hand + ld [wce1a], a + ld a, ZAPDOS3 + call LookForCardIDInHandList_Bank8 + jr c, .set_carry + ld a, ARTICUNO2 + call LookForCardIDInHandList_Bank8 + jr c, .set_carry + ld a, MOLTRES2 + call LookForCardIDInHandList_Bank8 + jr c, .set_carry + ; none found + +.no_carry + or a + ret +.set_carry + scf + ret + +AIDecide_PokemonTrader_BlisteringPokemon: ; 21f41 (8:5f41) +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, RHYHORN + ld a, RHYDON + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, RHYHORN + ld b, RHYDON + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, CUBONE + ld a, MAROWAK1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, CUBONE + ld b, MAROWAK1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, PONYTA + ld a, RAPIDASH + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, PONYTA + ld b, RAPIDASH + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + jr .no_carry + +; a card in deck was found to look for, +; check if there are duplicates in hand to trade with. +.find_duplicates + ld [wce1a], a + call FindDuplicatePokemonCards + jr c, .set_carry +.no_carry + or a + ret +.set_carry + scf + ret + +AIDecide_PokemonTrader_SoundOfTheWaves: ; 21f85 (8:5f85) +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, SEEL + ld a, DEWGONG + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, SEEL + ld b, DEWGONG + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld b, KRABBY + ld a, KINGLER + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, KRABBY + ld b, KINGLER + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld b, SHELLDER + ld a, CLOYSTER + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, SHELLDER + ld b, CLOYSTER + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld b, HORSEA + ld a, SEADRA + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, HORSEA + ld b, SEADRA + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + ld b, TENTACOOL + ld a, TENTACRUEL + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .choose_hand + ld a, TENTACOOL + ld b, TENTACRUEL + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .choose_hand + jr .no_carry + +; card was found as target in deck, +; look for card in hand to trade with +.choose_hand + ld [wce1a], a + ld a, SEEL + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, KRABBY + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, HORSEA + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, SHELLDER + call CheckIfHasCardIDInHand + jr c, .set_carry + ld a, TENTACOOL + call CheckIfHasCardIDInHand + jr c, .set_carry + ; none found + +.no_carry + or a + ret +.set_carry + scf + ret + +AIDecide_PokemonTrader_PowerGenerator: ; 2200b (8:600b) +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, PIKACHU2 + ld a, RAICHU1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jp c, .find_duplicates + ld b, PIKACHU1 + ld a, RAICHU1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, PIKACHU2 + ld b, RAICHU1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, PIKACHU1 + ld b, RAICHU1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, VOLTORB + ld a, ELECTRODE2 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, VOLTORB + ld a, ELECTRODE1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, VOLTORB + ld b, ELECTRODE2 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, VOLTORB + ld b, ELECTRODE1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, MAGNEMITE1 + ld a, MAGNETON2 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, MAGNEMITE2 + ld a, MAGNETON2 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, MAGNEMITE1 + ld a, MAGNETON1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, MAGNEMITE2 + ld a, MAGNETON1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, MAGNEMITE2 + ld b, MAGNETON2 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, MAGNEMITE1 + ld b, MAGNETON2 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, MAGNEMITE2 + ld b, MAGNETON1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, MAGNEMITE1 + ld b, MAGNETON1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ; bug, missing jr .no_carry + +; since this last check falls through regardless of result, +; register a might hold an invalid deck index, +; which might lead to hilarious results like Brandon +; trading a Pikachu with a Grass Energy from the deck. +; however, since it's deep in a tower of conditionals, +; reaching here is extremely unlikely. + +; a card in deck was found to look for, +; check if there are duplicates in hand to trade with. +.find_duplicates + ld [wce1a], a + call FindDuplicatePokemonCards + jr c, .set_carry + or a + ret +.set_carry + scf + ret + +AIDecide_PokemonTrader_FlowerGarden: ; 220a8 (8:60a8) +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, BULBASAUR + ld a, IVYSAUR + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, IVYSAUR + ld a, VENUSAUR2 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, BULBASAUR + ld b, IVYSAUR + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, IVYSAUR + ld b, VENUSAUR2 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, BELLSPROUT + ld a, WEEPINBELL + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, WEEPINBELL + ld a, VICTREEBEL + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, BELLSPROUT + ld b, WEEPINBELL + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, WEEPINBELL + ld b, VICTREEBEL + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, ODDISH + ld a, GLOOM + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, GLOOM + ld a, VILEPLUME + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, ODDISH + ld b, GLOOM + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, GLOOM + ld b, VILEPLUME + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + jr .no_carry + +; a card in deck was found to look for, +; check if there are duplicates in hand to trade with. +.find_duplicates + ld [wce1a], a + call FindDuplicatePokemonCards + jr c, .found +.no_carry + or a + ret +.found + scf + ret + +AIDecide_PokemonTrader_StrangePower: ; 22122 (8:6122) +; looks for a Pokemon in hand to trade with Mr Mime in deck. +; inputting Mr Mime in register e for the function is redundant +; since it already checks whether a Mr Mime exists in the hand. + ld a, MR_MIME + ld e, MR_MIME + call LookForCardIDToTradeWithDifferentHandCard + jr nc, .no_carry +; found + ld [wce1a], a + ld a, e + scf + ret +.no_carry + or a + ret + +AIDecide_PokemonTrader_Flamethrower: ; 22133 (8:6133) +; for each of the following cards, +; first run a check if there's a pre-evolution in +; Play Area or in the hand. If there is, choose it as target. +; otherwise, check if the evolution card is in +; hand and if so, choose it as target instead. + ld b, CHARMANDER + ld a, CHARMELEON + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld b, CHARMELEON + ld a, CHARIZARD + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, CHARMANDER + ld b, CHARMELEON + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld a, CHARMELEON + ld b, CHARIZARD + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, VULPIX + ld a, NINETALES1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, VULPIX + ld b, NINETALES1 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, GROWLITHE + ld a, ARCANINE2 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, GROWLITHE + ld b, ARCANINE2 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + ld b, EEVEE + ld a, FLAREON2 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, EEVEE + ld b, FLAREON2 + call LookForCardIDInDeck_GivenCardIDInHand + jr c, .find_duplicates + jr .no_carry + +; a card in deck was found to look for, +; check if there are duplicates in hand to trade with. +.find_duplicates + ld [wce1a], a + call FindDuplicatePokemonCards + jr c, .set_carry +.no_carry + or a + ret +.set_carry + scf + ret diff --git a/src/engine/duel/effect_functions.asm b/src/engine/duel/effect_functions.asm new file mode 100644 index 0000000..ce3a517 --- /dev/null +++ b/src/engine/duel/effect_functions.asm @@ -0,0 +1,11194 @@ +Poison50PercentEffect: ; 2c000 (b:4000) + ldtx de, PoisonCheckText + call TossCoin_BankB + ret nc + +PoisonEffect: ; 2c007 (b:4007) + lb bc, CNF_SLP_PRZ, POISONED + jr ApplyStatusEffect + +DoublePoisonEffect: ; 2c00c (b:400c) + lb bc, CNF_SLP_PRZ, DOUBLE_POISONED + jr ApplyStatusEffect + +Paralysis50PercentEffect: ; 2c011 (b:4011) + ldtx de, ParalysisCheckText + call TossCoin_BankB + ret nc + +ParalysisEffect: ; 2c018 (b:4018) + lb bc, PSN_DBLPSN, PARALYZED + jr ApplyStatusEffect + +Confusion50PercentEffect: ; 2c01d (b:401d) + ldtx de, ConfusionCheckText + call TossCoin_BankB + ret nc + +ConfusionEffect: ; 2c024 (b:4024) + lb bc, PSN_DBLPSN, CONFUSED + jr ApplyStatusEffect + +Sleep50PercentEffect: ; 2c029 (b:4029) + ldtx de, SleepCheckText + call TossCoin_BankB + ret nc + +SleepEffect: ; 2c030 (b:4030) + lb bc, PSN_DBLPSN, ASLEEP + jr ApplyStatusEffect + +ApplyStatusEffect: ; 2c035 (b:4035) + ldh a, [hWhoseTurn] + ld hl, wWhoseTurn + cp [hl] + jr nz, .can_induce_status + ld a, [wTempNonTurnDuelistCardID] + cp CLEFAIRY_DOLL + jr z, .cant_induce_status + cp MYSTERIOUS_FOSSIL + jr z, .cant_induce_status + ; Snorlax's Thick Skinned prevents it from being statused... + cp SNORLAX + jr nz, .can_induce_status + call SwapTurn + xor a + ; ...unless already so, or if affected by Muk's Toxic Gas + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + call SwapTurn + jr c, .can_induce_status + +.cant_induce_status + ld a, c + ld [wNoEffectFromWhichStatus], a + call SetNoEffectFromStatus + or a + ret + +.can_induce_status + ld hl, wEffectFunctionsFeedbackIndex + push hl + ld e, [hl] + ld d, $0 + ld hl, wEffectFunctionsFeedback + add hl, de + call SwapTurn + ldh a, [hWhoseTurn] + ld [hli], a + call SwapTurn + ld [hl], b ; mask of status conditions not to discard on the target + inc hl + ld [hl], c ; status condition to inflict to the target + pop hl + ; advance wEffectFunctionsFeedbackIndex + inc [hl] + inc [hl] + inc [hl] + scf + ret + +TossCoin_BankB: ; 2c07e (b:407e) + call TossCoin + ret + +TossCoinATimes_BankB: ; 2c082 (b:4082) + call TossCoinATimes + ret + +CommentedOut_2c086: ; 2c086 (b:4086) + ret + +Func_2c087: ; 2c087 (b:4087) + xor a + jr Func_2c08c + +Func_2c08a: ; 2c08a (b:408a) + ld a, $1 + +Func_2c08c: ; 2c08c (b:408c) + push de + push af + ld a, OPPACTION_TOSS_COIN_A_TIMES + call SetOppAction_SerialSendDuelData + pop af + pop de + call SerialSend8Bytes + call TossCoinATimes + ret + +SetNoEffectFromStatus: ; 2c09c (b:409c) + ld a, EFFECT_FAILED_NO_EFFECT + ld [wEffectFailed], a + ret + +SetWasUnsuccessful: ; 2c0a2 (b:40a2) + ld a, EFFECT_FAILED_UNSUCCESSFUL + ld [wEffectFailed], a + ret + +Func_2c0a8: ; 2c0a8 (b:40a8) + ldh a, [hTemp_ffa0] + push af + ldh a, [hWhoseTurn] + ldh [hTemp_ffa0], a + ld a, OPPACTION_6B30 + call SetOppAction_SerialSendDuelData + bank1call Func_4f2d + ld c, a + pop af + ldh [hTemp_ffa0], a + ld a, c + ret + +Func_2c0bd: ; 2c0bd (b:40bd) + call ExchangeRNG + bank1call Func_4f2d + call ShuffleDeck + ret + +; return carry if Player is the Turn Duelist +IsPlayerTurn: ; 2c0c7 (b:40c7) + ld a, DUELVARS_DUELIST_TYPE + call GetTurnDuelistVariable + cp DUELIST_TYPE_PLAYER + jr z, .player + or a + ret +.player + scf + ret + +; Stores information about the attack damage for AI purposes +; taking into account poison damage between turns. +; if target poisoned +; [wAIMinDamage] <- [wDamage] +; [wAIMaxDamage] <- [wDamage] +; else +; [wAIMinDamage] <- [wDamage] + d +; [wAIMaxDamage] <- [wDamage] + e +; [wDamage] <- [wDamage] + a +UpdateExpectedAIDamage_AccountForPoison: ; 2c0d4 (b:40d4) + push af + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + and POISONED | DOUBLE_POISONED + jr z, UpdateExpectedAIDamage.skip_push_af + pop af + ld a, [wDamage] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + ret + +; Sets some variables for AI use +; [wAIMinDamage] <- [wDamage] + d +; [wAIMaxDamage] <- [wDamage] + e +; [wDamage] <- [wDamage] + a +UpdateExpectedAIDamage: ; 2c0e9 (b:40e9) + push af + +.skip_push_af + ld hl, wDamage + ld a, [hl] + add d + ld [wAIMinDamage], a + ld a, [hl] + add e + ld [wAIMaxDamage], a + pop af + add [hl] + ld [hl], a + ret + +; Stores information about the attack damage for AI purposes +; [wDamage] <- a (average amount of damage) +; [wAIMinDamage] <- d (minimum) +; [wAIMaxDamage] <- e (maximum) +SetExpectedAIDamage: ; 2c0fb (b:40fb) + ld [wDamage], a + xor a + ld [wDamage + 1], a + ld a, d + ld [wAIMinDamage], a + ld a, e + ld [wAIMaxDamage], a + ret + +Func_2c10b: ; 2c10b (b:410b) + ldh [hTempPlayAreaLocation_ff9d], a + bank1call Func_61a1 + bank1call PrintPlayAreaCardList_EnableLCD + bank1call Func_6194 + ret + +; deal damage to all the turn holder's benched Pokemon +; input: a = amount of damage to deal to each Pokemon +DealDamageToAllBenchedPokemon: ; 2c117 (b:4117) + ld e, a + ld d, $00 + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld b, PLAY_AREA_ARENA + jr .skip_to_bench +.loop + push bc + call DealDamageToPlayAreaPokemon_RegularAnim + pop bc +.skip_to_bench + inc b + dec c + jr nz, .loop + ret + +Func_2c12e: ; 2c12e (b:412e) + ld [wLoadedAttackAnimation], a + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld c, $0 ; neither WEAKNESS nor RESISTANCE + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation + ret + +; apply a status condition of type 1 identified by register a to the target +ApplySubstatus1ToDefendingCard: ; 2c140 (b:4140) + push af + ld a, DUELVARS_ARENA_CARD_SUBSTATUS1 + call GetTurnDuelistVariable + pop af + ld [hli], a + ret + +; apply a status condition of type 2 identified by register a to the target, +; unless prevented by wNoDamageOrEffect +ApplySubstatus2ToDefendingCard: ; 2c149 (b:4149) + push af + call CheckNoDamageOrEffect + jr c, .no_damage_orEffect + ld a, DUELVARS_ARENA_CARD_SUBSTATUS2 + call GetNonTurnDuelistVariable + pop af + ld [hl], a + ld l, $f6 + ld [hl], a + ret + +.no_damage_orEffect + pop af + push hl + bank1call DrawDuelMainScene + pop hl + ld a, l + or h + call nz, DrawWideTextBox_PrintText + ret + +; overwrites in wDamage, wAIMinDamage and wAIMaxDamage +; with the value in a. +SetDefiniteDamage: ; 2c166 (b:4166) + ld [wDamage], a + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + xor a + ld [wDamage + 1], a + ret + +; overwrites wAIMinDamage and wAIMaxDamage +; with value in wDamage. +SetDefiniteAIDamage: ; 2c174 (b:4174) + ld a, [wDamage] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + ret + +; returns in a some random occupied Play Area location +; in Turn Duelist's Play Area. +PickRandomPlayAreaCard: ; 2c17e (b:417e) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + call Random + or a + ret + +; outputs in hl the next position +; in hTempList to place a new card, +; and increments hCurSelectionItem. +GetNextPositionInTempList: ; 2c188 (b:4188) + push de + ld hl, hCurSelectionItem + ld a, [hl] + inc [hl] + ld e, a + ld d, $00 + ld hl, hTempList + add hl, de + pop de + ret + +; creates in wDuelTempList list of attached Fire Energy cards +; that are attached to the Turn Duelist's Arena card. +CreateListOfFireEnergyAttachedToArena: ; 2c197 (b:4197) + ld a, TYPE_ENERGY_FIRE + ; fallthrough + +; creates in wDuelTempList a list of cards that +; are in the Arena of the same type as input a. +; this is called to list Energy cards of a specific type +; that are attached to the Arena Pokemon. +; input: +; a = TYPE_ENERGY_* constant +; output: +; a = number of cards in list; +; wDuelTempList filled with cards, terminated by $ff +CreateListOfEnergyAttachedToArena: ; 2c199 (b:4199) + ld b, a + ld c, 0 + ld de, wDuelTempList + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop + ld a, [hl] + cp CARD_LOCATION_ARENA + jr nz, .next + push de + ld a, l + call GetCardIDFromDeckIndex + call GetCardType + pop de + cp b + jr nz, .next ; is same as input type? + ld a, l + ld [de], a + inc de + inc c +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop + + ld a, $ff + ld [de], a + ld a, c + ret + +; prints the text "<X> devolved to <Y>!" with +; the proper card names and levels. +; input: +; d = deck index of the lower stage card +; e = deck index of card that was devolved +PrintDevolvedCardNameAndLevelText: ; 2c1c4 (b:41c4) + push de + ld a, e + call LoadCardDataToBuffer1_FromDeckIndex + ld bc, wTxRam2 + ld hl, wLoadedCard1Name + ld a, [hli] + ld [bc], a + inc bc + ld a, [hl] + ld [bc], a + + inc bc ; wTxRam2_b + xor a + ld [bc], a + inc bc + ld [bc], a + + ld a, d + call LoadCardDataToBuffer1_FromDeckIndex + ld a, 18 + call CopyCardNameAndLevel + ld [hl], $00 + ldtx hl, PokemonDevolvedToText + call DrawWideTextBox_WaitForInput + pop de + ret + +HandleSwitchDefendingPokemonEffect: ; 2c1ec (b:41ec) + ld e, a + cp $ff + ret z + +; check Defending Pokemon's HP + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + or a + jr nz, .switch + +; if 0, handle Destiny Bond first + push de + bank1call HandleDestinyBondSubstatus + pop de + +.switch + call HandleNoDamageOrEffect + ret c + +; attack was successful, switch Defending Pokemon + call SwapTurn + call SwapArenaWithBenchPokemon + call SwapTurn + + xor a + ld [wccc5], a + ld [wDuelDisplayedScreen], a + inc a + ld [wccef], a + ret + +; returns carry if Defending has No Damage or Effect +; if so, print its appropriate text. +HandleNoDamageOrEffect: ; 2c216 (b:4216) + call CheckNoDamageOrEffect + ret nc + ld a, l + or h + call nz, DrawWideTextBox_PrintText + scf + ret + +; applies HP recovery on Pokemon after an attack +; with HP recovery effect, and handles its animation. +; input: +; d = damage effectiveness +; e = HP amount to recover +ApplyAndAnimateHPRecovery: ; 2c221 (b:4221) + push de + ld hl, wccbd + ld [hl], e + inc hl + ld [hl], d + +; get Arena card's damage + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + pop de + or a + ret z ; return if no damage + +; load correct animation + push de + ld a, ATK_ANIM_HEAL + ld [wLoadedAttackAnimation], a + ld bc, $01 ; arrow + bank1call PlayAttackAnimation + +; compare HP to be restored with max HP +; if HP to be restored would cause HP to +; be larger than max HP, cap it accordingly + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ld b, $00 + pop de + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + add e + ld e, a + ld a, 0 + adc d + ld d, a + ; de = damage dealt + current HP + ; bc = max HP of card + call CompareDEtoBC + jr c, .skip_cap + ; cap de to value in bc + ld e, c + ld d, b + +.skip_cap + ld [hl], e ; apply new HP to arena card + bank1call WaitAttackAnimation + ret + +; returns carry if Play Area has no damage counters. +CheckIfPlayAreaHasAnyDamage: ; 2c25b (b:425b) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA +.loop_play_area + call GetCardDamageAndMaxHP + or a + ret nz ; found damage + inc e + dec d + jr nz, .loop_play_area + ; no damage found + scf + ret + +; makes a list in wDuelTempList with the deck indices +; of Trainer cards found in Turn Duelist's Discard Pile. +; returns carry set if no Trainer cards found, and loads +; corresponding text to notify this. +CreateTrainerCardListFromDiscardPile: ; 2c26e (b:426e) +; get number of cards in Discard Pile +; and have hl point to the end of the +; Discard Pile list in wOpponentDeckCards. + ld a, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE + call GetTurnDuelistVariable + ld b, a + add DUELVARS_DECK_CARDS + ld l, a + + ld de, wDuelTempList + inc b + jr .next_card + +.check_trainer + ld a, [hl] + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_TRAINER + jr nz, .next_card + + ld a, [hl] + ld [de], a + inc de + +.next_card + dec l + dec b + jr nz, .check_trainer + + ld a, $ff ; terminating byte + ld [de], a + ld a, [wDuelTempList] + cp $ff + jr z, .no_trainers + or a + ret +.no_trainers + ldtx hl, ThereAreNoTrainerCardsInDiscardPileText + scf + ret + +; makes a list in wDuelTempList with the deck indices +; of all basic energy cards found in Turn Duelist's Discard Pile. +CreateEnergyCardListFromDiscardPile_OnlyBasic: ; 2c2a0 (b:42a0) + ld c, $01 + jr CreateEnergyCardListFromDiscardPile + +; makes a list in wDuelTempList with the deck indices +; of all energy cards (including Double Colorless) +; found in Turn Duelist's Discard Pile. +CreateEnergyCardListFromDiscardPile_AllEnergy: ; 2c2a4 (b:42a4) + ld c, $00 +; fallthrough + +; makes a list in wDuelTempList with the deck indices +; of energy cards found in Turn Duelist's Discard Pile. +; if (c == 0), all energy cards are allowed; +; if (c != 0), double colorless energy cards are not included. +; returns carry if no energy cards were found. +CreateEnergyCardListFromDiscardPile: ; 2c2a6 (b:42a6) +; get number of cards in Discard Pile +; and have hl point to the end of the +; Discard Pile list in wOpponentDeckCards. + ld a, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE + call GetTurnDuelistVariable + ld b, a + add DUELVARS_DECK_CARDS + ld l, a + + ld de, wDuelTempList + inc b + jr .next_card + +.check_energy + ld a, [hl] + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + and TYPE_ENERGY + jr z, .next_card + +; if (c != $00), then we dismiss Double Colorless +; energy cards found. + ld a, c + or a + jr z, .copy + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY_DOUBLE_COLORLESS + jr nc, .next_card + +.copy + ld a, [hl] + ld [de], a + inc de + +; goes through Discard Pile list +; in wOpponentDeckCards in descending order. +.next_card + dec l + dec b + jr nz, .check_energy + +; terminating byte on wDuelTempList + ld a, $ff + ld [de], a + +; check if any energy card was found +; by checking whether the first byte +; in wDuelTempList is $ff. +; if none were found, return carry. + ld a, [wDuelTempList] + cp $ff + jr z, .set_carry + or a + ret + +.set_carry + scf + ret + +; returns carry if Deck is empty +CheckIfDeckIsEmpty: ; 2c2e0 (b:42e0) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ldtx hl, NoCardsLeftInTheDeckText + cp DECK_SIZE + ccf + ret + +; searches through Deck in wDuelTempList looking for +; a certain card or cards, and prints text depending +; on whether at least one was found. +; if none were found, asks the Player whether to look +; in the Deck anyway, and returns carry if No is selected. +; uses SEARCHEFFECT_* as input which determines what to search for: +; SEARCHEFFECT_CARD_ID = search for card ID in e +; SEARCHEFFECT_NIDORAN = search for either NidoranM or NidoranF +; SEARCHEFFECT_BASIC_FIGHTING = search for any Basic Fighting Pokemon +; SEARCHEFFECT_BASIC_ENERGY = search for any Basic Energy +; SEARCHEFFECT_POKEMON = search for any Pokemon card +; input: +; d = SEARCHEFFECT_* constant +; e = (optional) card ID to search for in deck +; hl = text to print if Deck has card(s) +; output: +; carry set if refused to look at deck +LookForCardsInDeck: ; 2c2ec (b:42ec) + push hl + push bc + ld a, [wDuelTempList] + cp $ff + jr z, .none_in_deck + ld a, d + ld hl, .search_table + call JumpToFunctionInTable + jr c, .none_in_deck + pop bc + pop hl + call DrawWideTextBox_WaitForInput + or a + ret + +.none_in_deck + pop hl + call LoadTxRam2 + pop hl + ldtx hl, ThereIsNoInTheDeckText + call DrawWideTextBox_WaitForInput + ldtx hl, WouldYouLikeToCheckTheDeckText + call YesOrNoMenuWithText_SetCursorToYes + ret + +.search_table + dw .SearchDeckForCardID + dw .SearchDeckForNidoran + dw .SearchDeckForBasicFighting + dw .SearchDeckForBasicEnergy + dw .SearchDeckForPokemon + +.set_carry ; 2c321 (b:4321) + scf + ret + +; returns carry if no card with +; same card ID as e is found in Deck +.SearchDeckForCardID ; 2c323 (b:4323) + ld hl, wDuelTempList +.loop_deck_e + ld a, [hli] + cp $ff + jr z, .set_carry + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp e + jr nz, .loop_deck_e + or a + ret + +; returns carry if no NidoranM or NidoranF card is found in Deck +.SearchDeckForNidoran ; 2c336 (b:4336) + ld hl, wDuelTempList +.loop_deck_nidoran + ld a, [hli] + cp $ff + jr z, .set_carry + call GetCardIDFromDeckIndex + ld a, e + cp NIDORANF + jr z, .found_nidoran + cp NIDORANM + jr nz, .loop_deck_nidoran +.found_nidoran + or a + ret + +; returns carry if no Basic Fighting Pokemon is found in Deck +.SearchDeckForBasicFighting ; 2c34c (b:434c) + ld hl, wDuelTempList +.loop_deck_fighting + ld a, [hli] + cp $ff + jr z, .set_carry + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_PKMN_FIGHTING + jr nz, .loop_deck_fighting + ld a, [wLoadedCard2Stage] + or a ; BASIC + jr nz, .loop_deck_fighting + ret + +; returns carry if no Basic Energy cards are found in Deck +.SearchDeckForBasicEnergy ; 2c365 (b:4365) + ld hl, wDuelTempList +.loop_deck_energy + ld a, [hli] + cp $ff + jr z, .set_carry + call GetCardIDFromDeckIndex + call GetCardType + cp TYPE_ENERGY_DOUBLE_COLORLESS + jr z, .loop_deck_energy + and TYPE_ENERGY + jr z, .loop_deck_energy + or a + ret + +; returns carry if no Pokemon cards are found in Deck +.SearchDeckForPokemon ; 2c37d (b:437d) + ld hl, wDuelTempList +.loop_deck_pkmn + ld a, [hli] + cp $ff + jr z, .set_carry + call GetCardIDFromDeckIndex + call GetCardType + cp TYPE_ENERGY + jr nc, .loop_deck_pkmn + or a + ret + +; handles the Player selection of attack +; to use, i.e. Amnesia or Metronome on. +; returns carry if none selected. +; outputs: +; d = card index of defending card +; e = attack index selected +HandleDefendingPokemonAttackSelection: ; 2c391 (b:4391) + bank1call DrawDuelMainScene + call SwapTurn + xor a + ldh [hCurSelectionItem], a + +.start + bank1call PrintAndLoadAttacksToDuelTempList + push af + ldh a, [hCurSelectionItem] + ld hl, .menu_parameters + call InitializeMenuParameters + pop af + + ld [wNumMenuItems], a + call EnableLCD +.loop_input + call DoFrame + ldh a, [hKeysPressed] + bit B_BUTTON_F, a + jr nz, .set_carry + and START + jr nz, .open_atk_page + call HandleMenuInput + jr nc, .loop_input + cp -1 + jr z, .loop_input + +; an attack was selected + ldh a, [hCurMenuItem] + add a + ld e, a + ld d, $00 + ld hl, wDuelTempList + add hl, de + ld d, [hl] + inc hl + ld e, [hl] + call SwapTurn + or a + ret + +.set_carry + call SwapTurn + scf + ret + +.open_atk_page + ldh a, [hCurMenuItem] + ldh [hCurSelectionItem], a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + bank1call OpenAttackPage + call SwapTurn + bank1call DrawDuelMainScene + call SwapTurn + jr .start + +.menu_parameters + db 1, 13 ; cursor x, cursor y + db 2 ; y displacement between items + db 2 ; number of items + db SYM_CURSOR_R ; cursor tile number + db SYM_SPACE ; tile behind cursor + dw NULL ; function pointer if non-0 + +; loads in hl the pointer to attack's name. +; input: +; d = deck index of card +; e = attack index (0 = first attack, 1 = second attack) +GetAttackName: ; 2c3fc (b:43fc) + ld a, d + call LoadCardDataToBuffer1_FromDeckIndex + ld hl, wLoadedCard1Atk1Name + inc e + dec e + jr z, .load_name + ld hl, wLoadedCard1Atk2Name +.load_name + ld a, [hli] + ld h, [hl] + ld l, a + ret + +; returns carry if Defending Pokemon +; doesn't have an attack. +CheckIfDefendingPokemonHasAnyAttack: ; 2c40e (b:440e) + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Atk1Category] + cp POKEMON_POWER + jr nz, .has_attack + ld hl, wLoadedCard2Atk2Name + ld a, [hli] + or [hl] + jr nz, .has_attack + call SwapTurn + scf + ret +.has_attack + call SwapTurn + or a + ret + +; overwrites HP and Stage data of the card that was +; devolved in the Play Area to the values of new card. +; if the damage exceeds HP of pre-evolution, +; then HP is set to zero. +; input: +; a = card index of pre-evolved card +UpdateDevolvedCardHPAndStage: ; 2c431 (b:4431) + push bc + push de + push af + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetCardDamageAndMaxHP + ld b, a ; store damage + ld a, e + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + pop af + + ld [hl], a + call LoadCardDataToBuffer2_FromDeckIndex + ld a, e + add DUELVARS_ARENA_CARD_HP + ld l, a + ld a, [wLoadedCard2HP] + sub b ; subtract damage from new HP + jr nc, .got_hp + ; damage exceeds HP + xor a ; 0 HP +.got_hp + ld [hl], a + ld a, e +; overwrite card stage + add DUELVARS_ARENA_CARD_STAGE + ld l, a + ld a, [wLoadedCard2Stage] + ld [hl], a + pop de + pop bc + ret + +; reset various status after devolving card. +ResetDevolvedCardStatus: ; 2c45d (b:445d) +; if it's Arena card, clear status conditions + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .skip_clear_status + call ClearAllStatusConditions +.skip_clear_status +; reset changed color status + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_CHANGED_TYPE + call GetTurnDuelistVariable + ld [hl], $00 +; reset C2 flags + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_FLAGS + ld l, a + ld [hl], $00 + ret + +; prompts the Player with a Yes/No question +; whether to quit the screen, even though +; they can select more cards from list. +; [hCurSelectionItem] holds number of cards +; that were already selected by the Player. +; input: +; - a = total number of cards that can be selected +; output: +; - carry set if "No" was selected +AskWhetherToQuitSelectingCards: ; 2c476 (b:4476) + ld hl, hCurSelectionItem + sub [hl] + ld l, a + ld h, $00 + call LoadTxRam3 + ldtx hl, YouCanSelectMoreCardsQuitText + call YesOrNoMenuWithText + ret + +; handles the selection of a forced switch by link/AI opponent or by the player. +; outputs the Play Area location of the chosen bench card in hTempPlayAreaLocation_ff9d. +DuelistSelectForcedSwitch: ; 2c487 (b:4487) + ld a, DUELVARS_DUELIST_TYPE + call GetNonTurnDuelistVariable + cp DUELIST_TYPE_LINK_OPP + jr z, .link_opp + + cp DUELIST_TYPE_PLAYER + jr z, .player + +; AI opponent + call SwapTurn + bank1call AIDoAction_ForcedSwitch + call SwapTurn + + ld a, [wPlayerAttackingAttackIndex] + ld e, a + ld a, [wPlayerAttackingCardIndex] + ld d, a + ld a, [wPlayerAttackingCardID] + call CopyAttackDataAndDamage_FromCardID + call Func_16f6 + ret + +.player + ldtx hl, SelectPkmnOnBenchToSwitchWithActiveText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench + ld a, $01 + ld [wcbd4], a +.asm_2c4c0 + bank1call OpenPlayAreaScreenForSelection + jr c, .asm_2c4c0 + call SwapTurn + ret + +.link_opp +; get selection from link opponent + ld a, OPPACTION_FORCE_SWITCH_ACTIVE + call SetOppAction_SerialSendDuelData +.loop + call SerialRecvByte + jr nc, .received + halt + nop + jr .loop +.received + ldh [hTempPlayAreaLocation_ff9d], a + ret + +; returns in a the card index of energy card +; attached to Defending Pokemon +; that is to be discarded by the AI for an effect. +; outputs $ff is none was found. +; output: +; a = deck index of attached energy card chosen +AIPickEnergyCardToDiscardFromDefendingPokemon: ; 2c4da (b:44da) + call SwapTurn + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + + xor a + call CreateArenaOrBenchEnergyCardList + jr nc, .has_energy + ; no energy, return + ld a, $ff + jr .done + +.has_energy + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld e, COLORLESS + ld a, [wAttachedEnergies + COLORLESS] + or a + jr nz, .pick_color ; has colorless attached? + + ; no colorless energy attached. + ; if it's colorless Pokemon, just + ; pick any energy card at random... + ld a, [wLoadedCard1Type] + cp COLORLESS + jr nc, .choose_random + + ; ...if not, check if it has its + ; own color energy attached. + ; if it doesn't, pick at random. + ld e, a + ld d, $00 + ld hl, wAttachedEnergies + add hl, de + ld a, [hl] + or a + jr z, .choose_random + +; pick attached card with same color as e +.pick_color + ld hl, wDuelTempList +.loop_energy + ld a, [hli] + cp $ff + jr z, .choose_random + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + and TYPE_PKMN + cp e + jr nz, .loop_energy + dec hl + +.done_chosen + ld a, [hl] +.done + call SwapTurn + ret + +.choose_random + call CountCardsInDuelTempList + ld hl, wDuelTempList + call ShuffleCards + jr .done_chosen + +; handles AI logic to pick attack for Amnesia +AIPickAttackForAmnesia: ; 2c532 (b:4532) +; load Defending Pokemon attacks + call SwapTurn + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + call HandleEnergyBurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call LoadCardDataToBuffer2_FromDeckIndex +; if has no attack 1 name, return + ld hl, wLoadedCard2Atk1Name + ld a, [hli] + or [hl] + jr z, .chosen + +; if Defending Pokemon has enough energy for second attack, choose it + ld e, SECOND_ATTACK + bank1call _CheckIfEnoughEnergiesToAttack + jr nc, .chosen +; otherwise if first attack isn't a Pkmn Power, choose it instead. + ld e, FIRST_ATTACK_OR_PKMN_POWER + ld a, [wLoadedCard2Atk1Category] + cp POKEMON_POWER + jr nz, .chosen +; if it is a Pkmn Power, choose second attack. + ld e, SECOND_ATTACK +.chosen + ld a, e + call SwapTurn + ret + +; Return in a the PLAY_AREA_* of the non-turn holder's Pokemon card in bench with the lowest (remaining) HP. +; if multiple cards are tied for the lowest HP, the one with the highest PLAY_AREA_* is returned. +GetBenchPokemonWithLowestHP: ; 2c564 (b:4564) + call SwapTurn + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + lb de, PLAY_AREA_ARENA, $ff + ld b, d + ld a, DUELVARS_BENCH1_CARD_HP + call GetTurnDuelistVariable + jr .start +; find Play Area location with least amount of HP +.loop_bench + ld a, e + cp [hl] + jr c, .next ; skip if HP is higher + ld e, [hl] + ld d, b + +.next + inc hl +.start + inc b + dec c + jr nz, .loop_bench + + ld a, d + call SwapTurn + ret + +; handles drawing and selection of screen for +; choosing a color (excluding colorless), for use +; of Shift Pkmn Power and Conversion attacks. +; outputs in a the color that was selected or, +; if B was pressed, returns carry. +; input: +; a = Play Area location (PLAY_AREA_*), with: +; bit 7 not set if it's applying to opponent's card +; bit 7 set if it's applying to player's card +; hl = text to be printed in the bottom box +; output: +; a = color that was selected +HandleColorChangeScreen: ; 2c588 (b:4588) + or a + call z, SwapTurn + push af + call .DrawScreen + pop af + call z, SwapTurn + + ld hl, .menu_params + xor a + call InitializeMenuParameters + call EnableLCD + +.loop_input + call DoFrame + call HandleMenuInput + jr nc, .loop_input + cp -1 ; b pressed? + jr z, .set_carry + ld e, a + ld d, $00 + ld hl, ShiftListItemToColor + add hl, de + ld a, [hl] + or a + ret +.set_carry + scf + ret + +.menu_params + db 1, 1 ; cursor x, cursor y + db 2 ; y displacement between items + db MAX_PLAY_AREA_POKEMON ; number of items + db SYM_CURSOR_R ; cursor tile number + db SYM_SPACE ; tile behind cursor + dw NULL ; function pointer if non-0 + +.DrawScreen: ; 2c5be (b:45be) + push hl + push af + call EmptyScreen + call ZeroObjectPositions + call LoadDuelCardSymbolTiles + +; load card data + pop af + and $7f + ld [wTempPlayAreaLocation_cceb], a + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + +; draw card gfx + ld de, v0Tiles1 + $20 tiles ; destination offset of loaded gfx + ld hl, wLoadedCard1Gfx + ld a, [hli] + ld h, [hl] + ld l, a + lb bc, $30, TILE_SIZE + call LoadCardGfx + bank1call SetBGP6OrSGB3ToCardPalette + bank1call FlushAllPalettesOrSendPal23Packet + ld a, $a0 + lb hl, 6, 1 + lb de, 9, 2 + lb bc, 8, 6 + call FillRectangle + bank1call ApplyBGP6OrSGB3ToCardImage + +; print card name and level at the top + ld a, 16 + call CopyCardNameAndLevel + ld [hl], $00 + lb de, 7, 0 + call InitTextPrinting + ld hl, wDefaultText + call ProcessText + +; list all the colors + ld hl, ShiftMenuData + call PlaceTextItems + +; print card's color, resistance and weakness + ld a, [wTempPlayAreaLocation_cceb] + call GetPlayAreaCardColor + inc a + lb bc, 15, 9 + call WriteByteToBGMap0 + ld a, [wTempPlayAreaLocation_cceb] + call GetPlayAreaCardWeakness + lb bc, 15, 10 + bank1call PrintCardPageWeaknessesOrResistances + ld a, [wTempPlayAreaLocation_cceb] + call GetPlayAreaCardResistance + lb bc, 15, 11 + bank1call PrintCardPageWeaknessesOrResistances + + call DrawWideTextBox + +; print list of color names on all list items + lb de, 4, 1 + ldtx hl, ColorListText + call InitTextPrinting_ProcessTextFromID + +; print input hl to text box + lb de, 1, 14 + pop hl + call InitTextPrinting_ProcessTextFromID + +; draw and apply palette to color icons + ld hl, ColorTileAndBGP + lb de, 2, 0 + ld c, NUM_COLORED_TYPES +.loop_colors + ld a, [hli] + push de + push bc + push hl + lb hl, 1, 2 + lb bc, 2, 2 + call FillRectangle + + ld a, [wConsole] + cp CONSOLE_CGB + jr nz, .skip_vram1 + pop hl + push hl + call BankswitchVRAM1 + ld a, [hl] + lb hl, 0, 0 + lb bc, 2, 2 + call FillRectangle + call BankswitchVRAM0 + +.skip_vram1 + pop hl + pop bc + pop de + inc hl + inc e + inc e + dec c + jr nz, .loop_colors + ret + +; loads wTxRam2 and wTxRam2_b: +; [wTxRam2] <- wLoadedCard1Name +; [wTxRam2_b] <- input color as text symbol +; input: +; a = type (color) constant +LoadCardNameAndInputColor: ; 2c686 (b:4686) + add a + ld e, a + ld d, $00 + ld hl, ColorToTextSymbol + add hl, de + +; load wTxRam2 with card's name + ld de, wTxRam2 + ld a, [wLoadedCard1Name] + ld [de], a + inc de + ld a, [wLoadedCard1Name + 1] + ld [de], a + +; load wTxRam2_b with ColorToTextSymbol + inc de + ld a, [hli] + ld [de], a + inc de + ld a, [hli] + ld [de], a + ret + +ShiftMenuData: ; 2c6a1 (b:46a1) + ; x, y, text id + textitem 10, 9, TypeText + textitem 10, 10, WeaknessText + textitem 10, 11, ResistanceText + db $ff + +ColorTileAndBGP: ; 2c6ae (b:46ae) + ; tile, BG + db $e4, $02 + db $e0, $01 + db $eC, $02 + db $e8, $01 + db $f0, $03 + db $f4, $03 + +ShiftListItemToColor: ; 2c6ba (b:46ba) + db GRASS + db FIRE + db WATER + db LIGHTNING + db FIGHTING + db PSYCHIC + +ColorToTextSymbol: ; 2c6c0 (b:46c0) + tx FireSymbolText + tx GrassSymbolText + tx LightningSymbolText + tx WaterSymbolText + tx FightingSymbolText + tx PsychicSymbolText + +DrawSymbolOnPlayAreaCursor: ; 2c6cc (b:46cc) + ld c, a + add a + add c + add 2 + ; a = 3*a + 2 + ld c, a + ld a, b + ld b, 0 + call WriteByteToBGMap0 + ret + +; possibly unreferenced +Func_2c6d9: ; 2c6d9 (b:46d9) + ldtx hl, IncompleteText + call DrawWideTextBox_WaitForInput + ret + +PlayAreaSelectionMenuParameters: ; 2c6e0 (b:46e0) + db 0, 0 ; cursor x, cursor y + db 3 ; y displacement between items + db MAX_PLAY_AREA_POKEMON ; number of items + db SYM_CURSOR_R ; cursor tile number + db SYM_SPACE ; tile behind cursor + dw NULL ; function pointer if non-0 + +BenchSelectionMenuParameters: ; 2c6e8 (b:46e8) + db 0, 3 ; cursor x, cursor y + db 3 ; y displacement between items + db MAX_PLAY_AREA_POKEMON ; number of items + db SYM_CURSOR_R ; cursor tile number + db SYM_SPACE ; tile behind cursor + dw NULL ; function pointer if non-0 + +SpitPoison_AIEffect: ; 2c6f0 (b:46f0) + ld a, 10 / 2 + lb de, 0, 10 + jp SetExpectedAIDamage + +; If heads, defending Pokemon becomes poisoned +SpitPoison_Poison50PercentEffect: ; 2c6f8 (b:46f8) + ldtx de, PoisonCheckText + call TossCoin_BankB + jp c, PoisonEffect + ld a, ATK_ANIM_SPIT_POISON_SUCCESS + ld [wLoadedAttackAnimation], a + call SetNoEffectFromStatus + ret + +; outputs in hTemp_ffa0 the result of the coin toss (0 = tails, 1 = heads). +; in case it was heads, stores in hTempPlayAreaLocation_ffa1 +; the PLAY_AREA_* location of the Bench Pokemon that was selected for switch. +TerrorStrike_50PercentSelectSwitchPokemon: ; 2c70a (b:470a) + xor a + ldh [hTemp_ffa0], a + +; return failure if no Pokemon to switch to + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + ret c + +; toss coin and store whether it was tails (0) or heads (1) in hTemp_ffa0. +; return if it was tails. + ldtx de, IfHeadsChangeOpponentsActivePokemonText + call Func_2c08a + ldh [hTemp_ffa0], a + ret nc + + call DuelistSelectForcedSwitch + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempPlayAreaLocation_ffa1], a + ret + +; if coin toss at hTemp_ffa0 was heads and it's possible, +; switch the Defending Pokemon +TerrorStrike_SwitchDefendingPokemon: ; 2c726 (b:4726) + ldh a, [hTemp_ffa0] + or a + ret z + ldh a, [hTempPlayAreaLocation_ffa1] + call HandleSwitchDefendingPokemonEffect + ret + +PoisonFang_AIEffect: ; 2c730 (b:4730) + ld a, 10 + lb de, 10, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +WeepinbellPoisonPowder_AIEffect: ; 2c738 (b:4738) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +; return carry if there are no Pokemon cards in the non-turn holder's bench +VictreebelLure_AssertPokemonInBench: ; 2c740 (b:4740) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +; return in hTempPlayAreaLocation_ffa1 the PLAY_AREA_* location +; of the Bench Pokemon that was selected for switch +VictreebelLure_SelectSwitchPokemon: ; 2c74b (b:474b) + ldtx hl, SelectPkmnOnBenchToSwitchWithActiveText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench +.select_pokemon + bank1call OpenPlayAreaScreenForSelection + jr c, .select_pokemon + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call SwapTurn + ret + +; Return in hTemp_ffa0 the PLAY_AREA_* of the non-turn holder's Pokemon card in bench with the lowest (remaining) HP. +; if multiple cards are tied for the lowest HP, the one with the highest PLAY_AREA_* is returned. +VictreebelLure_GetBenchPokemonWithLowestHP: ; 2c764 (b:4764) + call GetBenchPokemonWithLowestHP + ldh [hTemp_ffa0], a + ret + +; Defending Pokemon is swapped out for the one with the PLAY_AREA_* at hTemp_ffa0 +; unless Mew's Neutralizing Shield or Haunter's Transparency prevents it. +VictreebelLure_SwitchDefendingPokemon: ; 2c76a (b:476a) + call SwapTurn + ldh a, [hTemp_ffa0] + ld e, a + call HandleNShieldAndTransparency + call nc, SwapArenaWithBenchPokemon + call SwapTurn + xor a + ld [wDuelDisplayedScreen], a + ret + +; If heads, defending Pokemon can't retreat next turn +AcidEffect: ; 2c77e (b:477e) + ldtx de, AcidCheckText + call TossCoin_BankB + ret nc + ld a, SUBSTATUS2_UNABLE_RETREAT + call ApplySubstatus2ToDefendingCard + ret + +GloomPoisonPowder_AIEffect: ; 2c78b (b:478b) + ld a, 10 + lb de, 10, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +; Defending Pokemon and user become confused +FoulOdorEffect: ; 2c793 (b:4793) + call ConfusionEffect + call SwapTurn + call ConfusionEffect + call SwapTurn + ret + +; If heads, prevent all damage done to user next turn +KakunaStiffenEffect: ; 2c7a0 (b:47a0) + ldtx de, IfHeadsNoDamageNextTurnText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_NO_DAMAGE_STIFFEN + call ApplySubstatus1ToDefendingCard + ret + +KakunaPoisonPowder_AIEffect: ; 2c7b4 (b:47b4) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +GolbatLeechLifeEffect: ; 2c7bc (b:47bc) + ld hl, wDealtDamage + ld e, [hl] + inc hl ; wDamageEffectiveness + ld d, [hl] + call ApplyAndAnimateHPRecovery + ret + +VenonatLeechLifeEffect: ; 2c7c6 (b:47c6) + ld hl, wDealtDamage + ld e, [hl] + inc hl ; wDamageEffectiveness + ld d, [hl] + call ApplyAndAnimateHPRecovery + ret + +; During your next turn, double damage +SwordsDanceEffect: ; 2c7d0 (b:47d0) + ld a, [wTempTurnDuelistCardID] + cp SCYTHER + ret nz + ld a, SUBSTATUS1_NEXT_TURN_DOUBLE_DAMAGE + call ApplySubstatus1ToDefendingCard + ret + +; If heads, defending Pokemon becomes confused +ZubatSupersonicEffect: ; 2c7dc (b:47dc) + call Confusion50PercentEffect + call nc, SetNoEffectFromStatus + ret + +ZubatLeechLifeEffect: ; 2c7e3 (b:47e3) + ld hl, wDealtDamage + ld e, [hl] + inc hl + ld d, [hl] + call ApplyAndAnimateHPRecovery + ret + +Twineedle_AIEffect: ; 2c7ed (b:47ed) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +; Flip 2 coins; deal 30x number of heads +Twineedle_MultiplierEffect: ; 2c7f5 (b:47f5) + ld hl, 30 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + ld e, a + add a + add e + call ATimes10 + call SetDefiniteDamage + ret + +BeedrillPoisonSting_AIEffect: ; 2c80d (b:480d) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +ExeggcuteLeechSeedEffect: ; 2c815 (b:4815) + ld hl, wDealtDamage + ld a, [hli] + or a + ret z ; return if no damage dealt + ld de, 10 + call ApplyAndAnimateHPRecovery + ret + +FoulGas_AIEffect: ; 2c822 (b:4822) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage + +; If heads, defending Pokemon becomes poisoned. If tails, defending Pokemon becomes confused +FoulGas_PoisonOrConfusionEffect: ; 2c82a (b:482a) + ldtx de, PoisonedIfHeadsConfusedIfTailsText + call TossCoin_BankB + jp c, PoisonEffect + jp ConfusionEffect + +; an exact copy of KakunaStiffenEffect +; If heads, prevent all damage done to user next turn +MetapodStiffenEffect: ; 2c836 (b:4836) + ldtx de, IfHeadsNoDamageNextTurnText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_NO_DAMAGE_STIFFEN + call ApplySubstatus1ToDefendingCard + ret + +; returns carry if no cards in Deck or if +; Play Area is full already. +Sprout_CheckDeckAndPlayArea: ; 2c84a (b:484a) + call CheckIfDeckIsEmpty + ret c ; return if no cards in deck + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +Sprout_PlayerSelectEffect: ; 2c85a (b:485a) + ld a, $ff + ldh [hTemp_ffa0], a + + call CreateDeckCardList + ldtx hl, ChooseAnOddishFromDeckText + ldtx bc, OddishText + lb de, SEARCHEFFECT_CARD_ID, ODDISH + call LookForCardsInDeck + ret c + +; draw Deck list interface and print text + bank1call Func_5591 + ldtx hl, ChooseAnOddishText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + +.loop + bank1call DisplayCardList + jr c, .pressed_b + call GetCardIDFromDeckIndex + ld bc, ODDISH + call CompareDEtoBC + jr nz, .play_sfx + +; Oddish was selected + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + or a + ret + +.play_sfx + ; play SFX and loop back + call Func_3794 + jr .loop + +.pressed_b +; figure if Player can exit the screen without selecting, +; that is, if the Deck has no Oddish card. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_b_press + ld a, [hl] + cp CARD_LOCATION_DECK + jr nz, .next + ld a, l + call GetCardIDFromDeckIndex + ld bc, ODDISH + call CompareDEtoBC + jr z, .play_sfx ; found Oddish, go back to top loop +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_b_press + +; no Oddish in Deck, can safely exit screen + ld a, $ff + ldh [hTemp_ffa0], a + or a + ret + +Sprout_AISelectEffect: ; 2c8b7 (b:48b7) + call CreateDeckCardList + ld hl, wDuelTempList +.loop_deck + ld a, [hli] + ldh [hTemp_ffa0], a + cp $ff + ret z ; no Oddish + call GetCardIDFromDeckIndex + ld a, e + cp ODDISH + jr nz, .loop_deck + ret ; Oddish found + +Sprout_PutInPlayAreaEffect: ; 2c8cc (b:48cc) + ldh a, [hTemp_ffa0] + cp $ff + jr z, .shuffle + call SearchCardInDeckAndAddToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + call IsPlayerTurn + jr c, .shuffle + ; display card on screen + ldh a, [hTemp_ffa0] + ldtx hl, PlacedOnTheBenchText + bank1call DisplayCardDetailScreen +.shuffle + call Func_2c0bd + ret + +; returns carry if no Pokemon on Bench +Teleport_CheckBench: ; 2c8ec (b:48ec) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, ThereAreNoPokemonOnBenchText + cp 2 + ret + +Teleport_PlayerSelectEffect: ; 2c8f7 (b:48f7) + ldtx hl, SelectPkmnOnBenchToSwitchWithActiveText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInBench + ld a, $01 + ld [wcbd4], a +.loop + bank1call OpenPlayAreaScreenForSelection + jr c, .loop + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret + +Teleport_AISelectEffect: ; 2c90f (b:490f) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + call Random + ldh [hTemp_ffa0], a + ret + +Teleport_SwitchEffect: ; 2c91a (b:491a) + ldh a, [hTemp_ffa0] + ld e, a + call SwapArenaWithBenchPokemon + xor a + ld [wDuelDisplayedScreen], a + ret + +BigEggsplosion_AIEffect: ; 2c925 (b:4925) + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + call SetDamageToATimes20 + inc h + jr nz, .capped + ld l, 255 +.capped + ld a, l + ld [wAIMaxDamage], a + srl a + ld [wDamage], a + xor a + ld [wAIMinDamage], a + ret + +; Flip coins equal to attached energies; deal 20x number of heads +BigEggsplosion_MultiplierEffect: ; 2c944 (b:4944) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld hl, 20 + call LoadTxRam3 + ld a, [wTotalAttachedEnergies] + ldtx de, DamageCheckIfHeadsXDamageText + call TossCoinATimes_BankB +; fallthrough + +; set damage to 20*a. Also return result in hl +SetDamageToATimes20: ; 2c958 (b:4958) + ld l, a + ld h, $00 + ld e, l + ld d, h + add hl, hl + add hl, hl + add hl, de + add hl, hl + add hl, hl + ld a, l + ld [wDamage], a + ld a, h + ld [wDamage + 1], a + ret + +Thrash_AIEffect: ; 2c96b (b:496b) + ld a, (30 + 40) / 2 + lb de, 30, 40 + jp SetExpectedAIDamage + +; If heads 10 more damage; if tails, 10 damage to itself +Thrash_ModifierEffect: ; 2c973 (b:4973) + ldtx de, IfHeadPlus10IfTails10ToYourselfText + call TossCoin_BankB + ldh [hTemp_ffa0], a + ret nc + ld a, 10 + call AddToDamage + ret + +Thrash_RecoilEffect: ; 2c982 (b:4982) + ldh a, [hTemp_ffa0] + or a + ret nz + ld a, 10 + call DealRecoilDamageToSelf + ret + +Toxic_AIEffect: ; 2c98c (b:498c) + ld a, 20 + lb de, 20, 20 + jp UpdateExpectedAIDamage + +; Defending Pokémon becomes double poisoned (takes 20 damage per turn rather than 10) +Toxic_DoublePoisonEffect: ; 2c994 (b:4994) + call DoublePoisonEffect + ret + +BoyfriendsEffect: ; 2c998 (b:4998) + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld c, PLAY_AREA_ARENA +.loop + ld a, [hl] + cp $ff + jr z, .done + call GetCardIDFromDeckIndex + ld a, e + cp NIDOKING + jr nz, .next + ld a, d + cp $00 ; why check d? Card IDs are only 1 byte long + jr nz, .next + inc c +.next + inc hl + jr .loop +.done +; c holds number of Nidoking found in Play Area + ld a, c + add a + call ATimes10 + call AddToDamage ; adds 2 * 10 * c + ret + +NidoranFFurySwipes_AIEffect: ; 2c9be (b:49be) + ld a, 30 / 2 + lb de, 0, 30 + jp SetExpectedAIDamage + +NidoranFFurySwipes_MultiplierEffect: ; 2c9c6 (b:49c6) + ld hl, 10 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 3 + call TossCoinATimes_BankB + call ATimes10 + call SetDefiniteDamage + ret + +NidoranFCallForFamily_CheckDeckAndPlayArea: ; 2c9db (b:49db) + call CheckIfDeckIsEmpty + ret c ; return if no cards in deck + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +NidoranFCallForFamily_PlayerSelectEffect: ; 2c9eb (b:49eb) + ld a, $ff + ldh [hTemp_ffa0], a + + call CreateDeckCardList + ldtx hl, ChooseNidoranFromDeckText + ldtx bc, NidoranMNidoranFText + lb de, SEARCHEFFECT_NIDORAN, $00 + call LookForCardsInDeck + ret c + +; draw Deck list interface and print text + bank1call Func_5591 + ldtx hl, ChooseNidoranText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + +.loop + bank1call DisplayCardList + jr c, .pressed_b + call GetCardIDFromDeckIndex + ld bc, NIDORANF + call CompareDEtoBC + jr z, .selected_nidoran + ld bc, NIDORANM + call CompareDEtoBC + jr nz, .loop ; .play_sfx would be more appropriate here + +.selected_nidoran + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + or a + ret + +.play_sfx + ; play SFX and loop back + call Func_3794 + jr .loop + +.pressed_b +; figure if Player can exit the screen without selecting, +; that is, if the Deck has no NidoranF or NidoranM card. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_b_press + ld a, [hl] + cp CARD_LOCATION_DECK + jr nz, .next + ld a, l + call GetCardIDFromDeckIndex + ld bc, NIDORANF + call CompareDEtoBC + jr z, .play_sfx ; found, go back to top loop + ld bc, NIDORANM + jr z, .play_sfx ; found, go back to top loop +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_b_press + +; no Nidoran in Deck, can safely exit screen + ld a, $ff + ldh [hTemp_ffa0], a + or a + ret + +NidoranFCallForFamily_AISelectEffect: ; 2ca55 (b:4a55) + call CreateDeckCardList + ld hl, wDuelTempList +.loop_deck + ld a, [hli] + ldh [hTemp_ffa0], a + cp $ff + ret z ; none found + call GetCardIDFromDeckIndex + ld a, e + cp NIDORANF + jr z, .found + cp NIDORANM + jr nz, .loop_deck +.found + ret + +NidoranFCallForFamily_PutInPlayAreaEffect: ; 2ca6e (b:4a6e) + ldh a, [hTemp_ffa0] + cp $ff + jr z, .shuffle + call SearchCardInDeckAndAddToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + call IsPlayerTurn + jr c, .shuffle + ; display card on screen + ldh a, [hTemp_ffa0] + ldtx hl, PlacedOnTheBenchText + bank1call DisplayCardDetailScreen +.shuffle + call Func_2c0bd + ret + +HornHazard_AIEffect: ; 2ca8e (b:4a8e) + ld a, 30 / 2 + lb de, 0, 30 + jp SetExpectedAIDamage + +HornHazard_NoDamage50PercentEffect: ; 2ca96 (b:4a96) + ldtx de, DamageCheckIfTailsNoDamageText + call TossCoin_BankB + jr c, .heads + xor a + call SetDefiniteDamage + call SetWasUnsuccessful + ret +.heads + ld a, ATK_ANIM_HIT + ld [wLoadedAttackAnimation], a + ret + +NidorinaSupersonicEffect: ; 2caac (b:4aac) + call Confusion50PercentEffect + call nc, SetNoEffectFromStatus + ret + +NidorinaDoubleKick_AIEffect: ; 2cab3 (b:4ab3) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +NidorinaDoubleKick_MultiplierEffect: ; 2cabb (b:4abb) + ld hl, 30 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + ld e, a + add a + add e + call ATimes10 + call SetDefiniteDamage + ret + +NidorinoDoubleKick_AIEffect: ; 2cad3 (b:4ad3) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +NidorinoDoubleKick_MultiplierEffect: ; 2cabb (b:4abb) + ld hl, 30 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + ld e, a + add a + add e + call ATimes10 + call SetDefiniteDamage + ret + +ButterfreeWhirlwind_CheckBench: ; 2caf3 (b:4af3) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + jr nc, .has_bench + ; no bench, do not do effect + ld a, $ff + ldh [hTemp_ffa0], a + ret +.has_bench + call DuelistSelectForcedSwitch + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret + +ButterfreeWhirlwind_SwitchEffect: ; 2cb09 (b:4b09) + ldh a, [hTemp_ffa0] + call HandleSwitchDefendingPokemonEffect + ret + +ButterfreeMegaDrainEffect: ; 2cb0f (b:4b0f) + ld hl, wDealtDamage + ld a, [hli] + ld h, [hl] + ld l, a + srl h + rr l + bit 0, l + jr z, .rounded + ; round up to nearest 10 + ld de, 10 / 2 + add hl, de +.rounded + ld e, l + ld d, h + call ApplyAndAnimateHPRecovery + ret + +WeedlePoisonSting_AIEffect: ; 2cb27 (b:4b27) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +IvysaurPoisonPowder_AIEffect: ; 2cb2f (b:4b2f) + ld a, 10 + lb de, 10, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +BulbasaurLeechSeedEffect: ; 2cb37 (b:4b37) + ld hl, wDealtDamage + ld a, [hli] + or [hl] + ret z ; return if no damage dealt + lb de, 0, 10 + call ApplyAndAnimateHPRecovery + ret + +; returns carry if no Grass Energy in Play Area +EnergyTrans_CheckPlayArea: ; 2cb44 (b:4b44) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret c ; cannot use Pkmn Power + +; search in Play Area for at least 1 Grass Energy type + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_deck + ld a, [hl] + and CARD_LOCATION_PLAY_AREA + jr z, .next + push hl + ld a, l + call GetCardIDFromDeckIndex + call GetCardType + pop hl + cp TYPE_ENERGY_GRASS + ret z +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_deck + +; none found + ldtx hl, NoGrassEnergyText + scf + ret + +EnergyTrans_PrintProcedure: ; 2cb6f (b:4b6f) + ldtx hl, ProcedureForEnergyTransferText + bank1call DrawWholeScreenTextBox + or a + ret + +EnergyTrans_TransferEffect: ; 2cb77 (b:4b77) + ld a, DUELVARS_DUELIST_TYPE + call GetTurnDuelistVariable + cp DUELIST_TYPE_PLAYER + jr z, .player +; not player + bank1call Func_61a1 + bank1call PrintPlayAreaCardList_EnableLCD + ret + +.player + xor a + ldh [hCurSelectionItem], a + bank1call Func_61a1 + +.draw_play_area + bank1call PrintPlayAreaCardList_EnableLCD + push af + ldh a, [hCurSelectionItem] + ld hl, PlayAreaSelectionMenuParameters + call InitializeMenuParameters + pop af + ld [wNumMenuItems], a + +; handle the action of taking a Grass Energy card +.loop_input_take + call DoFrame + call HandleMenuInput + jr nc, .loop_input_take + cp -1 ; b press? + ret z + +; a press + ldh [hAIPkmnPowerEffectParam], a + ldh [hCurSelectionItem], a + call CheckIfCardHasGrassEnergyAttached + jr c, .play_sfx ; no Grass attached + + ldh [hAIEnergyTransEnergyCard], a + ldh a, [hAIEnergyTransEnergyCard] ; useless + ; temporarily take card away to draw Play Area + call AddCardToHand + bank1call PrintPlayAreaCardList_EnableLCD + ldh a, [hAIPkmnPowerEffectParam] + ld e, a + ldh a, [hAIEnergyTransEnergyCard] + ; give card back + call PutHandCardInPlayArea + + ; draw Grass symbol near cursor + ldh a, [hAIPkmnPowerEffectParam] + ld b, SYM_GRASS + call DrawSymbolOnPlayAreaCursor + +; handle the action of placing a Grass Energy card +.loop_input_put + call DoFrame + call HandleMenuInput + jr nc, .loop_input_put + cp -1 ; b press? + jr z, .remove_symbol + +; a press + ldh [hCurSelectionItem], a + ldh [hAIEnergyTransPlayAreaLocation], a + ld a, OPPACTION_6B15 + call SetOppAction_SerialSendDuelData + ldh a, [hAIEnergyTransPlayAreaLocation] + ld e, a + ldh a, [hAIEnergyTransEnergyCard] + ; give card being held to this Pokemon + call AddCardToHand + call PutHandCardInPlayArea + +.remove_symbol + ldh a, [hAIPkmnPowerEffectParam] + ld b, SYM_SPACE + call DrawSymbolOnPlayAreaCursor + call EraseCursor + jr .draw_play_area + +.play_sfx + call Func_3794 + jr .loop_input_take + +EnergyTrans_AIEffect: ; 2cbfb (b:4bfb) + ldh a, [hAIEnergyTransPlayAreaLocation] + ld e, a + ldh a, [hAIEnergyTransEnergyCard] + call AddCardToHand + call PutHandCardInPlayArea + bank1call PrintPlayAreaCardList_EnableLCD + ret + +; returns carry if no Grass Energy cards +; attached to card in Play Area location of a. +; input: +; a = PLAY_AREA_* of location to check +CheckIfCardHasGrassEnergyAttached: ; 2cc0a (b:4c0a) + or CARD_LOCATION_PLAY_AREA + ld e, a + + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop + ld a, [hl] + cp e + jr nz, .next + push de + push hl + ld a, l + call GetCardIDFromDeckIndex + call GetCardType + pop hl + pop de + cp TYPE_ENERGY_GRASS + jr z, .no_carry +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop + scf + ret +.no_carry + ld a, l + or a + ret + +GrimerMinimizeEffect: ; 2cc30 (b:4c30) + ld a, SUBSTATUS1_REDUCE_BY_20 + call ApplySubstatus1ToDefendingCard + ret + +ToxicGasEffect: ; 2cc36 (b:4c36) + scf + ret + +Sludge_AIEffect: ; 2cc38 (b:4c38) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +; returns carry if no cards in Deck +; or if Play Area is full already. +BellsproutCallForFamily_CheckDeckAndPlayArea: ; 2cc40 (b:4c40) + call CheckIfDeckIsEmpty + ret c ; return if no cards in deck + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +BellsproutCallForFamily_PlayerSelectEffect: ; 2cc50 (b:4c50) + ld a, $ff + ldh [hTemp_ffa0], a + + call CreateDeckCardList + ldtx hl, ChooseABellsproutFromDeckText + ldtx bc, BellsproutText + lb de, SEARCHEFFECT_CARD_ID, BELLSPROUT + call LookForCardsInDeck + ret c + +; draw Deck list interface and print text + bank1call Func_5591 + ldtx hl, ChooseABellsproutText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + +.loop + bank1call DisplayCardList + jr c, .pressed_b + call GetCardIDFromDeckIndex + ld bc, BELLSPROUT + call CompareDEtoBC + jr nz, .play_sfx + +; Bellsprout was selected + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + or a + ret + +.play_sfx + ; play SFX and loop back + call Func_3794 + jr .loop + +.pressed_b +; figure if Player can exit the screen without selecting, +; that is, if the Deck has no Bellsprout card. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_b_press + ld a, [hl] + cp CARD_LOCATION_DECK + jr nz, .next + ld a, l + call GetCardIDFromDeckIndex + ld bc, BELLSPROUT + call CompareDEtoBC + jr z, .play_sfx ; found Bellsprout, go back to top loop +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_b_press + +; no Bellsprout in Deck, can safely exit screen + ld a, $ff + ldh [hTemp_ffa0], a + or a + ret + +BellsproutCallForFamily_AISelectEffect: ; 2ccad (b:4cad) + call CreateDeckCardList + ld hl, wDuelTempList +.loop_deck + ld a, [hli] + ldh [hTemp_ffa0], a + cp $ff + ret z ; no Bellsprout + call GetCardIDFromDeckIndex + ld a, e + cp BELLSPROUT + jr nz, .loop_deck + ret ; Bellsprout found + +BellsproutCallForFamily_PutInPlayAreaEffect: ; 2ccc2 (b:4cc2) + ldh a, [hTemp_ffa0] + cp $ff + jr z, .shuffle + call SearchCardInDeckAndAddToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + call IsPlayerTurn + jr c, .shuffle + ldh a, [hTemp_ffa0] + ldtx hl, PlacedOnTheBenchText + bank1call DisplayCardDetailScreen +.shuffle + call Func_2c0bd + ret + +WeezingSmog_AIEffect: ; 2cce2 (b:4ce2) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +WeezingSelfdestructEffect: ; 2ccea (b:4cea) + ld a, 60 + call DealRecoilDamageToSelf + ld a, $01 + ld [wIsDamageToSelf], a + ld a, 10 + call DealDamageToAllBenchedPokemon + call SwapTurn + xor a + ld [wIsDamageToSelf], a + ld a, 10 + call DealDamageToAllBenchedPokemon + call SwapTurn + ret + +Shift_OncePerTurnCheck: ; 2cd09 (b:4d09) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + and USED_PKMN_POWER_THIS_TURN + jr nz, .already_used + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret +.already_used + ldtx hl, OnlyOncePerTurnText + scf + ret + +Shift_PlayerSelectEffect: ; 2cd21 (b:4d21) + ldtx hl, ChoosePokemonWishToColorChangeText + ldh a, [hTemp_ffa0] + or $80 + call HandleColorChangeScreen + ldh [hAIPkmnPowerEffectParam], a + ret c ; cancelled + +; check whether the color selected is valid + ; look in Turn Duelist's Play Area + call .CheckColorInPlayArea + ret nc + ; look in NonTurn Duelist's Play Area + call SwapTurn + call .CheckColorInPlayArea + call SwapTurn + ret nc + ; not found in either Duelist's Play Area + ldtx hl, UnableToSelectText + call DrawWideTextBox_WaitForInput + jr Shift_PlayerSelectEffect ; loop back to start + +; checks in input color in a exists in Turn Duelist's Play Area +; returns carry if not found. +.CheckColorInPlayArea: ; 2cd44 (b:4d44) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld b, PLAY_AREA_ARENA +.loop_play_area + push bc + ld a, b + call GetPlayAreaCardColor + pop bc + ld hl, hAIPkmnPowerEffectParam + cp [hl] + ret z ; found + inc b + dec c + jr nz, .loop_play_area + ; not found + scf + ret + +Shift_ChangeColorEffect: ; 2cd5d (b:4d5d) + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + set USED_PKMN_POWER_THIS_TURN_F, [hl] + + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_CHANGED_TYPE + ld l, a + ldh a, [hAIPkmnPowerEffectParam] + or HAS_CHANGED_COLOR + ld [hl], a + call LoadCardNameAndInputColor + ldtx hl, ChangedTheColorOfText + call DrawWideTextBox_WaitForInput + ret + +VenomPowder_AIEffect: ; 2cd84 (b:4d84) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage + +VenomPowder_PoisonConfusion50PercentEffect: ; 2cd8c (b:4d8c) + ldtx de, VenomPowderCheckText + call TossCoin_BankB + ret nc ; return if tails + +; heads + call PoisonEffect + call ConfusionEffect + ret c + ld a, CONFUSED | POISONED + ld [wNoEffectFromWhichStatus], a + ret + +TangelaPoisonPowder_AIEffect: ; 2cda0 (b:4da0) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +Heal_OncePerTurnCheck: ; 2cda8 (b:4da8) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + and USED_PKMN_POWER_THIS_TURN + jr nz, .already_used + + call CheckIfPlayAreaHasAnyDamage + ldtx hl, NoPokemonWithDamageCountersText + ret c ; no damage counters to heal + + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret + +.already_used + ldtx hl, OnlyOncePerTurnText + scf + ret + +Heal_RemoveDamageEffect: ; 2cdc7 (b:4dc7) + ldtx de, IfHeadsHealIsSuccessfulText + call TossCoin_BankB + ldh [hAIPkmnPowerEffectParam], a + jr nc, .done + + ld a, DUELVARS_DUELIST_TYPE + call GetTurnDuelistVariable + cp DUELIST_TYPE_LINK_OPP + jr z, .link_opp + and DUELIST_TYPE_AI_OPP + jr nz, .done + +; player + ldtx hl, ChoosePkmnToRemoveDamageCounterText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInPlayArea +.loop_input + bank1call OpenPlayAreaScreenForSelection + jr c, .loop_input + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hPlayAreaEffectTarget], a + ld e, a + call GetCardDamageAndMaxHP + or a + jr z, .loop_input ; has no damage counters + ldh a, [hTempPlayAreaLocation_ff9d] + call SerialSend8Bytes + jr .done + +.link_opp + call SerialRecv8Bytes + ldh [hPlayAreaEffectTarget], a + ; fallthrough + +.done +; flag Pkmn Power as being used regardless of coin outcome + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + set USED_PKMN_POWER_THIS_TURN_F, [hl] + ldh a, [hAIPkmnPowerEffectParam] + or a + ret z ; return if coin was tails + + ldh a, [hPlayAreaEffectTarget] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + add 10 ; remove 1 damage counter + ld [hl], a + ldh a, [hPlayAreaEffectTarget] + call Func_2c10b + call ExchangeRNG + ret + +PetalDance_AIEffect: ; 2ce23 (b:4e23) + ld a, 120 / 2 + lb de, 0, 120 + jp SetExpectedAIDamage + +PetalDance_MultiplierEffect: ; 2ce2b (b:4e2b) + ld hl, 40 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 3 + call TossCoinATimes_BankB + add a + add a + call ATimes10 + ; a = 4 * 10 * heads + call SetDefiniteDamage + call SwapTurn + call ConfusionEffect + call SwapTurn + ret + +PoisonWhip_AIEffect: ; 2ce4b (b:4e4b) + ld a, 10 + lb de, 10, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +SolarPower_CheckUse: ; 2ce53 (b:4e53) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + and USED_PKMN_POWER_THIS_TURN + jr nz, .already_used + + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret c ; can't use PKMN due to status or Toxic Gas + +; return carry if none of the Arena cards have status conditions + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + or a + jr nz, .has_status + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + or a + jr z, .no_status +.has_status + or a + ret +.already_used + ldtx hl, OnlyOncePerTurnText + scf + ret +.no_status + ldtx hl, NotAffectedByPoisonSleepParalysisOrConfusionText + scf + ret + +SolarPower_RemoveStatusEffect: ; 2ce82 (b:4e82) + ld a, ATK_ANIM_HEAL_BOTH_SIDES + ld [wLoadedAttackAnimation], a + bank1call Func_7415 + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld c, $00 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation + + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + set USED_PKMN_POWER_THIS_TURN_F, [hl] + ld l, DUELVARS_ARENA_CARD_STATUS + ld [hl], NO_STATUS + + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + ld [hl], NO_STATUS + bank1call DrawDuelHUDs + ret + +VenusaurMegaDrainEffect: ; 2ceb0 (b:4eb0) + ld hl, wDealtDamage + ld a, [hli] + ld h, [hl] + ld l, a + srl h + rr l + bit 0, l + jr z, .rounded + ; round up to nearest 10 + ld de, 10 / 2 + add hl, de +.rounded + ld e, l + ld d, h + call ApplyAndAnimateHPRecovery + ret + +; applies the damage bonus for attacks that get bonus +; from extra Water energy cards. +; this bonus is always 10 more damage for each extra Water energy +; and is always capped at a maximum of 20 damage. +; input: +; b = number of Water energy cards needed for paying Energy Cost +; c = number of colorless energy cards needed for paying Energy Cost +ApplyExtraWaterEnergyDamageBonus: ; 2cec8 (b:4ec8) + ld a, [wMetronomeEnergyCost] + or a + jr z, .not_metronome + ld c, a ; amount of colorless needed for Metronome + ld b, 0 ; no Water energy needed for Metronome + +.not_metronome + push bc + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + pop bc + + ld hl, wAttachedEnergies + WATER + ld a, c + or a + jr z, .check_bonus ; is Energy cost all water energy? + + ; it's not, so we need to remove the + ; Water energy cards from calculations + ; if they pay for colorless instead. + ld a, [wTotalAttachedEnergies] + cp [hl] + jr nz, .check_bonus ; skip if at least 1 non-Water energy attached + + ; Water is the only energy color attached + ld a, c + add b + ld b, a + ; b += c + +.check_bonus + ld a, [hl] + sub b + jr c, .skip_bonus ; is water energy < b? + jr z, .skip_bonus ; is water energy == b? + +; a holds number of water energy not payed for energy cost + cp 3 + jr c, .less_than_3 + ld a, 2 ; cap this to 2 for bonus effect +.less_than_3 + call ATimes10 + call AddToDamage ; add 10 * a to damage + +.skip_bonus + ld a, [wDamage] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + ret + +OmastarWaterGunEffect: ; 2cf05 (b:4f05) + lb bc, 1, 1 + jr ApplyExtraWaterEnergyDamageBonus + +OmastarSpikeCannon_AIEffect: ; 2cf0a (b:4f0a) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +OmastarSpikeCannon_MultiplierEffect: ; 2cf12 (b:4f12) + ld hl, 30 + call LoadTxRam3 + ld a, 2 + ldtx de, DamageCheckIfHeadsXDamageText + call TossCoinATimes_BankB + ld e, a + add a + add e + call ATimes10 + call SetDefiniteDamage ; 3 * 10 * heads + ret + +ClairvoyanceEffect: ; 2cf2a (b:4f2a) + scf + ret + +OmanyteWaterGunEffect: ; 2cf2c (b:4f2c) + lb bc, 1, 0 + jp ApplyExtraWaterEnergyDamageBonus + +WartortleWithdrawEffect: ; 2cf32 (b:4f32) + ldtx de, IfHeadsNoDamageNextTurnText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_NO_DAMAGE_10 + call ApplySubstatus1ToDefendingCard + ret + +RainDanceEffect: ; 2cf46 (b:4f46) + scf + ret + +HydroPumpEffect: ; 2cf48 (b:4f48) + lb bc, 3, 0 + jp ApplyExtraWaterEnergyDamageBonus + +KinglerFlail_AIEffect: ; 2cf4e (b:4f4e) + call KinglerFlail_HPCheck + jp SetDefiniteAIDamage + +KinglerFlail_HPCheck: ; 2cf54 (b:4f54) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call SetDefiniteDamage + ret + +; returns carry if no cards in Deck +; or if Play Area is full already. +KrabbyCallForFamily_CheckDeckAndPlayArea: ; 2cf5d (b:4f5d) + call CheckIfDeckIsEmpty + ret c ; return if no cards in deck + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +KrabbyCallForFamily_PlayerSelectEffect: ; 2cf6d (b:4f6d) + ld a, $ff + ldh [hTemp_ffa0], a + + call CreateDeckCardList + ldtx hl, ChooseAKrabbyFromDeckText + ldtx bc, KrabbyText + lb de, SEARCHEFFECT_CARD_ID, KRABBY + call LookForCardsInDeck + ret c + +; draw Deck list interface and print text + bank1call Func_5591 + ldtx hl, ChooseAKrabbyText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + +.loop + bank1call DisplayCardList + jr c, .pressed_b + call GetCardIDFromDeckIndex + ld bc, KRABBY + call CompareDEtoBC + jr nz, .play_sfx + +; Krabby was selected + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + or a + ret + +.play_sfx + ; play SFX and loop back + call Func_3794 + jr .loop + +.pressed_b +; figure if Player can exit the screen without selecting, +; that is, if the Deck has no Krabby card. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_b_press + ld a, [hl] + cp CARD_LOCATION_DECK + jr nz, .next + ld a, l + call GetCardIDFromDeckIndex + ld bc, KRABBY + call CompareDEtoBC + jr z, .play_sfx ; found Krabby, go back to top loop +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_b_press + +; no Krabby in Deck, can safely exit screen + ld a, $ff + ldh [hTemp_ffa0], a + or a + ret + +KrabbyCallForFamily_AISelectEffect: ; 2cfdf (b:4fdf) + call CreateDeckCardList + ld hl, wDuelTempList +.loop_deck + ld a, [hli] + ldh [hTemp_ffa0], a + cp $ff + ret z ; no Krabby + call GetCardIDFromDeckIndex + ld a, e + cp KRABBY + jr nz, .loop_deck + ret ; Krabby found + +KrabbyCallForFamily_PutInPlayAreaEffect: ; 2cfca (b:4fca) + ldh a, [hTemp_ffa0] + cp $ff + jr z, .shuffle + call SearchCardInDeckAndAddToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + call IsPlayerTurn + jr c, .shuffle + ldh a, [hTemp_ffa0] + ldtx hl, PlacedOnTheBenchText + bank1call DisplayCardDetailScreen +.shuffle + call Func_2c0bd + ret + +MagikarpFlail_AIEffect: ; 2cfff (b:4fff) + call MagikarpFlail_HPCheck + jp SetDefiniteAIDamage + +MagikarpFlail_HPCheck: ; 2d005 (b:5005) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call SetDefiniteDamage + ret + +HeadacheEffect: ; 2d00e (b:500e) + ld a, DUELVARS_ARENA_CARD_SUBSTATUS3 + call GetNonTurnDuelistVariable + set SUBSTATUS3_HEADACHE, [hl] + ret + +PsyduckFurySwipes_AIEffect: ; 2d016 (b:5016) + ld a, 30 / 2 + lb de, 0, 30 + jp SetExpectedAIDamage + +PsyduckFurySwipes_MultiplierEffect: ; 2d01e (b:501e) + ld hl, 10 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 3 + call TossCoinATimes_BankB + call ATimes10 + call SetDefiniteDamage + ret + +GolduckHyperBeam_PlayerSelectEffect: ; 2d033 (b:5033) + call SwapTurn + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr z, .no_energy + +; draw Energy Card list screen + ldtx hl, ChooseDiscardEnergyCardFromOpponentText + call DrawWideTextBox_WaitForInput + xor a ; PLAY_AREA_ARENA + call CreateArenaOrBenchEnergyCardList + xor a ; PLAY_AREA_ARENA + bank1call DisplayEnergyDiscardScreen + +.loop_input + bank1call HandleEnergyDiscardMenuInput + jr c, .loop_input + + call SwapTurn + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a ; store selected card to discard + ret + +.no_energy + call SwapTurn + ld a, $ff + ldh [hTemp_ffa0], a + or a + ret + +GolduckHyperBeam_AISelectEffect: ; 2d065 (b:5065) + call AIPickEnergyCardToDiscardFromDefendingPokemon + ldh [hTemp_ffa0], a + ret + +GolduckHyperBeam_DiscardEffect: ; 2d06b (b:506b) + call HandleNoDamageOrEffect + ret c ; return if attack had no effect + + ; check if energy card was chosen to discard + ldh a, [hTemp_ffa0] + cp $ff + ret z ; return if none selected + + ; discard Defending card's energy + call SwapTurn + call PutCardInDiscardPile + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + ld [hl], LAST_TURN_EFFECT_DISCARD_ENERGY + call SwapTurn + ret + +SeadraWaterGunEffect: ; 2d085 (b:5085) + lb bc, 1, 1 + jp ApplyExtraWaterEnergyDamageBonus + +SeadraAgilityEffect: ; 2d08b (b:508b) + ldtx de, IfHeadsDoNotReceiveDamageOrEffectText + call TossCoin_BankB + ret nc ; return if tails + ld a, ATK_ANIM_AGILITY_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_AGILITY + call ApplySubstatus1ToDefendingCard + ret + +ShellderSupersonicEffect: ; 2d09d (b:509d) + call Confusion50PercentEffect + call nc, SetNoEffectFromStatus + ret + +HideInShellEffect: ; 2d0a4 (b:50a4) + ldtx de, IfHeadsNoDamageNextTurnText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_NO_DAMAGE_11 + call ApplySubstatus1ToDefendingCard + ret + +VaporeonQuickAttack_AIEffect: ; 2d0b8 (b:50b8) + ld a, (10 + 30) / 2 + lb de, 10, 30 + jp SetExpectedAIDamage + +VaporeonQuickAttack_DamageBoostEffect: ; 2d0c0 (b:50c0) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; return if tails + ld a, 20 + call AddToDamage + ret + +VaporeonWaterGunEffect: ; 2d0d3 (b:50d3) + lb bc, 2, 1 + jp ApplyExtraWaterEnergyDamageBonus + +; returns carry if Arena card has no Water Energy attached +; or if it doesn't have any damage counters. +StarmieRecover_CheckEnergyHP: ; 2d0d9 (b:50d9) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + WATER] + ldtx hl, NotEnoughWaterEnergyText + cp 1 + ret c ; return if not enough energy + call GetCardDamageAndMaxHP + ldtx hl, NoDamageCountersText + cp 10 + ret ; return carry if no damage + +StarmieRecover_PlayerSelectEffect: ; 2d0f0 (b:50f0) + ld a, TYPE_ENERGY_WATER + call CreateListOfEnergyAttachedToArena + xor a ; PLAY_AREA_ARENA + bank1call DisplayEnergyDiscardScreen +.loop_input + bank1call HandleEnergyDiscardMenuInput + jr c, .loop_input + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a ; store card chosen + ret + +StarmieRecover_AISelectEffect: ; 2d103 (b:5103) + ld a, TYPE_ENERGY_WATER + call CreateListOfEnergyAttachedToArena + ld a, [wDuelTempList] ; pick first card + ldh [hTemp_ffa0], a + ret + +StarmieRecover_DiscardEffect: ; 2d10e (b:510e) + ldh a, [hTemp_ffa0] + call PutCardInDiscardPile + ret + +StarmieRecover_HealEffect: ; 2d114 (b:5114) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ld e, a ; all damage for recovery + ld d, 0 + call ApplyAndAnimateHPRecovery + ret + +SquirtleWithdrawEffect: ; 2d120 (b:5120) + ldtx de, IfHeadsNoDamageNextTurnText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_NO_DAMAGE_10 + call ApplySubstatus1ToDefendingCard + ret + +HorseaSmokescreenEffect: ; 2d134 (b:5134) + ld a, SUBSTATUS2_SMOKESCREEN + call ApplySubstatus2ToDefendingCard + ret + +TentacruelSupersonicEffect: ; 2d13a (b:513a) + call Confusion50PercentEffect + call nc, SetNoEffectFromStatus + ret + +JellyfishSting_AIEffect: ; 2d141 (b:5141) + ld a, 10 + lb de, 10, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +; returns carry if Defending Pokemon has no attacks +PoliwhirlAmnesia_CheckAttacks: ; 2d149 (b:5149) + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Atk1Category] + cp POKEMON_POWER + jr nz, .has_attack + ld hl, wLoadedCard2Atk2Name + ld a, [hli] + or [hl] + jr nz, .has_attack +; has no attack + call SwapTurn + ldtx hl, NoAttackMayBeChoosenText + scf + ret +.has_attack + call SwapTurn + or a + ret + +PoliwhirlAmnesia_PlayerSelectEffect: ; 2d16f (b:516f) + call PlayerPickAttackForAmnesia + ret + +PoliwhirlAmnesia_AISelectEffect: ; 2d173 (b:5173) + call AIPickAttackForAmnesia + ldh [hTemp_ffa0], a + ret + +PoliwhirlAmnesia_DisableEffect: ; 2d179 (b:5179) + call ApplyAmnesiaToAttack + ret + +PlayerPickAttackForAmnesia: ; 2d17d (b:517d) + ldtx hl, ChooseAttackOpponentWillNotBeAbleToUseText + call DrawWideTextBox_WaitForInput + call HandleDefendingPokemonAttackSelection + ld a, e + ldh [hTemp_ffa0], a + ret + +; applies the Amnesia effect on the defending Pokemon, +; for the attack index in hTemp_ffa0. +ApplyAmnesiaToAttack: ; 2d18a (b:518a) + ld a, SUBSTATUS2_AMNESIA + call ApplySubstatus2ToDefendingCard + ld a, [wNoDamageOrEffect] + or a + ret nz ; no effect + +; set selected attack as disabled + ld a, DUELVARS_ARENA_CARD_DISABLED_ATTACK_INDEX + call GetNonTurnDuelistVariable + ldh a, [hTemp_ffa0] + ld [hl], a + + ld l, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + ld [hl], LAST_TURN_EFFECT_AMNESIA + + call IsPlayerTurn + ret c ; return if Player + +; the rest of the routine if for Opponent +; to announce which attack was used for Amnesia. + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + ldh a, [hTemp_ffa0] + ld e, a + call GetAttackName + call LoadTxRam2 + ldtx hl, WasChosenForTheEffectOfAmnesiaText + call DrawWideTextBox_WaitForInput + call SwapTurn + ret + +PoliwhirlDoubleslap_AIEffect: ; 2d1c0 (b:51c0) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +PoliwhirlDoubleslap_MultiplierEffect: ; 2d1c8 (b:51c8) + ld hl, 30 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + ld e, a + add a + add e + call ATimes10 + call SetDefiniteDamage + ret + +PoliwrathWaterGunEffect: ; 2d1e0 (b:51e0) + lb bc, 2, 1 + jp ApplyExtraWaterEnergyDamageBonus + +Whirlpool_PlayerSelectEffect: ; 2d1e6 (b:51e6) + call SwapTurn + xor a ; PLAY_AREA_ARENA + call CreateArenaOrBenchEnergyCardList + jr c, .no_energy + + ldtx hl, ChooseDiscardEnergyCardFromOpponentText + call DrawWideTextBox_WaitForInput + xor a ; PLAY_AREA_ARENA + bank1call DisplayEnergyDiscardScreen +.loop_input + bank1call HandleEnergyDiscardMenuInput + jr c, .loop_input + + call SwapTurn + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a ; store selected card to discard + ret + +.no_energy + call SwapTurn + ld a, $ff + ldh [hTemp_ffa0], a + ret + +Whirlpool_AISelectEffect: ; 2d20e (b:520e) + call AIPickEnergyCardToDiscardFromDefendingPokemon + ldh [hTemp_ffa0], a + ret + +Whirlpool_DiscardEffect: ; 2d214 (b:5214) + call HandleNoDamageOrEffect + ret c ; return if attack had no effect + ldh a, [hTemp_ffa0] + cp $ff + ret z ; return if none selected + + ; discard Defending card's energy + ; this doesn't update DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call SwapTurn + call PutCardInDiscardPile + ; ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + ; call GetTurnDuelistVariable + ; ld [hl], LAST_TURN_EFFECT_DISCARD_ENERGY + call SwapTurn + ret + +PoliwagWaterGunEffect: ; 2d227 (b:5227) + lb bc, 1, 0 + jp ApplyExtraWaterEnergyDamageBonus + +ClampEffect: ; 2d22d (b:522d) + ld a, ATK_ANIM_HIT_EFFECT + ld [wLoadedAttackAnimation], a + ldtx de, SuccessCheckIfHeadsAttackIsSuccessfulText + call TossCoin_BankB + jp c, ParalysisEffect +; unsuccessful + xor a ; ATK_ANIM_NONE + ld [wLoadedAttackAnimation], a + call SetDefiniteDamage + call SetWasUnsuccessful + ret + +CloysterSpikeCannon_AIEffect: ; 2d246 (b:5246) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +CloysterSpikeCannon_MultiplierEffect: ; 2d24e (b:524e) + ld hl, 30 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + ld e, a + add a + add e + call ATimes10 + call SetDefiniteDamage + ret + +Blizzard_BenchDamage50PercentEffect: ; 2d266 (b:5266) + ldtx de, DamageToOppBenchIfHeadsDamageToYoursIfTailsText + call TossCoin_BankB + ldh [hTemp_ffa0], a ; store coin result + ret + +Blizzard_BenchDamageEffect: ; 2d26f (b:526f) + ldh a, [hTemp_ffa0] + or a + jr nz, .opp_bench + +; own bench + ld a, $01 + ld [wIsDamageToSelf], a + ld a, 10 + call DealDamageToAllBenchedPokemon + ret + +.opp_bench + call SwapTurn + ld a, 10 + call DealDamageToAllBenchedPokemon + call SwapTurn + ret + +; return carry if can use Cowardice +Cowardice_Check: ; 2d28b (b:528b) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret c ; return if cannot use + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret c ; return if no bench + + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + ldtx hl, CannotBeUsedInTurnWhichWasPlayedText + and CAN_EVOLVE_THIS_TURN + scf + ret z ; return if was played this turn + + or a + ret + +Cowardice_PlayerSelectEffect: ; 2d2ae (b:52ae) + ldh a, [hTemp_ffa0] + or a + ret nz ; return if not Arena card + ldtx hl, SelectPokemonToPlaceInTheArenaText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInBench + bank1call OpenPlayAreaScreenForSelection + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hAIPkmnPowerEffectParam], a + ret + +Cowardice_RemoveFromPlayAreaEffect: ; 2d2c3 (b:52c3) + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + +; put card in Discard Pile temporarily, so that +; all cards attached are discarded as well. + push af + ldh a, [hTemp_ffa0] + ld e, a + call MovePlayAreaCardToDiscardPile + +; if card was in Arena, swap selected Bench +; Pokemon with Arena, otherwise skip. + ldh a, [hTemp_ffa0] + or a + jr nz, .skip_switch + ldh a, [hAIPkmnPowerEffectParam] + ld e, a + call SwapArenaWithBenchPokemon + +.skip_switch +; move card back to Hand from Discard Pile +; and adjust Play Area + pop af + call MoveDiscardPileCardToHand + call AddCardToHand + call ShiftAllPokemonToFirstPlayAreaSlots + + xor a + ld [wDuelDisplayedScreen], a + ret + +LaprasWaterGunEffect: ; 2d2eb (b:52eb) + lb bc, 1, 0 + jp ApplyExtraWaterEnergyDamageBonus + +Quickfreeze_InitialEffect: ; 2d2f1 (b:52f1) + scf + ret + +Quickfreeze_Paralysis50PercentEffect: ; 2d2f3 (b:52f3) + ldtx de, ParalysisCheckText + call TossCoin_BankB + jr c, .heads + +; tails + call SetWasUnsuccessful + bank1call DrawDuelMainScene + bank1call Func_1bca + call WaitForWideTextBoxInput + ret + +.heads + call ParalysisEffect + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld c, $00 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call Func_741a + bank1call WaitAttackAnimation + bank1call Func_6df1 + bank1call DrawDuelHUDs + bank1call Func_1bca + call c, WaitForWideTextBoxInput + ret + +IceBreath_ZeroDamage: ; 2d329 (b:5329) + xor a + call SetDefiniteDamage + ret + +IceBreath_RandomPokemonDamageEffect: ; 2d32e (b:532e) + call SwapTurn + call PickRandomPlayAreaCard + ld b, a + ld de, 40 + call DealDamageToPlayAreaPokemon_RegularAnim + call SwapTurn + ret + +FocusEnergyEffect: ; 2d33f (b:533f) + ld a, [wTempTurnDuelistCardID] + cp VAPOREON1 + ret nz ; return if no Vaporeon1 + ld a, SUBSTATUS1_NEXT_TURN_DOUBLE_DAMAGE + call ApplySubstatus1ToDefendingCard + ret + +PlayerPickFireEnergyCardToDiscard: ; 2d34b (b:534b) + call CreateListOfFireEnergyAttachedToArena + xor a + bank1call DisplayEnergyDiscardScreen + bank1call HandleEnergyDiscardMenuInput + ldh a, [hTempCardIndex_ff98] + ldh [hTempList], a + ret + +AIPickFireEnergyCardToDiscard: ; 2d35a (b:535a) + call CreateListOfFireEnergyAttachedToArena + ld a, [wDuelTempList] + ldh [hTempList], a ; pick first in list + ret + +; returns carry if Arena card has no Fire Energy cards +ArcanineFlamethrower_CheckEnergy: ; 2d363 (b:5363) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies] + ldtx hl, NotEnoughFireEnergyText + cp 1 + ret + +ArcanineFlamethrower_PlayerSelectEffect: ; 2d371 (b:5371) + call PlayerPickFireEnergyCardToDiscard + ret + +ArcanineFlamethrower_AISelectEffect: ; 2d375 (b:5375) + call AIPickFireEnergyCardToDiscard + ret + +ArcanineFlamethrower_DiscardEffect: ; 2d379 (b:5379) + ldh a, [hTempList] + call PutCardInDiscardPile + ret + +TakeDownEffect: ; 2d37f (b:537f) + ld a, 30 + call DealRecoilDamageToSelf + ret + +ArcanineQuickAttack_AIEffect: ; 2d385 (b:5385) + ld a, (10 + 30) / 2 + lb de, 10, 30 + jp SetExpectedAIDamage + +ArcanineQuickAttack_DamageBoostEffect: ; 2d38d (b:538d) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; return if tails + ld a, 20 + call AddToDamage + ret + +; return carry if has less than 2 Fire Energy cards +FlamesOfRage_CheckEnergy: ; 2d3a0 (b:53a0) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies] + ldtx hl, NotEnoughFireEnergyText + cp 2 + ret + +FlamesOfRage_PlayerSelectEffect: ; 2d3ae (b:53ae) + ldtx hl, ChooseAndDiscard2FireEnergyCardsText + call DrawWideTextBox_WaitForInput + + xor a + ldh [hCurSelectionItem], a + call CreateListOfFireEnergyAttachedToArena + xor a + bank1call DisplayEnergyDiscardScreen +.loop_input + bank1call HandleEnergyDiscardMenuInput + ret c + call GetNextPositionInTempList + ldh a, [hTempCardIndex_ff98] + ld [hl], a + call RemoveCardFromDuelTempList + ldh a, [hCurSelectionItem] + cp 2 + ret nc ; return when 2 have been chosen + bank1call DisplayEnergyDiscardMenu + jr .loop_input + +FlamesOfRage_AISelectEffect: ; 2d3d5 (b:53d5) + call AIPickFireEnergyCardToDiscard + ld a, [wDuelTempList + 1] + ldh [hTempList + 1], a + ret + +FlamesOfRage_DiscardEffect: ; 2d3de (b:53de) + ldh a, [hTempList] + call PutCardInDiscardPile + ldh a, [hTempList + 1] + call PutCardInDiscardPile + ret + +FlamesOfRage_AIEffect: ; 2d3e9 (b:53e9) + call FlamesOfRage_DamageBoostEffect + jp SetDefiniteAIDamage + +FlamesOfRage_DamageBoostEffect: ; 2d3ef (b:53ef) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call AddToDamage + ret + +RapidashStomp_AIEffect: ; 2d3f8 (b:53f8) + ld a, (20 + 30) / 2 + lb de, 20, 30 + jp SetExpectedAIDamage + +RapidashStomp_DamageBoostEffect: ; 2d400 (b:5400) + ld hl, 10 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; return if tails + ld a, 10 + call AddToDamage + ret + +RapidashAgilityEffect: ; 2d413 (b:5413) + ldtx de, IfHeadsDoNotReceiveDamageOrEffectText + call TossCoin_BankB + ret nc ; return if tails + ld a, ATK_ANIM_AGILITY_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_AGILITY + call ApplySubstatus1ToDefendingCard + ret + +; returns carry if Opponent has no Pokemon in bench +NinetalesLure_CheckBench: ; 2d425 (b:5425) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +NinetalesLure_PlayerSelectEffect: ; 2d430 (b:5430) + ldtx hl, SelectPkmnOnBenchToSwitchWithActiveText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench +.loop_input + bank1call OpenPlayAreaScreenForSelection + jr c, .loop_input + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call SwapTurn + ret + +NinetalesLure_AISelectEffect: ; 2d449 (b:5449) + call GetBenchPokemonWithLowestHP + ldh [hTemp_ffa0], a + ret + +NinetalesLure_SwitchEffect: ; 2d44f (b:544f) + call SwapTurn + ldh a, [hTemp_ffa0] + ld e, a + call HandleNShieldAndTransparency + call nc, SwapArenaWithBenchPokemon + call SwapTurn + xor a + ld [wDuelDisplayedScreen], a + ret + +; return carry if no Fire energy cards +FireBlast_CheckEnergy: ; 2d463 (b:5463) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ldtx hl, NotEnoughFireEnergyText + ld a, [wAttachedEnergies] + cp 1 + ret + +FireBlast_PlayerSelectEffect: ; 2d471 (b:5471) + call PlayerPickFireEnergyCardToDiscard + ret + +FireBlast_AISelectEffect: ; 2d475 (b:5475) + call AIPickFireEnergyCardToDiscard + ret + +FireBlast_DiscardEffect: ; 2d479 (b:5479) + ldh a, [hTempList] + call PutCardInDiscardPile + ret + +; return carry if no Fire energy cards +Ember_CheckEnergy: ; 2d47f (b:547f) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ldtx hl, NotEnoughFireEnergyText + ld a, [wAttachedEnergies] + cp 1 + ret + +Ember_PlayerSelectEffect: ; 2d48d (b:548d) + call PlayerPickFireEnergyCardToDiscard + ret + +Ember_AISelectEffect: ; 2d491 (b:5491) + call AIPickFireEnergyCardToDiscard + ret + +Ember_DiscardEffect: ; 2d495 (b:5495) + ldh a, [hTempList] + call PutCardInDiscardPile + ret + +; return carry if no Fire energy cards +Wildfire_CheckEnergy: ; 2d49b (b:549b) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ldtx hl, NotEnoughFireEnergyText + ld a, [wAttachedEnergies] + cp 1 + ret + +Wildfire_PlayerSelectEffect: ; 2d4a9 (b:54a9) + ldtx hl, DiscardOppDeckAsManyFireEnergyCardsText + call DrawWideTextBox_WaitForInput + + xor a + ldh [hCurSelectionItem], a + call CreateListOfFireEnergyAttachedToArena + xor a + bank1call DisplayEnergyDiscardScreen + +; show list to Player and for each card selected to discard, +; just increase a counter and store it. +; this will be the output used by Wildfire_DiscardEnergyEffect. + xor a + ld [wEnergyDiscardMenuDenominator], a +.loop + ldh a, [hCurSelectionItem] + ld [wEnergyDiscardMenuNumerator], a + bank1call HandleEnergyDiscardMenuInput + jr c, .done + ld hl, hCurSelectionItem + inc [hl] + call RemoveCardFromDuelTempList + jr c, .done + bank1call DisplayEnergyDiscardMenu + jr .loop + +.done +; return carry if no cards were discarded +; output the result in hTemp_ffa0 + ldh a, [hCurSelectionItem] + ldh [hTemp_ffa0], a + or a + ret nz + scf + ret + +Wildfire_AISelectEffect: ; 2d4dd (b:54dd) +; AI always chooses 0 cards to discard + xor a + ldh [hTempList], a + ret + +Wildfire_DiscardEnergyEffect: ; 2d4e1 (b:54e1) + call CreateListOfFireEnergyAttachedToArena + ldh a, [hTemp_ffa0] + or a + ret z ; no cards to discard + +; discard cards from wDuelTempList equal to the number +; of cards that were input in hTemp_ffa0. +; these are all the Fire Energy cards attached to Arena card +; so it will discard the cards in order, regardless +; of the actual order that was selected by Player. + ld c, a + ld hl, wDuelTempList +.loop_discard + ld a, [hli] + call PutCardInDiscardPile + dec c + jr nz, .loop_discard + ret + +Wildfire_DiscardDeckEffect: ; 2d4f4 (b:54f4) + ldh a, [hTemp_ffa0] + ld c, a + ld b, $00 + call SwapTurn + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ld a, DECK_SIZE + sub [hl] + cp c + jr nc, .start_discard + ; only discard number of cards that are left in deck + ld c, a + +.start_discard + push bc + inc c + jr .check_remaining + +.loop + ; discard top card from deck + call DrawCardFromDeck + call nc, PutCardInDiscardPile +.check_remaining + dec c + jr nz, .loop + + pop hl + call LoadTxRam3 + ldtx hl, DiscardedCardsFromDeckText + call DrawWideTextBox_PrintText + call SwapTurn + ret + +Moltres1DiveBomb_AIEffect: ; 2d523 (b:5523) + ld a, 80 / 2 + lb de, 0, 80 + jp SetExpectedAIDamage + +Moltres1DiveBomb_Success50PercentEffect: ; 2d52b (b:552b) + ldtx de, SuccessCheckIfHeadsAttackIsSuccessfulText + call TossCoin_BankB + jr c, .heads +; tails + xor a + call SetDefiniteDamage + call SetWasUnsuccessful + ret +.heads + ld a, ATK_ANIM_DIVE_BOMB + ld [wLoadedAttackAnimation], a + ret + +FlareonQuickAttack_AIEffect: ; 2d541 (b:5541) + ld a, (10 + 30) / 2 + lb de, 10, 30 + jp SetExpectedAIDamage + +FlareonQuickAttack_DamageBoostEffect: ; 2d549 (b:5549) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; return if tails + ld a, 20 + call AddToDamage + ret + +; return carry if no Fire Energy attached +FlareonFlamethrower_CheckEnergy: ; 2d55c (b:555c) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ldtx hl, NotEnoughFireEnergyText + ld a, [wAttachedEnergies] + cp 1 + ret + +FlareonFlamethrower_PlayerSelectEffect: ; 2d56a (b:556a) + call PlayerPickFireEnergyCardToDiscard + ret + +FlareonFlamethrower_AISelectEffect: ; 2d56e (b:556e) + call AIPickFireEnergyCardToDiscard + ret + +FlareonFlamethrower_DiscardEffect: ; 2d572 (b:5572) + ldh a, [hTempList] + call PutCardInDiscardPile + ret + +; return carry if no Fire Energy attached +MagmarFlamethrower_CheckEnergy: ; 2d578 (b:5578) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ldtx hl, NotEnoughFireEnergyText + ld a, [wAttachedEnergies] + cp 1 + ret + +MagmarFlamethrower_PlayerSelectEffect: ; 2d586 (b:5586) + call PlayerPickFireEnergyCardToDiscard + ret + +MagmarFlamethrower_AISelectEffect: ; 2d58a (b:558a) + call AIPickFireEnergyCardToDiscard + ret + +MagmarFlamethrower_DiscardEffect: ; 2d58e (b:558e) + ldh a, [hTempList] + call PutCardInDiscardPile + ret + +MagmarSmokescreenEffect: ; 2d594 (b:5594) + ld a, SUBSTATUS2_SMOKESCREEN + call ApplySubstatus2ToDefendingCard + ret + +MagmarSmog_AIEffect: ; 2d59a (b:559a) + ld a, 5 + lb de, 0, 10 + jp UpdateExpectedAIDamage_AccountForPoison + +; return carry if no Fire Energy attached +CharmeleonFlamethrower_CheckEnergy: ; 2d5a2 (b:55a2) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ldtx hl, NotEnoughFireEnergyText + ld a, [wAttachedEnergies] + cp 1 + ret + +CharmeleonFlamethrower_PlayerSelectEffect: ; 2d5b0 (b:55b0) + call PlayerPickFireEnergyCardToDiscard + ret + +CharmeleonFlamethrower_AISelectEffect: ; 2d5b4 (b:55b4) + call AIPickFireEnergyCardToDiscard + ret + +CharmeleonFlamethrower_DiscardEffect: ; 2d5b8 (b:55b8) + ldh a, [hTempList] + call PutCardInDiscardPile + ret + +EnergyBurnEffect: ; 2d5be (b:55be) + scf + ret + +; return carry if has less than 2 Fire Energy cards +FireSpin_CheckEnergy: ; 2d5c0 (b:55c0) + xor a ; PLAY_AREA_ARENA + call CreateArenaOrBenchEnergyCardList + call CountCardsInDuelTempList + ldtx hl, NotEnoughEnergyCardsText + cp 2 + ret + +FireSpin_PlayerSelectEffect: ; 2d5cd (b:55cd) + ldtx hl, ChooseAndDiscard2EnergyCardsText + call DrawWideTextBox_WaitForInput + + xor a + ldh [hCurSelectionItem], a + xor a + call CreateArenaOrBenchEnergyCardList + call SortCardsInDuelTempListByID + xor a + bank1call DisplayEnergyDiscardScreen + + ld a, 2 + ld [wEnergyDiscardMenuDenominator], a +.loop_input + bank1call HandleEnergyDiscardMenuInput + ret c + call GetNextPositionInTempList + ldh a, [hTempCardIndex_ff98] + ld [hl], a + ld hl, wEnergyDiscardMenuNumerator + inc [hl] + ldh a, [hCurSelectionItem] + cp 2 + jr nc, .done + ldh a, [hTempCardIndex_ff98] + call RemoveCardFromDuelTempList + bank1call DisplayEnergyDiscardMenu + jr .loop_input +.done +; return when 2 have been chosen + or a + ret + +FireSpin_AISelectEffect: ; 2d606 (b:5606) + xor a ; PLAY_AREA_ARENA + call CreateArenaOrBenchEnergyCardList + ld hl, wDuelTempList + ld a, [hli] + ldh [hTempList], a + ld a, [hl] + ldh [hTempList + 1], a + ret + +FireSpin_DiscardEffect: ; 2d614 (b:5614) + ld hl, hTempList + ld a, [hli] + call PutCardInDiscardPile + ld a, [hli] + call PutCardInDiscardPile + ret + +; returns carry if Pkmn Power cannot be used +; or if Arena card is not Charizard. +; this is unused. +EnergyBurnCheck_Unreferenced: ; 2d620 (b:5620) + xor a + bank1call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret c + ld a, DUELVARS_ARENA_CARD + push de + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + pop de + cp CHARIZARD + jr nz, .not_charizard + or a + ret +.not_charizard + scf + ret + +FlareonRage_AIEffect: ; 2d638 (b:5638) + call FlareonRage_DamageBoostEffect + jp SetDefiniteAIDamage + +FlareonRage_DamageBoostEffect: ; 2d63e (b:563e) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call AddToDamage + ret + +MixUpEffect: ; 2d647 (b:5647) + call SwapTurn + call CreateHandCardList + call SortCardsInDuelTempListByID + +; first go through Hand to place +; all Pkmn cards in it in the Deck. + ld hl, wDuelTempList + ld c, 0 +.loop_hand + ld a, [hl] + cp $ff + jr z, .done_hand + call .CheckIfCardIsPkmnCard + jr nc, .next_hand + ; found Pkmn card, place in deck + inc c + ld a, [hl] + call RemoveCardFromHand + call ReturnCardToDeck +.next_hand + inc hl + jr .loop_hand + +.done_hand + ld a, c + ldh [hCurSelectionItem], a + push bc + ldtx hl, ThePkmnCardsInHandAndDeckWereShuffledText + call DrawWideTextBox_WaitForInput + + call Func_2c0bd + call CreateDeckCardList + pop bc + ldh a, [hCurSelectionItem] + or a + jr z, .done ; if no cards were removed from Hand, return + +; c holds the number of cards that were placed in the Deck. +; now pick Pkmn cards from the Deck to place in Hand. + ld hl, wDuelTempList +.loop_deck + ld a, [hl] + call .CheckIfCardIsPkmnCard + jr nc, .next_deck + dec c + ld a, [hl] + call SearchCardInDeckAndAddToHand + call AddCardToHand +.next_deck + inc hl + ld a, c + or a + jr nz, .loop_deck +.done + call SwapTurn + ret + +; returns carry if card index in a is Pkmn card +.CheckIfCardIsPkmnCard: ; 2d69a (b:569a) + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + ret + +DancingEmbers_AIEffect: ; 2d6a3 (b:56a3) + ld a, 80 / 2 + lb de, 0, 80 + jp SetExpectedAIDamage + +DancingEmbers_MultiplierEffect: ; 2d6ab (b:56ab) + ld hl, 10 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 8 + call TossCoinATimes_BankB + call ATimes10 + call SetDefiniteDamage + ret + +Firegiver_InitialEffect: ; 2d6c0 (b:56c0) + scf + ret + +Firegiver_AddToHandEffect: ; 2d6c2 (b:56c2) +; fill wDuelTempList with all Fire Energy card +; deck indices that are in the Deck. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable + ld de, wDuelTempList + ld c, 0 +.loop_cards + ld a, [hl] + cp CARD_LOCATION_DECK + jr nz, .next + push hl + push de + ld a, l + call GetCardIDFromDeckIndex + call GetCardType + pop de + pop hl + cp TYPE_ENERGY_FIRE + jr nz, .next + ld a, l + ld [de], a + inc de + inc c +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_cards + ld a, $ff + ld [de], a + +; check how many were found + ld a, c + or a + jr nz, .found + ; return if none found + ldtx hl, ThereWasNoFireEnergyText + call DrawWideTextBox_WaitForInput + call Func_2c0bd + ret + +.found +; pick a random number between 1 and 4, +; up to the maximum number of Fire Energy +; cards that were found. + ld a, 4 + call Random + inc a + cp c + jr c, .ok + ld a, c + +.ok + ldh [hCurSelectionItem], a +; load correct attack animation depending +; on what side the effect is from. + ld d, ATK_ANIM_FIREGIVER_PLAYER + ld a, [wDuelistType] + cp DUELIST_TYPE_PLAYER + jr z, .player_1 +; opponent + ld d, ATK_ANIM_FIREGIVER_OPP +.player_1 + ld a, d + ld [wLoadedAttackAnimation], a + +; start loop for adding Energy cards to hand + ldh a, [hCurSelectionItem] + ld c, a + ld hl, wDuelTempList +.loop_energy + push hl + push bc + ld bc, $0 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation + +; load correct coordinates to update the number of cards +; in hand and deck during animation. + lb bc, 18, 7 ; x, y for hand number + ld e, 3 ; y for deck number + ld a, [wLoadedAttackAnimation] + cp ATK_ANIM_FIREGIVER_PLAYER + jr z, .player_2 + lb bc, 4, 5 ; x, y for hand number + ld e, 10 ; y for deck number + +.player_2 +; update and print number of cards in hand + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + inc a + bank1call WriteTwoDigitNumberInTxSymbolFormat +; update and print number of cards in deck + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ld a, DECK_SIZE - 1 + sub [hl] + ld c, e + bank1call WriteTwoDigitNumberInTxSymbolFormat + +; load Fire Energy card index and add to hand + pop bc + pop hl + ld a, [hli] + call SearchCardInDeckAndAddToHand + call AddCardToHand + dec c + jr nz, .loop_energy + +; load the number of cards added to hand and print text + ldh a, [hCurSelectionItem] + ld l, a + ld h, $00 + call LoadTxRam3 + ldtx hl, DrewFireEnergyFromTheHandText + call DrawWideTextBox_WaitForInput + call Func_2c0bd + ret + +Moltres2DiveBomb_AIEffect: ; 2d76e (b:576e) + ld a, 70 / 2 + lb de, 0, 70 + jp SetExpectedAIDamage + +Moltres2DiveBomb_Success50PercentEffect: ; 2d776 (b:5776) + ldtx de, SuccessCheckIfHeadsAttackIsSuccessfulText + call TossCoin_BankB + jr c, .heads +; tails + xor a + call SetDefiniteDamage + call SetWasUnsuccessful + ret +.heads + ld a, ATK_ANIM_DIVE_BOMB + ld [wLoadedAttackAnimation], a + ret + +; output in de the number of energy cards +; attached to the Defending Pokemon times 10. +; used for attacks that deal 10x number of energy +; cards attached to the Defending card. +GetEnergyAttachedMultiplierDamage: ; 2d78c (b:578c) + call SwapTurn + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable + + ld c, 0 +.loop + ld a, [hl] + cp CARD_LOCATION_ARENA + jr nz, .next + ; is in Arena + ld a, l + call GetCardIDFromDeckIndex + call GetCardType + and TYPE_ENERGY + jr z, .next + ; is Energy attached to Arena card + inc c +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop + + call SwapTurn + ld l, c + ld h, $00 + ld b, $00 + add hl, hl ; hl = 2 * c + add hl, hl ; hl = 4 * c + add hl, bc ; hl = 5 * c + add hl, hl ; hl = 10 * c + ld e, l + ld d, h + ret + +; draws list of Energy Cards in Discard Pile +; for Player to select from. +; the Player can select up to 2 cards from the list. +; these cards are given in $ff-terminated list +; in hTempList. +HandleEnergyCardsInDiscardPileSelection: ; 2d7bc (b:57bc) + push hl + xor a + ldh [hCurSelectionItem], a + call CreateEnergyCardListFromDiscardPile_AllEnergy + pop hl + jr c, .finish + + call DrawWideTextBox_WaitForInput +.loop +; draws Discard Pile screen and textbox, +; and handles Player input + bank1call InitAndDrawCardListScreenLayout + ldtx hl, ChooseAnEnergyCardText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText + bank1call DisplayCardList + jr nc, .selected + +; Player is trying to exit screen, +; but can select up to 2 cards total. +; prompt Player to confirm exiting screen. + ld a, 2 + call AskWhetherToQuitSelectingCards + jr c, .loop + jr .finish + +.selected +; a card was selected, so add it to list + call GetNextPositionInTempList + ldh a, [hTempCardIndex_ff98] + ld [hl], a + call RemoveCardFromDuelTempList + or a + jr z, .finish ; no more cards? + ldh a, [hCurSelectionItem] + cp 2 + jr c, .loop ; already selected 2 cards? + +.finish +; place terminating byte on list + call GetNextPositionInTempList + ld [hl], $ff + or a + ret + +; returns carry if Pkmn Power cannot be used, and +; sets the correct text in hl for failure. +Curse_CheckDamageAndBench: ; 2d7fc (b:57fc) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + +; fail if Pkmn Power has already been used + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + ldtx hl, OnlyOncePerTurnText + and USED_PKMN_POWER_THIS_TURN + jr nz, .set_carry + +; fail if Opponent only has 1 Pokemon in Play Area + call SwapTurn + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + call SwapTurn + ldtx hl, CannotUseSinceTheresOnly1PkmnText + cp 2 + jr c, .set_carry + +; fail if Opponent has no damage counters + call SwapTurn + call CheckIfPlayAreaHasAnyDamage + call SwapTurn + ldtx hl, NoPokemonWithDamageCountersText + jr c, .set_carry + +; return carry if Pkmn Power cannot be used due +; to Toxic Gas or status. + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret + +.set_carry + scf + ret + +Curse_PlayerSelectEffect: ; 2d834 (b:5834) + ldtx hl, ProcedureForCurseText + bank1call DrawWholeScreenTextBox + call SwapTurn + xor a + ldh [hCurSelectionItem], a + bank1call Func_61a1 +.start + bank1call PrintPlayAreaCardList_EnableLCD + push af + ldh a, [hCurSelectionItem] + ld hl, PlayAreaSelectionMenuParameters + call InitializeMenuParameters + pop af + ld [wNumMenuItems], a + +; first pick a target to take 1 damage counter from. +.loop_input_first + call DoFrame + call HandleMenuInput + jr nc, .loop_input_first + cp $ff + jr z, .cancel + ldh [hCurSelectionItem], a + ldh [hTempPlayAreaLocation_ffa1], a + call GetCardDamageAndMaxHP + or a + jr nz, .picked_first ; test if has damage + ; play sfx + call Func_3794 + jr .loop_input_first + +.picked_first +; give 10 HP to card selected, draw the scene, +; then immediately revert this. + ldh a, [hTempPlayAreaLocation_ffa1] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + push af + push hl + add 10 + ld [hl], a + bank1call PrintPlayAreaCardList_EnableLCD + pop hl + pop af + ld [hl], a + +; draw damage counter on cursor + ldh a, [hTempPlayAreaLocation_ffa1] + ld b, SYM_HP_NOK + call DrawSymbolOnPlayAreaCursor + +; handle input to pick the target to receive the damage counter. +.loop_input_second + call DoFrame + call HandleMenuInput + jr nc, .loop_input_second + ldh [hPlayAreaEffectTarget], a + cp $ff + jr nz, .a_press ; was a pressed? + +; b press +; erase the damage counter symbol +; and loop back up again. + ldh a, [hTempPlayAreaLocation_ffa1] + ld b, SYM_SPACE + call DrawSymbolOnPlayAreaCursor + call EraseCursor + jr .start + +.a_press + ld hl, hTempPlayAreaLocation_ffa1 + cp [hl] + jr z, .loop_input_second ; same as first? +; a different Pokemon was picked, +; so store this Play Area location +; and erase the damage counter in the cursor. + ldh a, [hTempPlayAreaLocation_ffa1] + ld b, SYM_SPACE + call DrawSymbolOnPlayAreaCursor + call EraseCursor + call SwapTurn + or a + ret + +.cancel +; return carry if operation was cancelled. + call SwapTurn + scf + ret + +Curse_TransferDamageEffect: ; 2d8bb (b:58bb) +; set Pkmn Power as used + ldh a, [hTempList] + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + set USED_PKMN_POWER_THIS_TURN_F, [hl] + +; figure out the type of duelist that used Curse. +; if it was the player, no need to draw the Play Area screen. + call SwapTurn + ld a, DUELVARS_DUELIST_TYPE + call GetNonTurnDuelistVariable + cp DUELIST_TYPE_PLAYER + jr z, .vs_player + +; vs. opponent + bank1call Func_61a1 +.vs_player +; transfer the damage counter to the targets that were selected. + ldh a, [hPlayAreaEffectTarget] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + sub 10 + ld [hl], a + ldh a, [hTempPlayAreaLocation_ffa1] + add DUELVARS_ARENA_CARD_HP + ld l, a + ld a, 10 + add [hl] + ld [hl], a + + bank1call PrintPlayAreaCardList_EnableLCD + ld a, DUELVARS_DUELIST_TYPE + call GetNonTurnDuelistVariable + cp DUELIST_TYPE_PLAYER + jr z, .done +; vs. opponent + ldh a, [hPlayAreaEffectTarget] + ldh [hTempPlayAreaLocation_ff9d], a + bank1call Func_6194 + +.done + call SwapTurn + call ExchangeRNG + bank1call Func_6e49 + ret + +GengarDarkMind_PlayerSelectEffect: ; 2d903 (b:5903) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + jr nc, .has_bench +; no bench Pokemon to damage. + ld a, $ff + ldh [hTemp_ffa0], a + ret + +.has_bench +; opens Play Area screen to select Bench Pokemon +; to damage, and store it before returning. + ldtx hl, ChoosePkmnInTheBenchToGiveDamageText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench +.loop_input + bank1call OpenPlayAreaScreenForSelection + jr c, .loop_input + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call SwapTurn + ret + +GengarDarkMind_AISelectEffect: ; 2d92a (b:592a) + ld a, $ff + ldh [hTemp_ffa0], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + ret c ; return if no Bench Pokemon +; just pick Pokemon with lowest remaining HP. + call GetBenchPokemonWithLowestHP + ldh [hTemp_ffa0], a + ret + +GengarDarkMind_DamageBenchEffect: ; 2d93c (b:593c) + ldh a, [hTemp_ffa0] + cp $ff + ret z ; no target chosen + call SwapTurn + ld b, a + ld de, 10 + call DealDamageToPlayAreaPokemon_RegularAnim + call SwapTurn + ret + +SleepingGasEffect: ; 2d94f (b:594f) + call Sleep50PercentEffect + call nc, SetNoEffectFromStatus + ret + +DestinyBond_CheckEnergy: ; 2d956 (b:5956) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + PSYCHIC] + ldtx hl, NotEnoughPsychicEnergyText + cp 1 + ret + +DestinyBond_PlayerSelectEffect: ; 2d964 (b:5964) +; handle input and display of Energy card list + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + xor a + bank1call DisplayEnergyDiscardScreen + bank1call HandleEnergyDiscardMenuInput + ret c + ldh a, [hTempCardIndex_ff98] + ldh [hTempList], a + ret + +DestinyBond_AISelectEffect: ; 2d976 (b:5976) +; pick first card in list + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + ld a, [wDuelTempList] + ldh [hTempList], a + ret + +DestinyBond_DiscardEffect: ; 2d981 (b:5981) + ldh a, [hTempList] + call PutCardInDiscardPile + ret + +DestinyBond_DestinyBondEffect: ; 2d987 (b:5987) + ld a, SUBSTATUS1_DESTINY_BOND + call ApplySubstatus1ToDefendingCard + ret + +; returns carry if no Energy cards in Discard Pile. +EnergyConversion_CheckEnergy: ; 2d98d (b:598d) + call CreateEnergyCardListFromDiscardPile_AllEnergy + ldtx hl, ThereAreNoEnergyCardsInDiscardPileText + ret + +EnergyConversion_PlayerSelectEffect: ; 2d994 (b:5994) + ldtx hl, Choose2EnergyCardsFromDiscardPileForHandText + call HandleEnergyCardsInDiscardPileSelection + ret + +EnergyConversion_AISelectEffect: ; 2d99b (b:599b) + call CreateEnergyCardListFromDiscardPile_AllEnergy + ld hl, wDuelTempList + ld de, hTempList + ld c, 2 +; select the first two energy cards found in Discard Pile +.loop + ld a, [hli] + cp $ff + jr z, .done + ld [de], a + inc de + dec c + jr nz, .loop +.done + ld a, $ff + ld [de], a + ret + +EnergyConversion_AddToHandEffect: ; 2d9b4 (b:59b4) +; damage itself + ld a, 10 + call DealRecoilDamageToSelf + +; loop cards that were chosen +; until $ff is reached, +; and move them to the hand. + ld hl, hTempList + ld de, wDuelTempList +.loop_cards + ld a, [hli] + ld [de], a + inc de + cp $ff + jr z, .done + call MoveDiscardPileCardToHand + call AddCardToHand + jr .loop_cards + +.done + call IsPlayerTurn + ret c + bank1call Func_4b38 + ret + +; return carry if Defending Pokemon is not asleep +DreamEaterEffect: ; 2d9d6 (b:59d6) + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + and CNF_SLP_PRZ + cp ASLEEP + ret z ; return if asleep +; not asleep, set carry and load text + ldtx hl, OpponentIsNotAsleepText + scf + ret + +TransparencyEffect: ; 2d9e5 (b:59e5) + scf + ret + +; returns carry if neither the Turn Duelist or +; the non-Turn Duelist have any deck cards. +Prophecy_CheckDeck: ; 2d9e7 (b:59e7) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE + jr c, .no_carry + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetNonTurnDuelistVariable + cp DECK_SIZE + jr c, .no_carry + ldtx hl, NoCardsLeftInTheDeckText + scf + ret +.no_carry + or a + ret + +Prophecy_PlayerSelectEffect: ; 2da00 (b:5a00) + ldtx hl, ProcedureForProphecyText + bank1call DrawWholeScreenTextBox +.select_deck + bank1call DrawDuelMainScene + ldtx hl, PleaseSelectTheDeckText + call TwoItemHorizontalMenu + ldh a, [hKeysHeld] + and B_BUTTON + jr nz, Prophecy_PlayerSelectEffect ; loop back to start + + ldh a, [hCurMenuItem] + ldh [hTempList], a ; store selection in first position in list + or a + jr z, .turn_duelist + +; non-turn duelist + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetNonTurnDuelistVariable + cp DECK_SIZE + jr nc, .select_deck ; no cards, go back to deck selection + call SwapTurn + call HandleProphecyScreen + call SwapTurn + ret + +.turn_duelist + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE + jr nc, .select_deck ; no cards, go back to deck selection + call HandleProphecyScreen + ret + +Prophecy_AISelectEffect: ; 2da3c (b:5a3c) +; AI doesn't ever choose this attack +; so this it does no sorting. + ld a, $ff + ldh [hTemp_ffa0], a + ret + +Prophecy_ReorderDeckEffect: ; 2da41 (b:5a41) + ld hl, hTempList + ld a, [hli] + or a + jr z, .ReorderCards ; turn duelist's deck + cp $ff + ret z + + ; non-turn duelist's deck + call SwapTurn + call .ReorderCards + call SwapTurn + ret + +.ReorderCards + ld c, 0 +; add selected cards to hand in the specified order +.loop_add_hand + ld a, [hli] + cp $ff + jr z, .dec_hl + call SearchCardInDeckAndAddToHand + inc c + jr .loop_add_hand + +.dec_hl +; go to last card that was in the list + dec hl + dec hl + +.loop_return_deck +; return the cards to the top of the deck + ld a, [hld] + call ReturnCardToDeck + dec c + jr nz, .loop_return_deck + call IsPlayerTurn + ret c + ; print text in case it was the opponent + ldtx hl, ExchangedCardsInDuelistsHandText + call DrawWideTextBox_WaitForInput + ret + +; draw and handle Player selection for reordering +; the top 3 cards of Deck. +; the resulting list is output in order in hTempList. +HandleProphecyScreen: ; 2da76 (b:5a76) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ld b, a + ld a, DECK_SIZE + sub [hl] ; a = number of cards in deck + +; store in c the number of cards that will be reordered. +; this number is 3, unless the deck as fewer cards than +; that in which case it will be the number of cards remaining. + ld c, 3 + cp c + jr nc, .got_number_cards + ld c, a ; store number of remaining cards in c +.got_number_cards + ld a, c + inc a + ld [wNumberOfCardsToOrder], a + +; store in wDuelTempList the cards +; at top of Deck to be reordered. + ld a, b + add DUELVARS_DECK_CARDS + ld l, a + ld de, wDuelTempList +.loop_top_cards + ld a, [hli] + ld [de], a + inc de + dec c + jr nz, .loop_top_cards + ld a, $ff ; terminating byte + ld [de], a + +.start + call CountCardsInDuelTempList + ld b, a + ld a, 1 ; start at 1 + ldh [hCurSelectionItem], a + +; initialize buffer ahead in wDuelTempList. + ld hl, wDuelTempList + 10 + xor a +.loop_init_buffer + ld [hli], a + dec b + jr nz, .loop_init_buffer + ld [hl], $ff + + bank1call InitAndDrawCardListScreenLayout + ldtx hl, ChooseTheOrderOfTheCardsText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + bank1call Func_5735 + +.loop_selection + bank1call DisplayCardList + jr c, .clear + +; first check if this card was already selected + ldh a, [hCurMenuItem] + ld e, a + ld d, $00 + ld hl, wDuelTempList + 10 + add hl, de + ld a, [hl] + or a + jr nz, .loop_selection ; already chosen + +; being here means card hasn't been selected yet, +; so add its order number to buffer and increment +; the sort number for the next card. + ldh a, [hCurSelectionItem] + ld [hl], a + inc a + ldh [hCurSelectionItem], a + bank1call Func_5744 + ldh a, [hCurSelectionItem] + ld hl, wNumberOfCardsToOrder + cp [hl] + jr c, .loop_selection ; still more cards + +; confirm that the ordering has been completed + call EraseCursor + ldtx hl, IsThisOKText + call YesOrNoMenuWithText_LeftAligned + jr c, .start ; if not, return back to beginning of selection + +; write in hTempList the card list +; in order that was selected. + ld hl, wDuelTempList + 10 + ld de, wDuelTempList + ld c, 0 +.loop_order + ld a, [hli] + cp $ff + jr z, .done + push hl + push bc + ld c, a + ld b, $00 + ld hl, hTempList + add hl, bc + ld a, [de] + ld [hl], a + pop bc + pop hl + inc de + inc c + jr .loop_order +; now hTempList has the list of card deck indices +; in the order selected to be place on top of the deck. + +.done + ld b, $00 + ld hl, hTempList + 1 + add hl, bc + ld [hl], $ff ; terminating byte + or a + ret + +.clear +; check if any reordering was done. + ld hl, hCurSelectionItem + ld a, [hl] + cp 1 + jr z, .loop_selection ; none done, go back +; clear the order that was selected thus far. + dec a + ld [hl], a + ld c, a + ld hl, wDuelTempList + 10 +.loop_clear + ld a, [hli] + cp c + jr nz, .loop_clear + ; clear this byte + dec hl + ld [hl], $00 + bank1call Func_5744 + jr .loop_selection + +HypnoDarkMind_PlayerSelectEffect: ; 2db2b (b:5b2b) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + jr nc, .has_bench +; no bench Pokemon to damage. + ld a, $ff + ldh [hTemp_ffa0], a + ret + +.has_bench +; opens Play Area screen to select Bench Pokemon +; to damage, and store it before returning. + ldtx hl, ChoosePkmnInTheBenchToGiveDamageText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench +.loop_input + bank1call OpenPlayAreaScreenForSelection + jr c, .loop_input + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call SwapTurn + ret + +HypnoDarkMind_AISelectEffect: ; 2db52 (b:5b52) + ld a, $ff + ldh [hTemp_ffa0], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + ret c ; return if no Bench Pokemon +; just pick Pokemon with lowest remaining HP. + call GetBenchPokemonWithLowestHP + ldh [hTemp_ffa0], a + ret + +HypnoDarkMind_DamageBenchEffect: ; 2db64 (b:5b64) + ldh a, [hTemp_ffa0] + cp $ff + ret z ; no target chosen + call SwapTurn + ld b, a + ld de, 10 + call DealDamageToPlayAreaPokemon_RegularAnim + call SwapTurn + ret + +InvisibleWallEffect: ; 2db77 (b:5b77) + scf + ret + +MrMimeMeditate_AIEffect: ; 2db79 (b:5b79) + call MrMimeMeditate_DamageBoostEffect + jp SetDefiniteAIDamage + +MrMimeMeditate_DamageBoostEffect: ; 2db7f (b:5b7f) +; add damage counters of Defending card to damage + call SwapTurn + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call SwapTurn + call AddToDamage + ret + +; returns carry if Damage Swap cannot be used. +DamageSwap_CheckDamage: ; 2db8e (b:5b8e) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call CheckIfPlayAreaHasAnyDamage + jr c, .no_damage + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret +.no_damage + ldtx hl, NoPokemonWithDamageCountersText + scf + ret + +DamageSwap_SelectAndSwapEffect: ; 2dba2 (b:5ba2) + ld a, DUELVARS_DUELIST_TYPE + call GetTurnDuelistVariable + cp DUELIST_TYPE_PLAYER + jr z, .player +; non-player + bank1call Func_61a1 + bank1call PrintPlayAreaCardList_EnableLCD + ret + +.player + ldtx hl, ProcedureForDamageSwapText + bank1call DrawWholeScreenTextBox + xor a + ldh [hCurSelectionItem], a + bank1call Func_61a1 + +.start + bank1call PrintPlayAreaCardList_EnableLCD + push af + ldh a, [hCurSelectionItem] + ld hl, PlayAreaSelectionMenuParameters + call InitializeMenuParameters + pop af + ld [wNumMenuItems], a + +; handle selection of Pokemon to take damage from +.loop_input_first + call DoFrame + call HandleMenuInput + jr nc, .loop_input_first + cp $ff + ret z ; quit when B button is pressed + + ldh [hTempPlayAreaLocation_ffa1], a + ldh [hCurSelectionItem], a + +; if card has no damage, play sfx and return to start + call GetCardDamageAndMaxHP + or a + jr z, .no_damage + +; take damage away temporarily to draw UI. + ldh a, [hTempPlayAreaLocation_ffa1] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + push af + push hl + add 10 + ld [hl], a + bank1call PrintPlayAreaCardList_EnableLCD + pop hl + pop af + ld [hl], a + +; draw damage counter in cursor + ldh a, [hTempPlayAreaLocation_ffa1] + ld b, SYM_HP_NOK + call DrawSymbolOnPlayAreaCursor + +; handle selection of Pokemon to give damage to +.loop_input_second + call DoFrame + call HandleMenuInput + jr nc, .loop_input_second + ; if B is pressed, return damage counter + ; to card that it was taken from + cp $ff + jr z, .update_ui + +; try to give the card selected the damage counter +; if it would KO, ignore it. + ldh [hPlayAreaEffectTarget], a + ldh [hCurSelectionItem], a + call TryGiveDamageCounter_DamageSwap + jr c, .loop_input_second + + ld a, OPPACTION_6B15 + call SetOppAction_SerialSendDuelData + +.update_ui + ldh a, [hTempPlayAreaLocation_ffa1] + ld b, SYM_SPACE + call DrawSymbolOnPlayAreaCursor + call EraseCursor + jr .start + +.no_damage + call Func_3794 + jr .loop_input_first + +; tries to give damage counter to hPlayAreaEffectTarget, +; and if successful updates UI screen. +DamageSwap_SwapEffect: ; 2dc27 (b:5c27) + call TryGiveDamageCounter_DamageSwap + ret c + bank1call PrintPlayAreaCardList_EnableLCD + or a + ret + +; tries to give the damage counter to the target +; chosen by the Player (hPlayAreaEffectTarget). +; if the damage counter would KO card, then do +; not give the damage counter and return carry. +TryGiveDamageCounter_DamageSwap: ; 2dc30 (b:5c30) + ldh a, [hPlayAreaEffectTarget] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + sub 10 + jr z, .set_carry ; would bring HP to zero? +; has enough HP to receive a damage counter + ld [hl], a + ldh a, [hTempPlayAreaLocation_ffa1] + add DUELVARS_ARENA_CARD_HP + ld l, a + ld a, 10 + add [hl] + ld [hl], a + or a + ret +.set_carry + scf + ret + +PsywaveEffect: ; 2dc49 (b:5c49) + call GetEnergyAttachedMultiplierDamage + ld hl, wDamage + ld [hl], e + inc hl + ld [hl], d + ret + +; returns carry if neither Duelist has evolved Pokemon. +DevolutionBeam_CheckPlayArea: ; 2dc53 (b:5c53) + call CheckIfTurnDuelistHasEvolvedCards + ret nc + call SwapTurn + call CheckIfTurnDuelistHasEvolvedCards + call SwapTurn + ldtx hl, ThereAreNoStage1PokemonText + ret + +; returns carry of Player cancelled selection. +; otherwise, output in hTemp_ffa0 which Play Area +; was selected ($0 = own Play Area, $1 = opp. Play Area) +; and in hTempPlayAreaLocation_ffa1 selected card. +DevolutionBeam_PlayerSelectEffect: ; 2dc64 (b:5c64) + ldtx hl, ProcedureForDevolutionBeamText + bank1call DrawWholeScreenTextBox + +.start + bank1call DrawDuelMainScene + ldtx hl, PleaseSelectThePlayAreaText + call TwoItemHorizontalMenu + ldh a, [hKeysHeld] + and B_BUTTON + jr nz, .set_carry + +; a Play Area was selected + ldh a, [hCurMenuItem] + or a + jr nz, .opp_chosen + +; player chosen + call HandleEvolvedCardSelection + jr c, .start + + xor a +.store_selection + ld hl, hTemp_ffa0 + ld [hli], a ; store which Duelist Play Area selected + ldh a, [hTempPlayAreaLocation_ff9d] + ld [hl], a ; store which card selected + or a + ret + +.opp_chosen + call SwapTurn + call HandleEvolvedCardSelection + call SwapTurn + jr c, .start + ld a, $01 + jr .store_selection + +.set_carry + scf + ret + +DevolutionBeam_AISelectEffect: ; 2dc9e (b:5c9e) + ld a, $01 + ldh [hTemp_ffa0], a + call SwapTurn + call FindFirstNonBasicCardInPlayArea + call SwapTurn + jr c, .found + xor a + ldh [hTemp_ffa0], a + call FindFirstNonBasicCardInPlayArea +.found + ldh [hTempPlayAreaLocation_ffa1], a + ret + +DevolutionBeam_LoadAnimation: ; 2dcb6 (b:5cb6) + xor a ; ATK_ANIM_NONE + ld [wLoadedAttackAnimation], a + ret + +DevolutionBeam_DevolveEffect: ; 2dcbb (b:5cbb) + ldh a, [hTemp_ffa0] + or a + jr z, .DevolvePokemon + cp $ff + ret z + +; opponent's Play Area + call SwapTurn + ldh a, [hTempPlayAreaLocation_ffa1] + jr nz, .skip_handle_no_damage_effect + call HandleNoDamageOrEffect + jr c, .unaffected +.skip_handle_no_damage_effect + call .DevolvePokemon +.unaffected + call SwapTurn + ret + +.DevolvePokemon + ld a, ATK_ANIM_DEVOLUTION_BEAM + ld [wLoadedAttackAnimation], a + ldh a, [hTempPlayAreaLocation_ffa1] + ld b, a + ld c, $00 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation + +; load selected card's data + ldh a, [hTempPlayAreaLocation_ffa1] + ldh [hTempPlayAreaLocation_ff9d], a + ld [wTempPlayAreaLocation_cceb], a + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + +; check if car is affected + ld a, [wLoadedCard1ID] + ld [wTempNonTurnDuelistCardID], a + ld de, $0 + ldh a, [hTempPlayAreaLocation_ff9d] + or a + jr nz, .skip_substatus_check + call HandleNoDamageOrEffectSubstatus + jr c, .check_no_damage_effect +.skip_substatus_check + call HandleDamageReductionOrNoDamageFromPkmnPowerEffects +.check_no_damage_effect + call CheckNoDamageOrEffect + jr nc, .devolve + call DrawWideTextBox_WaitForInput + ret + +.devolve + ldh a, [hTempPlayAreaLocation_ffa1] + ldh [hTempPlayAreaLocation_ff9d], a + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + bank1call GetCardOneStageBelow + call PrintDevolvedCardNameAndLevelText + + ld a, d + call UpdateDevolvedCardHPAndStage + call ResetDevolvedCardStatus + +; add the evolved card to the hand + ld a, e + call AddCardToHand + +; check if this devolution KO's card + ldh a, [hTempPlayAreaLocation_ffa1] + call PrintPlayAreaCardKnockedOutIfNoHP + + xor a + ld [wDuelDisplayedScreen], a + ret + +; returns carry if Turn Duelist +; has no Stage1 or Stage2 cards in Play Area. +CheckIfTurnDuelistHasEvolvedCards: ; 2dd3b (b:5d3b) + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, h + ld e, DUELVARS_ARENA_CARD_STAGE +.loop + ld a, [hli] + cp $ff + jr z, .set_carry + ld a, [de] + inc de + or a + jr z, .loop ; is Basic Stage + ret +.set_carry + scf + ret + +; handles Player selection of an evolved card in Play Area. +; returns carry if Player cancelled operation. +HandleEvolvedCardSelection: ; 2dd50 (b:5d50) + bank1call HasAlivePokemonInPlayArea +.loop + bank1call OpenPlayAreaScreenForSelection + ret c + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_STAGE + call GetTurnDuelistVariable + or a + jr z, .loop ; if Basic, loop + ret + +; finds first occurrence in Play Area +; of Stage 1 or 2 card, and outputs its +; Play Area location in a, with carry set. +; if none found, don't return carry set. +FindFirstNonBasicCardInPlayArea: ; 2dd62 (b:5d62) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + + ld b, PLAY_AREA_ARENA + ld l, DUELVARS_ARENA_CARD_STAGE +.loop + ld a, [hli] + or a + jr nz, .not_basic + inc b + dec c + jr nz, .loop + or a + ret +.not_basic + ld a, b + scf + ret + +NeutralizingShieldEffect: ; 2dd79 (b:5d79) + scf + ret + +Psychic_AIEffect: ; 2dd7b (b:5d7b) + call Psychic_DamageBoostEffect + jp SetDefiniteAIDamage + +Psychic_DamageBoostEffect: ; 2dd81 (b:5d81) + call GetEnergyAttachedMultiplierDamage + ld hl, wDamage + ld a, e + add [hl] + ld [hli], a + ld a, d + adc [hl] + ld [hl], a + ret + +; return carry if no Psychic Energy attached +Barrier_CheckEnergy: ; 2dd8e (b:5d8e) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + PSYCHIC] + ldtx hl, NotEnoughPsychicEnergyText + cp 1 + ret + +Barrier_PlayerSelectEffect: ; 2dd9c (b:5d9c) + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + xor a ; PLAY_AREA_ARENA + bank1call DisplayEnergyDiscardScreen + bank1call HandleEnergyDiscardMenuInput + ret c + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + ret + +Barrier_AISelectEffect: ; 2ddae (b:5dae) +; AI picks the first energy in list + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + ld a, [wDuelTempList] + ldh [hTemp_ffa0], a + ret + +Barrier_DiscardEffect: ; 2ddb9 (b:5db9) + ldh a, [hTemp_ffa0] + call PutCardInDiscardPile + ret + +Barrier_BarrierEffect: ; 2ddbf (b:5dbf) + ld a, SUBSTATUS1_BARRIER + call ApplySubstatus1ToDefendingCard + ret + +Mewtwo3EnergyAbsorption_CheckDiscardPile: ; 2ddc5 (b:5dc5) + call CreateEnergyCardListFromDiscardPile_AllEnergy + ldtx hl, ThereAreNoEnergyCardsInDiscardPileText + ret + +Mewtwo3EnergyAbsorption_PlayerSelectEffect: ; 2ddcc (b:5dcc) + ldtx hl, Choose2EnergyCardsFromDiscardPileToAttachText + call HandleEnergyCardsInDiscardPileSelection + ret + +Mewtwo3EnergyAbsorption_AISelectEffect: ; 2ddd3 (b:5dd3) +; AI picks first 2 energy cards + call CreateEnergyCardListFromDiscardPile_AllEnergy + ld hl, wDuelTempList + ld de, hTempList + ld c, 2 +.loop + ld a, [hli] + cp $ff + jr z, .done + ld [de], a + inc de + dec c + jr nz, .loop +.done + ld a, $ff ; terminating byte + ld [de], a + ret + +Mewtwo3EnergyAbsorption_AddToHandEffect: ; 2ddec (b:5dec) + ld hl, hTempList +.loop + ld a, [hli] + cp $ff + ret z + push hl + call MoveDiscardPileCardToHand + call GetTurnDuelistVariable + ld [hl], CARD_LOCATION_ARENA + pop hl + jr .loop + +Mewtwo2EnergyAbsorption_CheckDiscardPile: ; 2ddff (b:5dff) + call CreateEnergyCardListFromDiscardPile_AllEnergy + ldtx hl, ThereAreNoEnergyCardsInDiscardPileText + ret + +Mewtwo2EnergyAbsorption_PlayerSelectEffect: ; 2de06 (b:5e06) + ldtx hl, Choose2EnergyCardsFromDiscardPileToAttachText + call HandleEnergyCardsInDiscardPileSelection + ret + +Mewtwo2EnergyAbsorption_AISelectEffect: ; 2de0d (b:5e0d) +; AI picks first 2 energy cards + call CreateEnergyCardListFromDiscardPile_AllEnergy + ld hl, wDuelTempList + ld de, hTempList + ld c, 2 +.loop + ld a, [hli] + cp $ff + jr z, .done + ld [de], a + inc de + dec c + jr nz, .loop +.done + ld a, $ff ; terminating byte + ld [de], a + ret + +Mewtwo2EnergyAbsorption_AddToHandEffect: ; 2de26 (b:5e26) + ld hl, hTempList +.loop + ld a, [hli] + cp $ff + ret z + push hl + call MoveDiscardPileCardToHand + call GetTurnDuelistVariable + ld [hl], CARD_LOCATION_ARENA + pop hl + jr .loop + +; returns carry if Strange Behavior cannot be used. +StrangeBehavior_CheckDamage: ; 2de39 (b:5e39) +; does Play Area have any damage counters? + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call CheckIfPlayAreaHasAnyDamage + ldtx hl, NoPokemonWithDamageCountersText + jr c, .set_carry +; can Slowbro receive any damage counters without KO-ing? + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ldtx hl, CannotUseBecauseItWillBeKnockedOutText + cp 10 + 10 + jr c, .set_carry +; can Pkmn Power be used? + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret + +.set_carry + scf + ret + +StrangeBehavior_SelectAndSwapEffect: ; 2de5b (b:5e5b) + ld a, DUELVARS_DUELIST_TYPE + call GetTurnDuelistVariable + cp DUELIST_TYPE_PLAYER + jr z, .player + +; not player + bank1call Func_61a1 + bank1call PrintPlayAreaCardList_EnableLCD + ret + +.player + ldtx hl, ProcedureForStrangeBehaviorText + bank1call DrawWholeScreenTextBox + + xor a + ldh [hCurSelectionItem], a + bank1call Func_61a1 +.start + bank1call PrintPlayAreaCardList_EnableLCD + push af + ldh a, [hCurSelectionItem] + ld hl, PlayAreaSelectionMenuParameters + call InitializeMenuParameters + pop af + + ld [wNumMenuItems], a +.loop_input + call DoFrame + call HandleMenuInput + jr nc, .loop_input + cp -1 + ret z ; return when B button is pressed + + ldh [hCurSelectionItem], a + ldh [hTempPlayAreaLocation_ffa1], a + ld hl, hTemp_ffa0 + cp [hl] + jr z, .play_sfx ; can't select Slowbro itself + + call GetCardDamageAndMaxHP + or a + jr z, .play_sfx ; can't select card without damage + + call TryGiveDamageCounter_StrangeBehavior + jr c, .play_sfx + ld a, OPPACTION_6B15 + call SetOppAction_SerialSendDuelData + jr .start + +.play_sfx + call Func_3794 + jr .loop_input + +StrangeBehavior_SwapEffect: ; 2deb3 (b:5eb3) + call TryGiveDamageCounter_StrangeBehavior + ret c + bank1call PrintPlayAreaCardList_EnableLCD + or a + ret + +; tries to give the damage counter to the target +; chosen by the Player (hTemp_ffa0). +; if the damage counter would KO card, then do +; not give the damage counter and return carry. +TryGiveDamageCounter_StrangeBehavior: ; 2debc (b:5ebc) + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + sub 10 + jr z, .set_carry ; would bring HP to zero? +; has enough HP to receive a damage counter + ld [hl], a + ldh a, [hTempPlayAreaLocation_ffa1] + add DUELVARS_ARENA_CARD_HP + ld l, a + ld a, 10 + add [hl] + ld [hl], a + or a + ret +.set_carry + scf + ret + +; returns carry if has no damage counters. +SpacingOut_CheckDamage: ; 2ded5 (b:5ed5) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ldtx hl, NoDamageCountersText + cp 10 + ret + +SpacingOut_Success50PercentEffect: ; 2dee0 (b:5ee0) + ldtx de, SuccessCheckIfHeadsAttackIsSuccessfulText + call TossCoin_BankB + ldh [hTemp_ffa0], a + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_RECOVER + ld [wLoadedAttackAnimation], a + ret + +SpacingOut_HealEffect: ; 2def1 (b:5ef1) + ldh a, [hTemp_ffa0] + or a + ret z ; coin toss was tails + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + or a + ret z ; no damage counters + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + add 10 + ld [hl], a + ret + +; sets carry if no Trainer cards in the Discard Pile. +Scavenge_CheckDiscardPile: ; 2df05 (b:5f05) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + PSYCHIC] + ldtx hl, NotEnoughPsychicEnergyText + cp 1 + ret c ; return if no Psychic energy attached + call CreateTrainerCardListFromDiscardPile + ldtx hl, ThereAreNoTrainerCardsInDiscardPileText ; this is redundant + ret + +Scavenge_PlayerSelectEnergyEffect: ; 2df1a (b:5f1a) + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + xor a ; PLAY_AREA_ARENA + bank1call DisplayEnergyDiscardScreen + bank1call HandleEnergyDiscardMenuInput + ret c + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + or a + ret + +Scavenge_AISelectEffect: ; 2df2d (b:5f2d) +; AI picks first Energy card in list + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + ld a, [wDuelTempList] + ldh [hTemp_ffa0], a +; AI picks first Trainer card in list + call CreateTrainerCardListFromDiscardPile + ld a, [wDuelTempList] + ldh [hTempPlayAreaLocation_ffa1], a + ret + +Scavenge_DiscardEffect: ; 2df40 (b:5f40) + ldh a, [hTemp_ffa0] + call PutCardInDiscardPile + ret + +Scavenge_PlayerSelectTrainerEffect: ; 2df46 (b:5f46) + call CreateTrainerCardListFromDiscardPile + bank1call Func_5591 + ldtx hl, PleaseSelectCardText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText +.loop_input + bank1call DisplayCardList + jr c, .loop_input + ldh a, [hTempCardIndex_ff98] + ldh [hTempPlayAreaLocation_ffa1], a + ret + +Scavenge_AddToHandEffect: ; 2df5f (b:5f5f) + ldh a, [hTempPlayAreaLocation_ffa1] + call MoveDiscardPileCardToHand + call AddCardToHand + call IsPlayerTurn + ret c + ldh a, [hTempPlayAreaLocation_ffa1] + ldtx hl, WasPlacedInTheHandText + bank1call DisplayCardDetailScreen + ret + +; returns carry if Defending Pokemon has no attacks +SlowpokeAmnesia_CheckAttacks: ; 2df74 (b:5f74) + call CheckIfDefendingPokemonHasAnyAttack + ldtx hl, NoAttackMayBeChoosenText + ret + +SlowpokeAmnesia_PlayerSelectEffect: ; 2df7b (b:5f7b) + call PlayerPickAttackForAmnesia + ret + +SlowpokeAmnesia_AISelectEffect: ; 2df7f (b:5f7f) + call AIPickAttackForAmnesia + ldh [hTemp_ffa0], a + ret + +SlowpokeAmnesia_DisableEffect: ; 2df85 (b:5f85) + call ApplyAmnesiaToAttack + ret + +; returns carry if Arena card has no Psychic Energy attached +; or if it doesn't have any damage counters. +KadabraRecover_CheckEnergyHP: ; 2df89 (b:5f89) + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wAttachedEnergies + PSYCHIC] + ldtx hl, NotEnoughPsychicEnergyText + cp 1 + ret c ; return if not enough energy + call GetCardDamageAndMaxHP + ldtx hl, NoDamageCountersText + cp 10 + ret ; return carry if no damage + +KadabraRecover_PlayerSelectEffect: ; 2dfa0 (b:5fa0) + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + xor a ; PLAY_AREA_ARENA + bank1call DisplayEnergyDiscardScreen + bank1call HandleEnergyDiscardMenuInput + ret c + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a ; store card chosen + ret + +KadabraRecover_AISelectEffect: ; 2dfb2 (b:5fb2) + ld a, TYPE_ENERGY_PSYCHIC + call CreateListOfEnergyAttachedToArena + ld a, [wDuelTempList] ; pick first card + ldh [hTemp_ffa0], a + ret + +KadabraRecover_DiscardEffect: ; 2dfbd (b:5fbd) + ldh a, [hTemp_ffa0] + call PutCardInDiscardPile + ret + +KadabraRecover_HealEffect: ; 2dfc3 (b:5fc3) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ld e, a ; all damage for recovery + ld d, 0 + call ApplyAndAnimateHPRecovery + ret + +JynxDoubleslap_AIEffect: ; 2dfd7 (b:5fd7) + ld a, 20 / 2 + lb de, 0, 20 + jp SetExpectedAIDamage + +JynxDoubleslap_MultiplierEffect: ; 2dfcf (b:5fcf) + ld hl, 10 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + call ATimes10 + call SetDefiniteDamage + ret + +JynxMeditate_AIEffect: ; 2dff2 (b:5ff2) + call JynxMeditate_DamageBoostEffect + jp SetDefiniteAIDamage + +JynxMeditate_DamageBoostEffect: ; 2dfec (b:5fec) +; add damage counters of Defending card to damage + call SwapTurn + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call SwapTurn + call AddToDamage + ret + +MysteryAttack_AIEffect: ; 2e001 (b:6001) + ld a, 10 + lb de, 0, 20 + jp SetExpectedAIDamage + +MysteryAttack_RandomEffect: ; 2e009 (b:6009) + ld a, 10 + call SetDefiniteDamage + +; chooses a random effect from 8 possible options. + call UpdateRNGSources + and %111 + ldh [hTemp_ffa0], a + ld hl, .random_effect + jp JumpToFunctionInTable + +.random_effect + dw ParalysisEffect + dw PoisonEffect + dw SleepEffect + dw ConfusionEffect + dw .no_effect ; this will actually activate recovery effect afterwards + dw .no_effect + dw .more_damage + dw .no_damage + +.more_damage + ld a, 20 + call SetDefiniteDamage + ret + +.no_damage + ld a, ATK_ANIM_GLOW_EFFECT + ld [wLoadedAttackAnimation], a + xor a + call SetDefiniteDamage + call SetNoEffectFromStatus +.no_effect + ret + +MysteryAttack_RecoverEffect: ; 2e03e (b:603e) +; in case the 5th option was chosen for random effect, +; trigger recovery effect for 10 HP. + ldh a, [hTemp_ffa0] + cp 4 + ret nz + lb de, 0, 10 + call ApplyAndAnimateHPRecovery + ret + +StoneBarrage_AIEffect: ; 2e04a (b:604a) + ld a, 10 + lb de, 0, 100 + jp SetExpectedAIDamage + +StoneBarrage_MultiplierEffect: ; 2e052 (b:6052) + xor a + ldh [hTemp_ffa0], a +.loop_coin_toss + ldtx de, FlipUntilFailAppears10DamageForEachHeadsText + xor a + call TossCoinATimes_BankB + jr nc, .tails + ld hl, hTemp_ffa0 + inc [hl] ; increase heads count + jr .loop_coin_toss + +.tails +; store resulting damage + ldh a, [hTemp_ffa0] + ld l, a + ld h, 10 + call HtimesL + ld de, wDamage + ld a, l + ld [de], a + inc de + ld a, h + ld [de], a + ret + +OnixHardenEffect: ; 2e075 (b:6075) + ld a, SUBSTATUS1_HARDEN + call ApplySubstatus1ToDefendingCard + ret + +PrimeapeFurySwipes_AIEffect: ; 2e07b (b:607b) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +PrimeapeFurySwipes_MultiplierEffect: ; 2e083 (b:6083) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 3 + call TossCoinATimes_BankB + add a + call ATimes10 + call SetDefiniteDamage + ret + +TantrumEffect: ; 2e099 (b:6099) + ldtx de, IfTailsYourPokemonBecomesConfusedText + call TossCoin_BankB + ret c ; return if heads +; confuse Pokemon + ld a, ATK_ANIM_MULTIPLE_SLASH + ld [wLoadedAttackAnimation], a + call SwapTurn + call ConfusionEffect + call SwapTurn + ret + +StrikesBackEffect: ; 2e0af (b:60af) + scf + ret + +KabutoArmorEffect: ; 2e0b1 (b:60b1) + scf + ret + +AbsorbEffect: ; 2e0b3 (b:60b3) + ld hl, wDealtDamage + ld a, [hli] + ld h, [hl] + ld l, a + srl h + rr l + bit 0, l + jr z, .rounded + ; round up to nearest 10 + ld de, 5 + add hl, de +.rounded + ld e, l + ld d, h + call ApplyAndAnimateHPRecovery + ret + +SnivelEffect: ; 2e0cb (b:60cb) + ld a, SUBSTATUS2_REDUCE_BY_20 + call ApplySubstatus2ToDefendingCard + ret + +CuboneRage_AIEffect: ; 2e0d1 (b:60d1) + call CuboneRage_DamageBoostEffect + jp SetDefiniteAIDamage + +CuboneRage_DamageBoostEffect: ; 2e0d7 (b:60d7) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call AddToDamage + ret + +Bonemerang_AIEffect: ; 2e0e0 (b:60e0) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +Bonemerang_MultiplierEffect: ; 2e0e8 (b:60e8) + ld hl, 30 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + ld e, a + add a ; a = 2 * heads + add e ; a = 3 * heads + call ATimes10 + call SetDefiniteDamage + ret + +; returns carry if can't add Pokemon from deck +MarowakCallForFamily_CheckDeckAndPlayArea: ; 2e100 (b:6100) + call CheckIfDeckIsEmpty + ret c ; no cards in deck + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +MarowakCallForFamily_PlayerSelectEffect: ; 2e110 (b:6110) + ld a, $ff + ldh [hTemp_ffa0], a + + call CreateDeckCardList + ldtx hl, ChooseBasicFightingPokemonFromDeckText + ldtx bc, FightingPokemonDeckText + lb de, SEARCHEFFECT_BASIC_FIGHTING, $00 + call LookForCardsInDeck + ret c + +; draw Deck list interface and print text + bank1call Func_5591 + ldtx hl, ChooseBasicFightingPokemonText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + +.loop + bank1call DisplayCardList + jr c, .pressed_b + + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp FIGHTING + jr nz, .play_sfx ; is Fighting? + ld a, [wLoadedCard2Stage] + or a + jr nz, .play_sfx ; is Basic? + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + or a + ret + +.play_sfx + ; play SFX and loop back + call Func_3794 + jr .loop + +.pressed_b +; figure if Player can exit the screen without selecting, +; that is, if the Deck has no Basic Fighting Pokemon. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_b_press + ld a, [hl] + cp CARD_LOCATION_DECK + jr nz, .next + ld a, l + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard1Type] + cp FIGHTING + jr nz, .next ; found, go back to top loop + ld a, [wLoadedCard1Stage] + or a + jr z, .play_sfx ; found, go back to top loop +.next + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_b_press + +; no valid card in Deck, can safely exit screen + ld a, $ff + ldh [hTemp_ffa0], a + or a + ret + +MarowakCallForFamily_AISelectEffect: ; 2e177 (b:6177) + call CreateDeckCardList + ld hl, wDuelTempList +.loop_deck + ld a, [hli] + ldh [hTemp_ffa0], a + cp $ff + ret z ; none found + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp FIGHTING + jr nz, .loop_deck + ld a, [wLoadedCard2Stage] + or a + jr nz, .loop_deck +; found + ret + +MarowakCallForFamily_PutInPlayAreaEffect: ; 2e194 (b:6194) + ldh a, [hTemp_ffa0] + cp $ff + jr z, .shuffle + call SearchCardInDeckAndAddToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + call IsPlayerTurn + jr c, .shuffle + ; display card on screen + ldh a, [hTemp_ffa0] + ldtx hl, PlacedOnTheBenchText + bank1call DisplayCardDetailScreen +.shuffle + call Func_2c0bd + ret + +KarateChop_AIEffect: ; 2e1b4 (b:61b4) + call KarateChop_DamageSubtractionEffect + jp SetDefiniteAIDamage + +KarateChop_DamageSubtractionEffect: ; 2e1ba (b:61ba) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ld e, a + ld hl, wDamage + ld a, [hl] + sub e + ld [hli], a + ld a, [hl] + sbc 0 + ld [hl], a + rla + ret nc +; cap it to 0 damage + xor a + call SetDefiniteDamage + ret + +SubmissionEffect: ; 2e1d1 (b:61d1) + ld a, 20 + call DealRecoilDamageToSelf + ret + +GolemSelfdestructEffect: ; 2e1d7 (b:61d7) + ld a, 100 + call DealRecoilDamageToSelf + ld a, $01 + ld [wIsDamageToSelf], a + ld a, 20 + call DealDamageToAllBenchedPokemon + call SwapTurn + xor a + ld [wIsDamageToSelf], a + ld a, 20 + call DealDamageToAllBenchedPokemon + call SwapTurn + ret + +GravelerHardenEffect: ; 2e1f6 (b:61f6) + ld a, SUBSTATUS1_HARDEN + call ApplySubstatus1ToDefendingCard + ret + +Ram_SelectSwitchEffect: ; 2e1fc (b:61fc) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + jr c, .no_bench + call DuelistSelectForcedSwitch + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret +.no_bench + ld a, $ff + ldh [hTemp_ffa0], a + ret + +Ram_RecoilSwitchEffect: ; 2e212 (b:6212) + ld a, 20 + call DealRecoilDamageToSelf + ldh a, [hTemp_ffa0] + call HandleSwitchDefendingPokemonEffect + ret + +LeerEffect: ; 2e21d (b:621d) + ldtx de, IfHeadsOpponentCannotAttackText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_LEER + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS2_LEER + call ApplySubstatus2ToDefendingCard + ret + +; return carry if opponent has no Bench Pokemon. +StretchKick_CheckBench: ; 2e231 (b:6231) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +StretchKick_PlayerSelectEffect: ; 2e23c (b:623c) + ldtx hl, ChoosePkmnInTheBenchToGiveDamageText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench +.loop_input + bank1call OpenPlayAreaScreenForSelection + jr c, .loop_input + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call SwapTurn + ret + +StretchKick_AISelectEffect: ; 2e255 (b:6255) +; chooses Bench Pokemon with least amount of remaining HP + call GetBenchPokemonWithLowestHP + ldh [hTemp_ffa0], a + ret + +StretchKick_BenchDamageEffect: ; 2e25b (b:625b) + call SwapTurn + ldh a, [hTemp_ffa0] + ld b, a + ld de, 20 + call DealDamageToPlayAreaPokemon_RegularAnim + call SwapTurn + ret + +SandAttackEffect: ; 2e26b (b:626b) + ld a, SUBSTATUS2_SAND_ATTACK + call ApplySubstatus2ToDefendingCard + ret + +SandslashFurySwipes_AIEffect: ; 2e271 (b:6271) + ld a, 60 / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +SandslashFurySwipes_MultiplierEffect: ; 2e279 (b:6279) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 3 + call TossCoinATimes_BankB + add a + call ATimes10 + call SetDefiniteDamage + ret + +EarthquakeEffect: ; 2e28f (b:628f) + ld a, $01 + ld [wIsDamageToSelf], a + ld a, 10 + call DealDamageToAllBenchedPokemon + ret + +PrehistoricPowerEffect: ; 2e29a (b:629a) + scf + ret + +; returns carry if Pkmn Power can't be used. +Peek_OncePerTurnCheck: ; 2e29c (b:629c) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + and USED_PKMN_POWER_THIS_TURN + jr nz, .already_used + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret +.already_used + ldtx hl, OnlyOncePerTurnText + scf + ret + +Peek_SelectEffect: ; 2e2b4 (b:62b4) +; set Pkmn Power used flag + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + set USED_PKMN_POWER_THIS_TURN_F, [hl] + + ld a, DUELVARS_DUELIST_TYPE + call GetTurnDuelistVariable + cp DUELIST_TYPE_LINK_OPP + jr z, .link_opp + and DUELIST_TYPE_AI_OPP + jr nz, .ai_opp + +; player + call Func_3b31 + call HandlePeekSelection + ldh [hAIPkmnPowerEffectParam], a + call SerialSend8Bytes + ret + +.link_opp + call SerialRecv8Bytes + ldh [hAIPkmnPowerEffectParam], a + +.ai_opp + ldh a, [hAIPkmnPowerEffectParam] + bit AI_PEEK_TARGET_HAND_F, a + jr z, .prize_or_deck + and (~AI_PEEK_TARGET_HAND & $ff) ; unset bit to get deck index +; if masked value is higher than $40, then it means +; that AI chose to look at Player's deck. +; all deck indices will be smaller than $40. + cp $40 + jr c, .hand + ldh a, [hAIPkmnPowerEffectParam] + jr .prize_or_deck + +.hand +; AI chose to look at random card in hand, +; so display it to the Player on screen. + call SwapTurn + ldtx hl, PeekWasUsedToLookInYourHandText + bank1call DisplayCardDetailScreen + call SwapTurn + ret + +.prize_or_deck +; AI chose either a prize card or Player's top deck card, +; so show Play Area and draw cursor appropriately. + call Func_3b31 + call SwapTurn + ldh a, [hAIPkmnPowerEffectParam] + xor $80 + call DrawAIPeekScreen + call SwapTurn + ldtx hl, CardPeekWasUsedOnText + call DrawWideTextBox_WaitForInput + ret + +BoneAttackEffect: ; 2e30f (b:630f) + ldtx de, IfHeadsOpponentCannotAttackText + call TossCoin_BankB + ret nc + ld a, SUBSTATUS2_BONE_ATTACK + call ApplySubstatus2ToDefendingCard + ret + +; return carry if neither Play Area +; has room for more Bench Pokemon. +Wail_BenchCheck: ; 2e31c (b:631c) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr c, .no_carry + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + jr c, .no_carry + ldtx hl, NoSpaceOnTheBenchText + scf + ret +.no_carry + or a + ret + +Wail_FillBenchEffect: ; 2e335 (b:6335) + call SwapTurn + call .FillBench + call SwapTurn + call .FillBench + +; display both Play Areas + ldtx hl, BasicPokemonWasPlacedOnEachBenchText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInPlayArea + bank1call OpenPlayAreaScreenForSelection + call SwapTurn + bank1call HasAlivePokemonInPlayArea + bank1call OpenPlayAreaScreenForSelection + call SwapTurn + ret + +.FillBench ; 2e35a (b:635a) + call CreateDeckCardList + ret c + ld hl, wDuelTempList + call ShuffleCards + +; if no more space in the Bench, then return. +.check_bench + push hl + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + pop hl + cp MAX_PLAY_AREA_POKEMON + jr nc, .done + +; there's still space, so look for the next +; Basic Pokemon card to put in the Bench. +.loop + ld a, [hli] + ldh [hTempCardIndex_ff98], a + cp $ff + jr z, .done + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .loop ; is Pokemon card? + ld a, [wLoadedCard2Stage] + or a + jr nz, .loop ; is Basic? +; place card in Bench + push hl + ldh a, [hTempCardIndex_ff98] + call SearchCardInDeckAndAddToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + pop hl + jr .check_bench + +.done + call Func_2c0bd + ret + +Thunderpunch_AIEffect: ; 2e399 (b:6399) + ld a, (30 + 40) / 2 + lb de, 30, 40 + jp SetExpectedAIDamage + +Thunderpunch_ModifierEffect: ; 2e3a1 (b:63a1) + ldtx de, IfHeadPlus10IfTails10ToYourselfText + call TossCoin_BankB + ldh [hTemp_ffa0], a + ret nc ; return if got tails + ld a, 10 + call AddToDamage + ret + +Thunderpunch_RecoilEffect: ; 2e3b0 (b:63b0) + ldh a, [hTemp_ffa0] + or a + ret nz ; return if got heads + ld a, 10 + call DealRecoilDamageToSelf + ret + +LightScreenEffect: ; 2e3ba (b:63ba) + ld a, SUBSTATUS1_HALVE_DAMAGE + call ApplySubstatus1ToDefendingCard + ret + +ElectabuzzQuickAttack_AIEffect: ; 2e3c0 (b:63c0) + ld a, (10 + 30) / 2 + lb de, 10, 30 + jp SetExpectedAIDamage + +ElectabuzzQuickAttack_DamageBoostEffect: ; 2e3c8 (b:63c8) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; return if tails + ld a, 20 + call AddToDamage + ret + +MagnemiteSelfdestructEffect: ; 2e3db (b:63db) + ld a, 40 + call DealRecoilDamageToSelf + + ld a, $01 + ld [wIsDamageToSelf], a + ld a, 10 + call DealDamageToAllBenchedPokemon + call SwapTurn + + xor a + ld [wIsDamageToSelf], a + ld a, 10 + call DealDamageToAllBenchedPokemon + call SwapTurn + ret + +ZapdosThunder_Recoil50PercentEffect: ; 2e3fa (b:63fa) + ld hl, 30 + call LoadTxRam3 + ldtx de, IfTailsDamageToYourselfTooText + call TossCoin_BankB + ldh [hTemp_ffa0], a + ret + +ZapdosThunder_RecoilEffect: ; 2e409 (b:6409) + ld hl, 30 + call LoadTxRam3 + ldh a, [hTemp_ffa0] + or a + ret nz ; return if got heads + ld a, 30 + call DealRecoilDamageToSelf + ret + +ThunderboltEffect: ; 2e419 (b:6419) + xor a + call CreateArenaOrBenchEnergyCardList + ld hl, wDuelTempList +; put all energy cards in Discard Pile +.loop + ld a, [hli] + cp $ff + ret z + call PutCardInDiscardPile + jr .loop + +ThunderstormEffect: ; 2e429 (b:6429) + ld a, 1 + ldh [hCurSelectionItem], a + + call SwapTurn + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld b, 0 + ld e, b + jr .next_pkmn + +.check_damage + push de + push bc + call .DisplayText + ld de, $0 + call SwapTurn + call TossCoin_BankB + call SwapTurn + push af + call GetNextPositionInTempList + pop af + ld [hl], a ; store result in list + pop bc + pop de + jr c, .next_pkmn + inc b ; increase number of tails + +.next_pkmn + inc e + dec c + jr nz, .check_damage + +; all coins were tossed for each Benched Pokemon + call GetNextPositionInTempList + ld [hl], $ff + ld a, b + ldh [hTemp_ffa0], a + call Func_3b21 + call SwapTurn + +; tally recoil damage + ldh a, [hTemp_ffa0] + or a + jr z, .skip_recoil + ; deal number of tails times 10 to self + call ATimes10 + call DealRecoilDamageToSelf +.skip_recoil + +; deal damage for Bench Pokemon that got heads + call SwapTurn + ld hl, hTempPlayAreaLocation_ffa1 + ld b, PLAY_AREA_BENCH_1 +.loop_bench + ld a, [hli] + cp $ff + jr z, .done + or a + jr z, .skip_damage ; skip if tails + ld de, 20 + call DealDamageToPlayAreaPokemon_RegularAnim +.skip_damage + inc b + jr .loop_bench + +.done + call SwapTurn + ret + +; displays text for current Bench Pokemon, +; printing its Bench number and name. +.DisplayText ; 2e491 (b:6491) + ld b, e + ldtx hl, BenchText + ld de, wDefaultText + call CopyText + ld a, $30 ; 0 FW character + add b + ld [de], a + inc de + ld a, $20 ; space FW character + ld [de], a + inc de + + ld a, DUELVARS_ARENA_CARD + add b + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld hl, wLoadedCard2Name + ld a, [hli] + ld h, [hl] + ld l, a + call CopyText + + xor a + ld [wDuelDisplayedScreen], a + ret + +JolteonQuickAttack_AIEffect: ; 2e4bb (b:64bb) + ld a, (10 + 30) / 2 + lb de, 10, 30 + jp SetExpectedAIDamage + +JolteonQuickAttack_DamageBoostEffect: ; 2e4c3 (b:64c3) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; return if tails + ld a, 20 + call AddToDamage + ret + +PinMissile_AIEffect: ; 2e4d6 (b:64d6) + ld a, (20 * 4) / 2 + lb de, 0, 80 + jp SetExpectedAIDamage + +PinMissile_MultiplierEffect: ; 2e4de (b:64de) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 4 + call TossCoinATimes_BankB + add a ; a = 2 * heads + call ATimes10 + call SetDefiniteDamage + ret + +Fly_AIEffect: ; 2e4f4 (b:64f4) + ld a, 30 / 2 + lb de, 0, 30 + jp SetExpectedAIDamage + +Fly_Success50PercentEffect: ; 2e4fc (b:64fc) + ldtx de, SuccessCheckIfHeadsAttackIsSuccessfulText + call TossCoin_BankB + jr c, .heads + xor a ; ATK_ANIM_NONE + ld [wLoadedAttackAnimation], a + call SetDefiniteDamage + call SetWasUnsuccessful + ret +.heads + ld a, ATK_ANIM_AGILITY_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_FLY + call ApplySubstatus1ToDefendingCard + ret + +ThunderJolt_Recoil50PercentEffect: ; 2e51a (b:651a) + ld hl, 10 + call LoadTxRam3 + ldtx de, IfTailsDamageToYourselfTooText + call TossCoin_BankB + ldh [hTemp_ffa0], a + ret + +ThunderJolt_RecoilEffect: ; 2e529 (b:6529) + ld hl, 10 + call LoadTxRam3 + ldh a, [hTemp_ffa0] + or a + ret nz ; return if was heads + ld a, 10 + call DealRecoilDamageToSelf + ret + +Spark_PlayerSelectEffect: ; 2e539 (b:6539) + ld a, $ff + ldh [hTemp_ffa0], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + ret c ; has no Bench Pokemon + + ldtx hl, ChoosePkmnInTheBenchToGiveDamageText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench + + ; the following two instructions can be removed + ; since Player selection will overwrite it. + ld a, PLAY_AREA_BENCH_1 + ldh [hTempPlayAreaLocation_ff9d], a + +.loop_input + bank1call OpenPlayAreaScreenForSelection + jr c, .loop_input + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call SwapTurn + ret + +Spark_AISelectEffect: ; 2e562 (b:6562) + ld a, $ff + ldh [hTemp_ffa0], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + ret c ; has no Bench Pokemon +; AI always picks Pokemon with lowest HP remaining + call GetBenchPokemonWithLowestHP + ldh [hTemp_ffa0], a + ret + +Spark_BenchDamageEffect: ; 2e574 (b:6574) + ldh a, [hTemp_ffa0] + cp $ff + ret z + call SwapTurn + ldh a, [hTemp_ffa0] + ld b, a + ld de, 10 + call DealDamageToPlayAreaPokemon_RegularAnim + call SwapTurn + ret + +Pikachu3GrowlEffect: ; 2e589 (b:6589) + ld a, SUBSTATUS2_GROWL + call ApplySubstatus2ToDefendingCard + ret + +Pikachu4GrowlEffect: ; 2e58f (b:658f) + ld a, SUBSTATUS2_GROWL + call ApplySubstatus2ToDefendingCard + ret + +ChainLightningEffect: ; 2e595 (b:6595) + ld a, 10 + call SetDefiniteDamage + call SwapTurn + call GetArenaCardColor + call SwapTurn + ldh [hCurSelectionItem], a + cp COLORLESS + ret z ; don't damage if colorless + +; opponent's Bench + call SwapTurn + call .DamageSameColorBench + call SwapTurn + +; own Bench + ld a, $01 + ld [wIsDamageToSelf], a + call .DamageSameColorBench + ret + +.DamageSameColorBench ; 2e5ba (b:65ba) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld e, a + ld d, PLAY_AREA_ARENA + jr .next_bench + +.check_damage + ld a, d + call GetPlayAreaCardColor + ld c, a + ldh a, [hCurSelectionItem] + cp c + jr nz, .next_bench ; skip if not same color +; apply damage to this Bench card + push de + ld b, d + ld de, 10 + call DealDamageToPlayAreaPokemon_RegularAnim + pop de + +.next_bench + inc d + dec e + jr nz, .check_damage + ret + +RaichuAgilityEffect: ; 2e5dc (b:65dc) + ldtx de, IfHeadsDoNotReceiveDamageOrEffectText + call TossCoin_BankB + ret nc ; skip if got tails + ld a, ATK_ANIM_AGILITY_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_AGILITY + call ApplySubstatus1ToDefendingCard + ret + +RaichuThunder_Recoil50PercentEffect: ; 2e5ee (b:65ee) + ld hl, 30 + call LoadTxRam3 + ldtx de, IfTailsDamageToYourselfTooText + call TossCoin_BankB + ldh [hTemp_ffa0], a + ret + +RaichuThunder_RecoilEffect: ; 2e5fd (b:65fd) + ld hl, 30 + call LoadTxRam3 + ldh a, [hTemp_ffa0] + or a + ret nz ; return if got heads + ld a, 30 + call DealRecoilDamageToSelf + ret + +Gigashock_PlayerSelectEffect: ; 2e60d (b:660d) + call SwapTurn + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + jr nc, .has_bench + call SwapTurn + ld a, $ff + ldh [hTempList], a + ret + +.has_bench + ldtx hl, ChooseUpTo3PkmnOnBenchToGiveDamageText + call DrawWideTextBox_WaitForInput + +; init number of items in list and cursor position + xor a + ldh [hCurSelectionItem], a + ld [wce72], a + bank1call Func_61a1 +.start + bank1call PrintPlayAreaCardList_EnableLCD + push af + ld a, [wce72] + ld hl, BenchSelectionMenuParameters + call InitializeMenuParameters + pop af + +; exclude Arena Pokemon from number of items + dec a + ld [wNumMenuItems], a + +.loop_input + call DoFrame + call HandleMenuInput + jr nc, .loop_input + cp -1 + jr z, .try_cancel + + ld [wce72], a + call .CheckIfChosenAlready + jr nc, .not_chosen + ; play SFX + call Func_3794 + jr .loop_input + +.not_chosen +; mark this Play Area location + ldh a, [hCurMenuItem] + inc a + ld b, SYM_LIGHTNING + call DrawSymbolOnPlayAreaCursor +; store it in the list of chosen Bench Pokemon + call GetNextPositionInTempList + ldh a, [hCurMenuItem] + inc a + ld [hl], a + +; check if 3 were chosen already + ldh a, [hCurSelectionItem] + ld c, a + cp 3 + jr nc, .chosen ; check if already chose 3 + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a + cp c + jr nz, .start ; if sill more options available, loop back + ; fallthrough if no other options available to choose + +.chosen + ldh a, [hCurMenuItem] + inc a + call Func_2c10b + ldh a, [hKeysPressed] + and B_BUTTON + jr nz, .try_cancel + call SwapTurn + call GetNextPositionInTempList + ld [hl], $ff ; terminating byte + ret + +.try_cancel + ldh a, [hCurSelectionItem] + or a + jr z, .start ; none selected, can safely loop back to start + +; undo last selection made + dec a + ldh [hCurSelectionItem], a + ld e, a + ld d, $00 + ld hl, hTempList + add hl, de + ld a, [hl] + + push af + ld b, SYM_SPACE + call DrawSymbolOnPlayAreaCursor + call EraseCursor + pop af + + dec a + ld [wce72], a + jr .start + +; returns carry if Bench Pokemon +; in register a was already chosen. +.CheckIfChosenAlready: ; 2e6af (b:66af) + inc a + ld c, a + ldh a, [hCurSelectionItem] + ld b, a + ld hl, hTempList + inc b + jr .next_check +.check_chosen + ld a, [hli] + cp c + scf + ret z ; return if chosen already +.next_check + dec b + jr nz, .check_chosen + or a + ret + +Gigashock_AISelectEffect: ; 2e6c3 (b:66c3) +; if Bench has 3 Pokemon or less, no need for selection, +; since AI will choose them all. + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON - 1 + jr nc, .start_selection + +; select them all + ld hl, hTempList + ld b, PLAY_AREA_ARENA + jr .next_bench +.select_bench + ld [hl], b + inc hl +.next_bench + inc b + dec a + jr nz, .select_bench + ld [hl], $ff ; terminating byte + ret + +.start_selection +; has more than 3 Bench cards, proceed to sort them +; by lowest remaining HP to highest, and pick first 3. + call SwapTurn + dec a + ld c, a + ld b, PLAY_AREA_BENCH_1 + +; first select all of the Bench Pokemon and write to list + ld hl, hTempList +.loop_all + ld [hl], b + inc hl + inc b + dec c + jr nz, .loop_all + ld [hl], $00 ; end list with $00 + +; then check each of the Bench Pokemon HP +; sort them from lowest remaining HP to highest. + ld de, hTempList +.loop_outer + ld a, [de] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld c, a + ld l, e + ld h, d + inc hl + +.loop_inner + ld a, [hli] + or a + jr z, .next ; reaching $00 means it's end of list + + push hl + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + pop hl + cp c + jr c, .loop_inner + ; a Bench Pokemon was found with less HP + ld c, a ; store its HP + +; switch the two + dec hl + ld b, [hl] + ld a, [de] + ld [hli], a + ld a, b + ld [de], a + jr .loop_inner + +.next + inc de + ld a, [de] + or a + jr nz, .loop_outer + +; done + ld a, $ff ; terminating byte + ldh [hTempList + 3], a + call SwapTurn + ret + +Gigashock_BenchDamageEffect: ; 2e71f (b:671f) + call SwapTurn + ld hl, hTempList +.loop_selection + ld a, [hli] + cp $ff + jr z, .done + push hl + ld b, a + ld de, 10 + call DealDamageToPlayAreaPokemon_RegularAnim + pop hl + jr .loop_selection +.done + call SwapTurn + ret + +Magneton1SelfdestructEffect: ; 2e739 (b:6739) + ld a, 80 + call DealRecoilDamageToSelf + +; own bench + ld a, $01 + ld [wIsDamageToSelf], a + ld a, 20 + call DealDamageToAllBenchedPokemon + +; opponent's bench + call SwapTurn + xor a + ld [wIsDamageToSelf], a + ld a, 20 + call DealDamageToAllBenchedPokemon + call SwapTurn + ret + +MagnetonSonicboom_UnaffectedByColorEffect: ; 2e758 (b:6758) + ld hl, wDamage + 1 + set UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, [hl] + ret + +MagnetonSonicboom_NullEffect: ; 2e75e (b:675e) + ret + +Magneton2SelfdestructEffect: ; 2e75f (b:675f) + ld a, 100 + call DealRecoilDamageToSelf + +; own bench + ld a, $01 + ld [wIsDamageToSelf], a + ld a, 20 + call DealDamageToAllBenchedPokemon + +; opponent's bench + call SwapTurn + xor a + ld [wIsDamageToSelf], a + ld a, 20 + call DealDamageToAllBenchedPokemon + call SwapTurn + ret + +PealOfThunder_InitialEffect: ; 2e77e (b:677e) + scf + ret + +PealOfThunder_RandomlyDamageEffect: ; 2e780 (b:6780) + call ExchangeRNG + ld de, 30 ; damage to inflict + call RandomlyDamagePlayAreaPokemon + bank1call Func_6e49 + ret + +; randomly damages a Pokemon in play, except +; card that is in [hTempPlayAreaLocation_ff9d]. +; plays thunder animation when Play Area is shown. +; input: +; de = amount of damage to deal +RandomlyDamagePlayAreaPokemon: ; 2e78d (b:678d) + xor a + ld [wNoDamageOrEffect], a + +; choose randomly which Play Area to attack + call UpdateRNGSources + and 1 + jr nz, .opp_play_area + +; own Play Area + ld a, $01 + ld [wIsDamageToSelf], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + call Random + ld b, a + ; can't select Zapdos + ldh a, [hTempPlayAreaLocation_ff9d] + cp b + jr z, RandomlyDamagePlayAreaPokemon ; re-roll Pokemon to attack + +.damage + ld a, ATK_ANIM_THUNDER_PLAY_AREA + ld [wLoadedAttackAnimation], a + call DealDamageToPlayAreaPokemon + ret + +.opp_play_area + xor a + ld [wIsDamageToSelf], a + call SwapTurn + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + call Random + ld b, a + call .damage + call SwapTurn + ret + +BigThunderEffect: ; 2e7cb (b:67cb) + call ExchangeRNG + ld de, 70 ; damage to inflict + call RandomlyDamagePlayAreaPokemon + ret + +MagneticStormEffect: ; 2e7d5 (b:67d5) + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable + +; writes in wDuelTempList all deck indices +; of Energy cards attached to Pokemon +; in the Turn Duelist's Play Area. + ld de, wDuelTempList + ld c, 0 +.loop_card_locations + ld a, [hl] + and CARD_LOCATION_PLAY_AREA + jr z, .next_card_location + +; is a card that is in the Play Area + push hl + push de + push bc + ld a, l + call GetCardIDFromDeckIndex + call GetCardType + pop bc + pop de + pop hl + and TYPE_ENERGY + jr z, .next_card_location +; is an Energy card attached to Pokemon in Play Area + ld a, l + ld [de], a + inc de + inc c +.next_card_location + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_card_locations + ld a, $ff ; terminating byte + ld [de], a + +; divide number of energy cards +; by number of Pokemon in Play Area + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld a, c + ld c, -1 +.loop_division + inc c + sub b + jr nc, .loop_division + ; c = floor(a / b) + +; evenly divides the Energy cards randomly +; to every Pokemon in the Play Area. + push bc + ld hl, wDuelTempList + call CountCardsInDuelTempList + call ShuffleCards + ld d, c + ld e, PLAY_AREA_ARENA +.start_attach + ld c, d + inc c + jr .check_done +.attach_energy + ld a, [hli] + push hl + push de + push bc + call AddCardToHand + call PutHandCardInPlayArea + pop bc + pop de + pop hl +.check_done + dec c + jr nz, .attach_energy +; go to next Pokemon in Play Area + inc e ; next in Play Area + dec b + jr nz, .start_attach + pop bc + + push hl + ld hl, hTempList + +; fill hTempList with PLAY_AREA_* locations +; that have Pokemon in them. + push hl + xor a +.loop_init + ld [hli], a + inc a + cp b + jr nz, .loop_init + pop hl + +; shuffle them and distribute +; the remaining cards in random order. + ld a, b + call ShuffleCards + pop hl + ld de, hTempList +.next_random_pokemon + ld a, [hl] + cp $ff + jr z, .done + push hl + push de + ld a, [de] + ld e, a + ld a, [hl] + call AddCardToHand + call PutHandCardInPlayArea + pop de + pop hl + inc hl + inc de + jr .next_random_pokemon + +.done + bank1call DrawDuelMainScene + bank1call DrawDuelHUDs + ldtx hl, TheEnergyCardFromPlayAreaWasMovedText + call DrawWideTextBox_WaitForInput + xor a + call Func_2c10b + ret + +ElectrodeSonicboom_UnaffectedByColorEffect: ; 2e870 (b:6870) + ld hl, wDamage + 1 + set UNAFFECTED_BY_WEAKNESS_RESISTANCE_F, [hl] + ret + +ElectrodeSonicboom_NullEffect: ; 2e876 (b:6876) + ret + +; return carry if no cards in Deck +EnergySpike_DeckCheck: ; 2e877 (b:6877) + call CheckIfDeckIsEmpty + ret + +EnergySpike_PlayerSelectEffect: ; 2e87b (b:687b) + ld a, $ff + ldh [hTemp_ffa0], a + +; search cards in Deck + call CreateDeckCardList + ldtx hl, Choose1BasicEnergyCardFromDeckText + ldtx bc, BasicEnergyText + lb de, SEARCHEFFECT_BASIC_ENERGY, 0 + call LookForCardsInDeck + ret c + + bank1call Func_5591 + ldtx hl, ChooseBasicEnergyCardText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText +.select_card + bank1call DisplayCardList + jr c, .try_cancel + call GetCardIDFromDeckIndex + call GetCardType + cp TYPE_ENERGY_DOUBLE_COLORLESS + jr nc, .select_card ; not a Basic Energy card + and TYPE_ENERGY + jr z, .select_card ; not a Basic Energy card + ; Energy card selected + + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + call EmptyScreen + ldtx hl, ChoosePokemonToAttachEnergyCardText + call DrawWideTextBox_WaitForInput + +; choose a Pokemon in Play Area to attach card + bank1call HasAlivePokemonInPlayArea +.loop_input + bank1call OpenPlayAreaScreenForSelection + jr c, .loop_input + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempPlayAreaLocation_ffa1], a + ret + +.play_sfx + call Func_3794 + jr .select_card + +.try_cancel +; Player tried exiting screen, if there are +; any Basic Energy cards, Player is forced to select them. +; otherwise, they can safely exit. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_deck + ld a, [hl] + cp CARD_LOCATION_DECK + jr nz, .next_card + ld a, l + call GetCardIDFromDeckIndex + call GetCardType + and TYPE_ENERGY + jr z, .next_card + cp TYPE_ENERGY_DOUBLE_COLORLESS + jr c, .play_sfx +.next_card + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_deck + ; can exit + + ld a, $ff + ldh [hTemp_ffa0], a + ret + +EnergySpike_AISelectEffect: ; 2e8f1 (b:68f1) +; AI doesn't select any card + ld a, $ff + ldh [hTemp_ffa0], a + ret + +EnergySpike_AttachEnergyEffect: ; 2e8f6 (b:68f6) + ldh a, [hTemp_ffa0] + cp $ff + jr z, .done + +; add card to hand and attach it to the selected Pokemon + call SearchCardInDeckAndAddToHand + call AddCardToHand + ldh a, [hTempPlayAreaLocation_ffa1] + ld e, a + ldh a, [hTemp_ffa0] + call PutHandCardInPlayArea + call IsPlayerTurn + jr c, .done + +; not Player, so show detail screen +; and which Pokemon was chosen to attach Energy. + ldh a, [hTempPlayAreaLocation_ffa1] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld hl, wLoadedCard1Name + ld de, wTxRam2_b + ld a, [hli] + ld [de], a + inc de + ld a, [hli] + ld [de], a + ldh a, [hTemp_ffa0] + ldtx hl, AttachedEnergyToPokemonText + bank1call DisplayCardDetailScreen + +.done + call Func_2c0bd + ret + +JolteonDoubleKick_AIEffect: ; 2e930 (b:6930) + ld a, 40 / 2 + lb de, 0, 40 + jp SetExpectedAIDamage + +JolteonDoubleKick_MultiplierEffect: ; 2e938 (b:6938) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + add a ; a = 2 * heads + call ATimes10 + call SetDefiniteDamage + ret + +TailWagEffect: ; 2e94e (b:694e) + ldtx de, IfHeadsOpponentCannotAttackText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_LURE + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS2_TAIL_WAG + call ApplySubstatus2ToDefendingCard + ret + +EeveeQuickAttack_AIEffect: ; 2e962 (b:5962) + ld a, (10 + 30) / 2 + lb de, 10, 30 + jp SetExpectedAIDamage + +EeveeQuickAttack_DamageBoostEffect: ; 2e96a (b:596a) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; return if tails + ld a, 20 + call AddToDamage + ret + +SpearowMirrorMove_AIEffect: ; 2e97d (b:697d) + jr MirrorMoveEffects.AIEffect + +SpearowMirrorMove_InitialEffect1: ; 2e97f (b:697f) + jr MirrorMoveEffects.InitialEffect1 + +SpearowMirrorMove_InitialEffect2: ; 2e981 (b:6981) + jr MirrorMoveEffects.InitialEffect2 + +SpearowMirrorMove_PlayerSelection: ; 2e983 (b:6983) + jr MirrorMoveEffects.PlayerSelection + +SpearowMirrorMove_AISelection: ; 2e985 (b:6985) + jr MirrorMoveEffects.AISelection + +SpearowMirrorMove_BeforeDamage: ; 2e987 (b:6987) + jr MirrorMoveEffects.BeforeDamage + +SpearowMirrorMove_AfterDamage: ; 2e989 (b:6989) + jp MirrorMoveEffects.AfterDamage + +; these are effect commands that Mirror Move uses +; in order to mimic last turn's attack. +; it covers all possible effect steps to perform its commands +; (i.e. selection for Amnesia and Energy discarding attacks, etc) +MirrorMoveEffects: ; 2e98c (b:698c) +.AIEffect + ld a, DUELVARS_ARENA_CARD_LAST_TURN_DAMAGE + call GetTurnDuelistVariable + ld a, [hl] + ld [wAIMinDamage], a + ld [wAIMaxDamage], a + ret + +.InitialEffect1 + ld a, DUELVARS_ARENA_CARD_LAST_TURN_DAMAGE + call GetTurnDuelistVariable + ld a, [hli] + or [hl] + inc hl + or [hl] + inc hl + ret nz ; return if has last turn damage + ld a, [hli] + or a + ret nz ; return if has last turn status + ; no attack received last turn + ldtx hl, YouDidNotReceiveAnAttackToMirrorMoveText + scf + ret + +.InitialEffect2 + ld a, $ff + ldh [hTemp_ffa0], a + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + or a + ret z ; no effect + cp LAST_TURN_EFFECT_AMNESIA + jp z, PlayerPickAttackForAmnesia + or a + ret + +.PlayerSelection + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + or a + ret z ; no effect +; handle Energy card discard effect + cp LAST_TURN_EFFECT_DISCARD_ENERGY + jp z, HandleEnergyDiscardEffectSelection + ret + +.AISelection + ld a, $ff + ldh [hTemp_ffa0], a + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + or a + ret z ; no effect + cp LAST_TURN_EFFECT_DISCARD_ENERGY + jr z, .discard_energy + cp LAST_TURN_EFFECT_AMNESIA + jr z, .pick_amnesia_attack + ret + +.discard_energy + call AIPickEnergyCardToDiscardFromDefendingPokemon + ldh [hTemp_ffa0], a + ret + +.pick_amnesia_attack + call AIPickAttackForAmnesia + ldh [hTemp_ffa0], a + ret + +.BeforeDamage +; if was attacked with Amnesia, apply it to the selected attack + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + cp LAST_TURN_EFFECT_AMNESIA + jr z, .apply_amnesia + +; otherwise, check if there was last turn damage, +; and write it to wDamage. + ld a, DUELVARS_ARENA_CARD_LAST_TURN_DAMAGE + call GetTurnDuelistVariable + ld de, wDamage + ld a, [hli] + ld [de], a + inc de + ld a, [hld] + ld [de], a + or [hl] + jr z, .no_damage + ld a, ATK_ANIM_HIT + ld [wLoadedAttackAnimation], a +.no_damage + inc hl + inc hl ; DUELVARS_ARENA_CARD_LAST_TURN_STATUS +; check if there was a status applied to Defending Pokemon +; from the attack it used. + push hl + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + ld e, l + ld d, h + pop hl + ld a, [hli] + or a + jr z, .no_status + push hl + push de + call .ExecuteStatusEffect + pop de + pop hl +.no_status +; hl is at DUELVARS_ARENA_CARD_LAST_TURN_SUBSTATUS2 +; apply substatus2 to self + ld e, DUELVARS_ARENA_CARD_SUBSTATUS2 + ld a, [hli] + ld [de], a + ret + +.apply_amnesia + call ApplyAmnesiaToAttack + ret + +.AfterDamage: ; 2ea28 (b:6a28) + ld a, [wNoDamageOrEffect] + or a + ret nz ; is unaffected + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + cp LAST_TURN_EFFECT_DISCARD_ENERGY + jr nz, .change_weakness + +; execute Energy discard effect for card chosen + call SwapTurn + ldh a, [hTemp_ffa0] + call PutCardInDiscardPile + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + ld [hl], LAST_TURN_EFFECT_DISCARD_ENERGY + call SwapTurn + +.change_weakness + ld a, DUELVARS_ARENA_CARD_LAST_TURN_CHANGE_WEAK + call GetTurnDuelistVariable + ld a, [hl] + or a + ret z ; weakness wasn't changed last turn + + push hl + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + call SwapTurn + pop hl + + ld a, [wLoadedCard2Weakness] + or a + ret z ; defending Pokemon has no weakness to change + +; apply same color weakness to Defending Pokemon + ld a, [hl] + push af + ld a, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS + call GetNonTurnDuelistVariable + pop af + ld [hl], a + +; print message of weakness color change + ld c, -1 +.loop_color + inc c + rla + jr nc, .loop_color + ld a, c + call SwapTurn + push af + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + pop af + call LoadCardNameAndInputColor + ldtx hl, ChangedTheWeaknessOfPokemonToColorText + call DrawWideTextBox_PrintText + call SwapTurn + ret + +.ExecuteStatusEffect: ; 2ea8f (b:6a8f) + ld c, a + and PSN_DBLPSN + jr z, .cnf_slp_prz + ld b, a + cp DOUBLE_POISONED + push bc + call z, DoublePoisonEffect + pop bc + ld a, b + cp POISONED + push bc + call z, PoisonEffect + pop bc +.cnf_slp_prz + ld a, c + and CNF_SLP_PRZ + ret z + cp CONFUSED + jp z, ConfusionEffect + cp ASLEEP + jp z, SleepEffect + cp PARALYZED + jp z, ParalysisEffect + ret + +FearowAgilityEffect: ; 2eab8 (b:6ab8) + ldtx de, IfHeadsDoNotReceiveDamageOrEffectText + call TossCoin_BankB + ret nc + ld a, ATK_ANIM_AGILITY_PROTECT + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_AGILITY + call ApplySubstatus1ToDefendingCard + ret + +; return carry if cannot use Step In +StepIn_BenchCheck: ; 2eaca (b:6aca) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ldtx hl, CanOnlyBeUsedOnTheBenchText + or a + jr z, .set_carry + + add DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + ldtx hl, OnlyOncePerTurnText + and USED_PKMN_POWER_THIS_TURN + jr nz, .set_carry + + ldh a, [hTempPlayAreaLocation_ff9d] + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + ret + +.set_carry + scf + ret + +StepIn_SwitchEffect: ; 2eae8 (b:6ae8) + ldh a, [hTemp_ffa0] + ld e, a + call SwapArenaWithBenchPokemon + ld a, DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + set USED_PKMN_POWER_THIS_TURN_F, [hl] + ret + +Dragonite2Slam_AIEffect: ; 2eaf6 (b:6af6) + ld a, (40 * 2) / 2 + lb de, 0, 80 + jp SetExpectedAIDamage + +Dragonite2Slam_MultiplierEffect: ; 2eafe (b:6afe) + ld hl, 40 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + add a + add a + call ATimes10 + call SetDefiniteDamage + ret + +ThickSkinnedEffect: ; 2eb15 (b:6b15) + scf + ret + +LeekSlap_AIEffect: ; 2eb17 (b:6b17) + ld a, 30 / 2 + lb de, 0, 30 + jp SetExpectedAIDamage + +; return carry if already used attack in this duel +LeekSlap_OncePerDuelCheck: ; 2eb1f (b:6b1f) +; can only use attack if it was never used before this duel + ld a, DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + and USED_LEEK_SLAP_THIS_DUEL + ret z + ldtx hl, ThisAttackCannotBeUsedTwiceText + scf + ret + +LeekSlap_SetUsedThisDuelFlag: ; 2eb2c (b:6b2c) + ld a, DUELVARS_ARENA_CARD_FLAGS + call GetTurnDuelistVariable + set USED_LEEK_SLAP_THIS_DUEL_F, [hl] + ret + +LeekSlap_NoDamage50PercentEffect: ; 2eb34 (b:6b34) + ldtx de, DamageCheckIfTailsNoDamageText + call TossCoin_BankB + ret c + xor a ; 0 damage + call SetDefiniteDamage + ret + +FetchEffect: ; 2eb40 (b:6b40) + ldtx hl, Draw1CardFromTheDeckText + call DrawWideTextBox_WaitForInput + bank1call DisplayDrawOneCardScreen + call DrawCardFromDeck + ret c ; return if deck is empty + call AddCardToHand + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wDuelistType] + cp DUELIST_TYPE_PLAYER + ret nz + ; show card on screen if it was Player + bank1call OpenCardPage_FromHand + ret + +CometPunch_AIEffect: ; 2eb5d (b:6b5d) + ld a, (20 * 4) / 2 + lb de, 0, 80 + jp SetExpectedAIDamage + +CometPunch_MultiplierEffect: ; 2eb65 (b:6b65) + ld hl, 20 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 4 + call TossCoinATimes_BankB + add a + call ATimes10 + call SetDefiniteDamage + ret + +TaurosStomp_AIEffect: ; 2eb7b (b:6b7b) + ld a, (20 + 30) / 2 + lb de, 20, 30 + jp SetExpectedAIDamage + +TaurosStomp_DamageBoostEffect: ; 2eb83 (b:6b83) + ld hl, 10 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsPlusDamageText + call TossCoin_BankB + ret nc ; tails + ld a, 10 + call AddToDamage + ret + +Rampage_AIEffect: ; 2eb96 (b:6b96) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call AddToDamage + jp SetDefiniteAIDamage + +Rampage_Confusion50PercentEffect: ; 2eba1 (b:6ba1) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call AddToDamage + ldtx de, IfTailsYourPokemonBecomesConfusedText + call TossCoin_BankB + ret c ; heads + call SwapTurn + call ConfusionEffect + call SwapTurn + ret + +FuryAttack_AIEffect: ; 2ebba (b:6bba) + ld a, (10 * 2) / 2 + lb de, 0, 20 + jp SetExpectedAIDamage + +FuryAttack_MultiplierEffect: ; 2ebc2 (b:6bc2) + ld hl, 10 + call LoadTxRam3 + ld a, 2 + ldtx de, DamageCheckIfHeadsXDamageText + call TossCoinATimes_BankB + call ATimes10 + call SetDefiniteDamage + ret + +RetreatAidEffect: ; 2ebd7 (b:6bd7) + scf + ret + +DodrioRage_AIEffect: ; 2ebd9 (b:6bd9) + call DodrioRage_DamageBoostEffect + jp SetDefiniteAIDamage + +DodrioRage_DamageBoostEffect: ; 2ebdf (b:6bdf) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call AddToDamage + ret + +PayDayEffect: ; 2ebe8 (b:6be8) + ldtx de, IfHeadsDraw1CardFromDeckText + call TossCoin_BankB + ret nc ; tails + ldtx hl, Draw1CardFromTheDeckText + call DrawWideTextBox_WaitForInput + bank1call DisplayDrawOneCardScreen + call DrawCardFromDeck + ret c ; empty deck + call AddCardToHand + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wDuelistType] + cp DUELIST_TYPE_PLAYER + ret nz + ; show card on screen if it was Player + bank1call OpenCardPage_FromHand + ret + +DragonairSlam_AIEffect: ; 2ec0c (b:6c0c) + ld a, (30 * 2) / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +DragonairSlam_MultiplierEffect: ; 2ec14 (b:6c14) + ld hl, 30 + call LoadTxRam3 + ld a, 2 + ldtx de, DamageCheckIfHeadsXDamageText + call TossCoinATimes_BankB + ld e, a + add a + add e + call ATimes10 + call SetDefiniteDamage + ret + +DragonairHyperBeam_PlayerSelectEffect: ; 2ec2c (b:6c2c) + jp HandleEnergyDiscardEffectSelection + +DragonairHyperBeam_AISelectEffect: ; 2ec2f (b:6c2f) + call AIPickEnergyCardToDiscardFromDefendingPokemon + ldh [hTemp_ffa0], a + ret + +DragonairHyperBeam_DiscardEffect: ; 2ec35 (b:6c35) + call HandleNoDamageOrEffect + ret c ; is unaffected + ldh a, [hTemp_ffa0] + cp $ff + ret z ; no energy card chosen + call SwapTurn + call PutCardInDiscardPile + ld a, DUELVARS_ARENA_CARD_LAST_TURN_EFFECT + call GetTurnDuelistVariable + ld [hl], LAST_TURN_EFFECT_DISCARD_ENERGY + call SwapTurn + ret + +; handles screen for selecting an Energy card to discard +; that is attached to Defending Pokemon, +; and store the Player selection in [hTemp_ffa0]. +HandleEnergyDiscardEffectSelection: ; 2ec4f (b:6c4f) + call SwapTurn + xor a ; PLAY_AREA_ARENA + call CreateArenaOrBenchEnergyCardList + jr c, .no_energy + ldtx hl, ChooseDiscardEnergyCardFromOpponentText + call DrawWideTextBox_WaitForInput + xor a ; PLAY_AREA_ARENA + bank1call DisplayEnergyDiscardScreen + +.loop_input + bank1call HandleEnergyDiscardMenuInput + jr c, .loop_input + + call SwapTurn + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a ; store selected card to discard + ret + +.no_energy + call SwapTurn + ld a, $ff + ldh [hTemp_ffa0], a + ret + +; return carry if Defending Pokemon has no attacks +ClefableMetronome_CheckAttacks: ; 2ec77 (b:6c77) + call CheckIfDefendingPokemonHasAnyAttack + ldtx hl, NoAttackMayBeChoosenText + ret + +ClefableMetronome_AISelectEffect: ; 2ec7e (b:6c7e) + call HandleAIMetronomeEffect + ret + +ClefableMetronome_UseAttackEffect: ; 2ec82 (b:6c82) + ld a, 1 ; energy cost of this attack + call HandlePlayerMetronomeEffect + ret + +ClefableMinimizeEffect: ; 2ec88 (b:6c88) + ld a, SUBSTATUS1_REDUCE_BY_20 + call ApplySubstatus1ToDefendingCard + ret + +HurricaneEffect: ; 2ec8e (b:6c8e) + call HandleNoDamageOrEffect + ret c ; is unaffected + + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + or a + ret z ; return if Pokemon was KO'd + +; look at all the card locations and put all cards +; that are in the Arena in the hand. + call SwapTurn + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_locations + ld a, [hl] + cp CARD_LOCATION_ARENA + jr nz, .next_card + ; card in Arena found, put in hand + ld a, l + call AddCardToHand +.next_card + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_locations + +; empty the Arena card slot + ld l, DUELVARS_ARENA_CARD + ld a, [hl] + ld [hl], $ff + ld l, DUELVARS_ARENA_CARD_HP + ld [hl], 0 + call LoadCardDataToBuffer1_FromDeckIndex + ld hl, wLoadedCard1Name + ld a, [hli] + ld h, [hl] + ld l, a + call LoadTxRam2 + ldtx hl, PokemonAndAllAttachedCardsReturnedToHandText + call DrawWideTextBox_WaitForInput + xor a + ld [wDuelDisplayedScreen], a + call SwapTurn + ret + +PidgeottoWhirlwind_SelectEffect: ; 2ecd3 (b:6cd3) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + jr nc, .switch + ; no Bench Pokemon + ld a, $ff + ldh [hTemp_ffa0], a + ret +.switch + call DuelistSelectForcedSwitch + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret + +PidgeottoWhirlwind_SwitchEffect: ; 2ece9 (b:6ce9) + ldh a, [hTemp_ffa0] + call HandleSwitchDefendingPokemonEffect + ret + +PidgeottoMirrorMove_AIEffect: ; 2ecef (b:6cef) + jp MirrorMoveEffects.AIEffect + +PidgeottoMirrorMove_InitialEffect1: ; 2ecf2 (b:6cf2) + jp MirrorMoveEffects.InitialEffect1 + +PidgeottoMirrorMove_InitialEffect2: ; 2ecf5 (b:6cf5) + jp MirrorMoveEffects.InitialEffect2 + +PidgeottoMirrorMove_PlayerSelection: ; 2ecf8 (b:6cf8) + jp MirrorMoveEffects.PlayerSelection + +PidgeottoMirrorMove_AISelection: ; 2ecfb (b:6cfb) + jp MirrorMoveEffects.AISelection + +PidgeottoMirrorMove_BeforeDamage: ; 2ecfe (b:6cfe) + jp MirrorMoveEffects.BeforeDamage + +PidgeottoMirrorMove_AfterDamage: ; 2ed01 (b:6d01) + jp MirrorMoveEffects.AfterDamage + +SingEffect: ; 2ed04 (b:6d04) + call Sleep50PercentEffect + call nc, SetNoEffectFromStatus + ret + +; return carry if Defending Pokemon has no attacks +ClefairyMetronome_CheckAttacks: ; 2ed0b (b:6d0b) + call CheckIfDefendingPokemonHasAnyAttack + ldtx hl, NoAttackMayBeChoosenText + ret + +ClefairyMetronome_AISelectEffect: ; 2ed12 (b:6d12) + call HandleAIMetronomeEffect + ret + +ClefairyMetronome_UseAttackEffect: ; 2ed16 (b:6d16) + ld a, 3 ; energy cost of this attack +; fallthrough + +; handles Metronome selection, and validates +; whether it can use the selected attack. +; if unsuccessful, returns carry. +; input: +; a = amount of colorless energy needed for Metronome +HandlePlayerMetronomeEffect: ; 2ed18 (b:6d18) + ld [wMetronomeEnergyCost], a + ldtx hl, ChooseOppAttackToBeUsedWithMetronomeText + call DrawWideTextBox_WaitForInput + + call HandleDefendingPokemonAttackSelection + ret c ; return if operation cancelled + +; store this attack as selected attack to use + ld hl, wMetronomeSelectedAttack + ld [hl], d + inc hl + ld [hl], e + +; compare selected attack's name with +; the attack that is loaded, which is Metronome. +; if equal, then cannot select it. +; (i.e. cannot use Metronome with Metronome.) + ld hl, wLoadedAttackName + ld a, [hli] + ld h, [hl] + ld l, a + push hl + call SwapTurn + call CopyAttackDataAndDamage_FromDeckIndex + call SwapTurn + pop de + ld hl, wLoadedAttackName + ld a, e + cp [hl] + jr nz, .try_use + inc hl + ld a, d + cp [hl] + jr nz, .try_use + ; cannot select Metronome + ldtx hl, UnableToSelectText +.failed + call DrawWideTextBox_WaitForInput +.set_carry + scf + ret + +.try_use +; run the attack checks to determine +; whether it can be used. + ld a, EFFECTCMDTYPE_INITIAL_EFFECT_1 + call TryExecuteEffectCommandFunction + jr c, .failed + ld a, EFFECTCMDTYPE_INITIAL_EFFECT_2 + call TryExecuteEffectCommandFunction + jr c, .set_carry + ; successful + +; send data to link opponent + bank1call SendAttackDataToLinkOpponent + ld a, OPPACTION_USE_METRONOME_ATTACK + call SetOppAction_SerialSendDuelData + ld hl, wMetronomeSelectedAttack + ld d, [hl] + inc hl + ld e, [hl] + ld a, [wMetronomeEnergyCost] + ld c, a + call SerialSend8Bytes + + ldh a, [hTempCardIndex_ff9f] + ld [wPlayerAttackingCardIndex], a + ld a, [wSelectedAttack] + ld [wPlayerAttackingAttackIndex], a + ld a, [wTempCardID_ccc2] + ld [wPlayerAttackingCardID], a + or a + ret + +; does nothing for AI. +HandleAIMetronomeEffect: ; 2ed86 (b:6d86) + ret + +DoTheWaveEffect: ; 2ed87 (b:6d87) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + dec a ; don't count arena card + call ATimes10 + call AddToDamage + ret + +; return carry if no damage counters +FirstAid_DamageCheck: ; 2ed94 (b:6d94) + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ldtx hl, NoDamageCountersText + cp 10 + ret + +FirstAid_HealEffect: ; 2ed9f (b:6d9f) + lb de, 0, 10 + call ApplyAndAnimateHPRecovery + ret + +JigglypuffDoubleEdgeEffect: ; 2eda6 (b:6da6) + ld a, 20 + call DealRecoilDamageToSelf + ret + +PounceEffect: ; 2edac (b:6dac) + ld a, SUBSTATUS2_POUNCE + call ApplySubstatus2ToDefendingCard + ret + +LickitungSupersonicEffect: ; 2edb2 (b:6db2) + call Confusion50PercentEffect + call nc, SetNoEffectFromStatus + ret + +PidgeyWhirlwind_SelectEffect: ; 2edb9 (b:6db9) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + cp 2 + jr nc, .switch + ; no Bench Pokemon + ld a, $ff + ldh [hTemp_ffa0], a + ret +.switch + call DuelistSelectForcedSwitch + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret + +PidgeyWhirlwind_SwitchEffect: ; 2edcf (b:6dcf) + ldh a, [hTemp_ffa0] + call HandleSwitchDefendingPokemonEffect + ret + +; return carry if Defending card has no weakness +Conversion1_WeaknessCheck: ; 2edd5 (b:6dd5) + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + call SwapTurn + ld a, [wLoadedCard2Weakness] + or a + ret nz + ldtx hl, NoWeaknessText + scf + ret + +Conversion1_PlayerSelectEffect: ; 2eded (b:6ded) + ldtx hl, ChooseWeaknessYouWishToChangeText + xor a ; PLAY_AREA_ARENA + call HandleColorChangeScreen + ldh [hTemp_ffa0], a + ret + +Conversion1_AISelectEffect: ; 2edf7 (b:6df7) + call AISelectConversionColor + ret + +Conversion1_ChangeWeaknessEffect: ; 2edfb (b:6dfb) + call HandleNoDamageOrEffect + ret c ; is unaffected + +; apply changed weakness + ld a, DUELVARS_ARENA_CARD_CHANGED_WEAKNESS + call GetNonTurnDuelistVariable + ldh a, [hTemp_ffa0] + call TranslateColorToWR + ld [hl], a + ld l, DUELVARS_ARENA_CARD_LAST_TURN_CHANGE_WEAK + ld [hl], a + +; print text box + call SwapTurn + ldtx hl, ChangedTheWeaknessOfPokemonToColorText + call PrintArenaCardNameAndColorText + call SwapTurn + +; apply substatus + ld a, SUBSTATUS2_CONVERSION2 + call ApplySubstatus2ToDefendingCard + ret + +; returns carry if Active Pokemon has no Resistance. +Conversion2_ResistanceCheck: ; 2ee1f (b:6e1f) + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Resistance] + or a + ret nz + ldtx hl, NoResistanceText + scf + ret + +Conversion2_PlayerSelectEffect: ; 2ee31 (b:6e31) + ldtx hl, ChooseResistanceYouWishToChangeText + ld a, $80 + call HandleColorChangeScreen + ldh [hTemp_ffa0], a + ret + +Conversion2_AISelectEffect: ; 2ee3c (b:6e3c) +; AI will choose Defending Pokemon's color +; unless it is colorless. + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + call SwapTurn + ld a, [wLoadedCard1Type] + cp COLORLESS + jr z, .is_colorless + ldh [hTemp_ffa0], a + ret + +.is_colorless + call SwapTurn + call AISelectConversionColor + call SwapTurn + ret + +Conversion2_ChangeResistanceEffect: ; 2ee5e (b:6e5e) +; apply changed resistance + ld a, DUELVARS_ARENA_CARD_CHANGED_RESISTANCE + call GetTurnDuelistVariable + ldh a, [hTemp_ffa0] + call TranslateColorToWR + ld [hl], a + ldtx hl, ChangedTheResistanceOfPokemonToColorText +; fallthrough + +; prints text that requires card name and color, +; with the card name of the Turn Duelist's Arena Pokemon +; and color in [hTemp_ffa0]. +; input: +; hl = text to print +PrintArenaCardNameAndColorText: ; 2ee6c (b:6e6c) + push hl + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ldh a, [hTemp_ffa0] + call LoadCardNameAndInputColor + pop hl + call DrawWideTextBox_PrintText + ret + +; handles AI logic for selecting a new color +; for weakness/resistance. +; - if within the context of Conversion1, looks +; in own Bench for a non-colorless card that can attack. +; - if within the context of Conversion2, looks +; in Player's Bench for a non-colorless card that can attack. +AISelectConversionColor: ; 2ee7f (b:6e7f) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + jr .next_pkmn_atk + +; look for a non-colorless Bench Pokemon +; that has enough energy to use an attack. +.loop_atk + push de + call GetPlayAreaCardAttachedEnergies + ld a, e + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + cp COLORLESS + jr z, .skip_pkmn_atk ; skip colorless Pokemon + ld e, FIRST_ATTACK_OR_PKMN_POWER + bank1call _CheckIfEnoughEnergiesToAttack + jr nc, .found + ld e, SECOND_ATTACK + bank1call _CheckIfEnoughEnergiesToAttack + jr nc, .found +.skip_pkmn_atk + pop de +.next_pkmn_atk + inc e + dec d + jr nz, .loop_atk + +; none found in Bench. +; next, look for a non-colorless Bench Pokemon +; that has any Energy cards attached. + ld d, e ; number of Play Area Pokemon + ld e, PLAY_AREA_ARENA + jr .next_pkmn_energy + +.loop_energy + push de + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr z, .skip_pkmn_energy + ld a, e + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + cp COLORLESS + jr nz, .found +.skip_pkmn_energy + pop de +.next_pkmn_energy + inc e + dec d + jr nz, .loop_energy + +; otherwise, just select a random energy. + ld a, NUM_COLORED_TYPES + call Random + ldh [hTemp_ffa0], a + ret + +.found + pop de + ld a, [wLoadedCard1Type] + and TYPE_PKMN + ldh [hTemp_ffa0], a + ret + +ScrunchEffect: ; 2eee7 (b:6ee7) + ldtx de, IfHeadsNoDamageNextTurnText + call TossCoin_BankB + jp nc, SetWasUnsuccessful + ld a, ATK_ANIM_SCRUNCH + ld [wLoadedAttackAnimation], a + ld a, SUBSTATUS1_NO_DAMAGE_17 + call ApplySubstatus1ToDefendingCard + ret + +ChanseyDoubleEdgeEffect: ; 2eefb (b:6efb) + ld a, 80 + call DealRecoilDamageToSelf + ret + +SuperFang_AIEffect: ; 2ef01 (b:6f01) + call SuperFang_HalfHPEffect + jp SetDefiniteAIDamage + +SuperFang_HalfHPEffect: ; 2ef07 (b:6f07) + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + srl a + bit 0, a + jr z, .rounded + ; round up + add 5 +.rounded + call SetDefiniteDamage + ret + +; return carry if no Pokemon in Bench +TrainerCardAsPokemon_BenchCheck: ; 2ef18 (b:6f18) + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +TrainerCardAsPokemon_PlayerSelectSwitch: ; 2ef27 (b:6f27) + ldh a, [hTemp_ffa0] + or a + ret nz ; no need to switch if it's not Arena card + + ldtx hl, SelectPokemonToPlaceInTheArenaText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInBench + bank1call OpenPlayAreaScreenForSelection + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempPlayAreaLocation_ffa1], a + ret + +TrainerCardAsPokemon_DiscardEffect: ; 2ef3c (b:6f3c) + ldh a, [hTemp_ffa0] + ld e, a + call MovePlayAreaCardToDiscardPile + ldh a, [hTemp_ffa0] + or a + jr nz, .shift_cards + ldh a, [hTempPlayAreaLocation_ffa1] + ld e, a + call SwapArenaWithBenchPokemon +.shift_cards + call ShiftAllPokemonToFirstPlayAreaSlots + ret + +HealingWind_InitialEffect: ; 2ef51 (b:6f51) + scf + ret + +HealingWind_PlayAreaHealEffect: ; 2ef53 (b:6f53) +; play initial animation + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld c, $00 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation + ld a, ATK_ANIM_HEALING_WIND_PLAY_AREA + ld [wLoadedAttackAnimation], a + + + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA +.loop_play_area + push de + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + call GetCardDamageAndMaxHP + or a + jr z, .next_pkmn ; skip if no damage + +; if less than 20 damage, cap recovery at 10 damage + ld de, 20 + cp e + jr nc, .heal + ld e, a + +.heal +; add HP to this card + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + add e + ld [hl], a + +; play heal animation + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld c, $01 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation +.next_pkmn + pop de + inc e + dec d + jr nz, .loop_play_area + + ret + +Dragonite1Slam_AIEffect: ; 2ef9c (b:6f9c) + ld a, (30 * 2) / 2 + lb de, 0, 60 + jp SetExpectedAIDamage + +Dragonite1Slam_MultiplierEffect: ; 2efa4 (b:6fa4) + ld hl, 30 + call LoadTxRam3 + ldtx de, DamageCheckIfHeadsXDamageText + ld a, 2 + call TossCoinATimes_BankB + ld c, a + add a + add c + call ATimes10 + call SetDefiniteDamage + ret + +; possibly unreferenced +Func_2efbc: ; 2efbc (b:6fbc) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld l, DUELVARS_ARENA_CARD_HP + ld de, wce76 +.asm_2efc7 + ld a, [hli] + ld [de], a + inc de + dec c + jr nz, .asm_2efc7 + ret + +; possibly unreferenced +Func_2efce: ; 2efce (b:6fce) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld l, DUELVARS_ARENA_CARD_HP + ld de, wce76 +.asm_2efd9 + ld a, [de] + inc de + ld [hli], a + dec c + jr nz, .asm_2efd9 + ret + +CatPunchEffect: ; 2efe0 (b:6fe0) + call SwapTurn + call PickRandomPlayAreaCard + ld b, a + ld a, ATK_ANIM_CAT_PUNCH_PLAY_AREA + ld [wLoadedAttackAnimation], a + ld de, 20 + call DealDamageToPlayAreaPokemon + call SwapTurn + ret + +MorphEffect: ; 2eff6 (b:6ff6) + call ExchangeRNG + call .PickRandomBasicPokemonFromDeck + jr nc, .successful + ldtx hl, AttackUnsuccessfulText + call DrawWideTextBox_WaitForInput + ret + +.successful + ld a, DUELVARS_ARENA_CARD_STAGE + call GetTurnDuelistVariable + or a + jr z, .skip_discard_stage_below + +; if this is a stage 1 Pokemon (in case it's used +; by Clefable's Metronome attack) then first discard +; the lower stage card. + push hl + xor a + ldh [hTempPlayAreaLocation_ff9d], a + bank1call GetCardOneStageBelow + ld a, d + call PutCardInDiscardPile + pop hl + ld [hl], BASIC + +.skip_discard_stage_below +; overwrite card ID + ldh a, [hTempCardIndex_ff98] + call GetCardIDFromDeckIndex + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff98], a + call _GetCardIDFromDeckIndex + ld [hl], e + +; overwrite HP to new card's maximum HP + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + ld [hl], c + +; clear changed color and status + ld l, DUELVARS_ARENA_CARD_CHANGED_TYPE + ld [hl], $00 + call ClearAllStatusConditions + +; load both card's names for printing text + ld a, [wTempTurnDuelistCardID] + ld e, a + ld d, $00 + call LoadCardDataToBuffer2_FromCardID + ld hl, wLoadedCard2Name + ld de, wTxRam2 + ld a, [hli] + ld [de], a + inc de + ld a, [hl] + ld [de], a + inc de + ldh a, [hTempCardIndex_ff98] + call LoadCardDataToBuffer2_FromDeckIndex + ld hl, wLoadedCard2Name + ld a, [hli] + ld [de], a + inc de + ld a, [hl] + ld [de], a + ldtx hl, MetamorphsToText + call DrawWideTextBox_WaitForInput + + xor a + ld [wDuelDisplayedScreen], a + ret + +; picks a random Pokemon in the Deck to morph. +; needs to be a Basic Pokemon that doesn't have +; the same ID as the Arena card. +; returns carry if no Pokemon were found. +.PickRandomBasicPokemonFromDeck ; 2f06a (b:706a) + call CreateDeckCardList + ret c ; empty deck + ld hl, wDuelTempList + call ShuffleCards +.loop_deck + ld a, [hli] + ldh [hTempCardIndex_ff98], a + cp $ff + jr z, .set_carry + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .loop_deck ; skip non-Pokemon cards + ld a, [wLoadedCard2Stage] + or a + jr nz, .loop_deck ; skip non-Basic cards + ld a, [wLoadedCard2ID] + cp DUELVARS_ARENA_CARD + jr z, .loop_deck ; skip cards with same ID as Arena card + ldh a, [hTempCardIndex_ff98] + or a + ret +.set_carry + scf + ret + +; returns in a and [hTempCardIndex_ff98] the deck index +; of random Basic Pokemon card in deck. +; if none are found, return carry. +PickRandomBasicCardFromDeck: ; 2f098 (b:7098) + call CreateDeckCardList + ret c ; return if empty deck + ld hl, wDuelTempList + call ShuffleCards +.loop_deck + ld a, [hli] + ldh [hTempCardIndex_ff98], a + cp $ff + jr z, .set_carry + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .loop_deck ; skip if not Pokemon card + ld a, [wLoadedCard2Stage] + or a + jr nz, .loop_deck ; skip if not Basic stage + ldh a, [hTempCardIndex_ff98] + or a + ret +.set_carry + scf + ret + +SlicingWindEffect: ; 2f0bf (b:70bf) + call SwapTurn + call PickRandomPlayAreaCard + ld b, a + ld de, 30 + call DealDamageToPlayAreaPokemon_RegularAnim + call SwapTurn + ret + +Gale_LoadAnimation: ; 2f0d0 (b:70d0) + ld a, ATK_ANIM_GALE + ld [wLoadedAttackAnimation], a + ret + +Gale_SwitchEffect: ; 2f0d6 (b:70d6) +; if Defending card is unaffected by attack +; jump directly to switching this card only. + call HandleNoDamageOrEffect + jr c, .SwitchWithRandomBenchPokemon + +; handle switching Defending card + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + or a + jr nz, .skip_destiny_bond + bank1call HandleDestinyBondSubstatus +.skip_destiny_bond + call SwapTurn + call .SwitchWithRandomBenchPokemon + jr c, .skip_clear_damage +; clear dealt damage because Pokemon was switched + xor a + ld hl, wDealtDamage + ld [hli], a + ld [hl], a +.skip_clear_damage + call SwapTurn +; fallthrough for attacking card switch + +.SwitchWithRandomBenchPokemon + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + ret c ; return if no Bench Pokemon + +; get random Bench location and swap + dec a + call Random + inc a + ld e, a + call SwapArenaWithBenchPokemon + + xor a + ld [wDuelDisplayedScreen], a + ret + +; return carry if Bench is full +FriendshipSong_BenchCheck: ; 2f10d (b:710d) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +FriendshipSong_AddToBench50PercentEffect: ; 2f119 (b:7119) + ldtx de, SuccessCheckIfHeadsAttackIsSuccessfulText + call TossCoin_BankB + jr c, .successful + +.none_came_text + ldtx hl, NoneCameText + call DrawWideTextBox_WaitForInput + ret + +.successful + call PickRandomBasicCardFromDeck + jr nc, .put_in_bench + ld a, ATK_ANIM_FRIENDSHIP_SONG + call Func_2c12e + call .none_came_text + call Func_2c0bd + ret + +.put_in_bench + call SearchCardInDeckAndAddToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + ld a, ATK_ANIM_FRIENDSHIP_SONG + call Func_2c12e + ldh a, [hTempCardIndex_ff98] + ldtx hl, CameToTheBenchText + bank1call DisplayCardDetailScreen + call Func_2c0bd + ret + +ExpandEffect: ; 2f153 (b:7153) + ld a, SUBSTATUS1_REDUCE_BY_10 + call ApplySubstatus1ToDefendingCard + ret + +; returns carry if either there are no damage counters +; or no Energy cards attached in the Play Area. +SuperPotion_DamageEnergyCheck: ; 2f159 (b:7159) + call CheckIfPlayAreaHasAnyDamage + ldtx hl, NoPokemonWithDamageCountersText + ret c ; no damage counters + call CheckIfThereAreAnyEnergyCardsAttached + ldtx hl, ThereIsNoEnergyCardAttachedText + ret + +SuperPotion_PlayerSelectEffect: ; 2f167 (b:7167) + ldtx hl, ChoosePokemonToRemoveDamageCounterFromText + call DrawWideTextBox_WaitForInput +.start + bank1call HasAlivePokemonInPlayArea +.read_input + bank1call OpenPlayAreaScreenForSelection + ret c ; exit if B is pressed + ld e, a + call GetCardDamageAndMaxHP + or a + jr z, .read_input ; Pokemon has no damage? + ldh a, [hCurMenuItem] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr nz, .got_pkmn + ; no energy cards attached + ldtx hl, NoEnergyCardsText + call DrawWideTextBox_WaitForInput + jr .start + +.got_pkmn +; Pokemon has damage and Energy cards attached, +; prompt the Player for Energy selection to discard. + ldh a, [hCurMenuItem] + bank1call CreateArenaOrBenchEnergyCardList + ldh a, [hCurMenuItem] + bank1call DisplayEnergyDiscardScreen + bank1call HandleEnergyDiscardMenuInput + ret c ; exit if B was pressed + + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempPlayAreaLocation_ffa1], a + ld e, a + +; cap the healing damage if +; it would make it exceed max HP. + call GetCardDamageAndMaxHP + ld c, 40 + cp 40 + jr nc, .heal + ld c, a +.heal + ld a, c + ldh [hPlayAreaEffectTarget], a + or a + ret + +SuperPotion_HealEffect: ; 2f1b5 (b:71b5) + ldh a, [hTemp_ffa0] + call PutCardInDiscardPile + ldh a, [hTempPlayAreaLocation_ffa1] + ldh [hTempPlayAreaLocation_ff9d], a + ldh a, [hPlayAreaEffectTarget] + call HealPlayAreaCardHP + ret + +; checks if there is at least one Energy card +; attached to some card in the Turn Duelist's Play Area. +; return no carry if one is found, +; and returns carry set if none is found. +CheckIfThereAreAnyEnergyCardsAttached: ; 2f1c4 (b:71c4) + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_deck + ld a, [hl] + bit CARD_LOCATION_PLAY_AREA_F, a + jr z, .next_card ; skip if not in Play Area + ld a, l + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_TRAINER + jr z, .next_card ; skip if it's a Trainer card + cp TYPE_ENERGY + jr nc, .found +.next_card + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_deck + scf + ret +.found + or a + ret + +; handles Player selection for Pokemon in Play Area, +; then opens screen to choose one of the energy cards +; attached to that selected Pokemon. +; outputs the selection in: +; [hTemp_ffa0] = play area location +; [hTempPlayAreaLocation_ffa1] = index of energy card +HandlePokemonAndEnergySelectionScreen: ; 2f1e7 (b:71e7) + bank1call HasAlivePokemonInPlayArea + bank1call OpenPlayAreaScreenForSelection + ret c ; exit if B is pressed + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr nz, .has_energy + ldtx hl, NoEnergyCardsText + call DrawWideTextBox_WaitForInput + jr HandlePokemonAndEnergySelectionScreen ; loop back to start + +.has_energy + ldh a, [hCurMenuItem] + bank1call CreateArenaOrBenchEnergyCardList + ldh a, [hCurMenuItem] + bank1call DisplayEnergyDiscardScreen + bank1call HandleEnergyDiscardMenuInput + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ldh a, [hTempCardIndex_ff98] + ldh [hTempPlayAreaLocation_ffa1], a + ret + +ImakuniEffect: ; 2f216 (b:7216) + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1ID] + +; cannot confuse Clefairy Doll and Mysterious Fossil + cp CLEFAIRY_DOLL + jr z, .failed + cp MYSTERIOUS_FOSSIL + jr z, .failed + +; cannot confuse Snorlax if its Pkmn Power is active + cp SNORLAX + jr nz, .success + xor a + call CheckCannotUseDueToStatus_OnlyToxicGasIfANon0 + jr c, .success + ; fallthrough if Thick Skinned is active + +.failed +; play confusion animation and print failure text + ld a, ATK_ANIM_IMAKUNI_CONFUSION + call Func_2fea9 + ldtx hl, ThereWasNoEffectText + call DrawWideTextBox_WaitForInput + ret + +.success +; play confusion animation and confuse card + ld a, ATK_ANIM_IMAKUNI_CONFUSION + call Func_2fea9 + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and PSN_DBLPSN + or CONFUSED + ld [hl], a + bank1call DrawDuelHUDs + ret + +; returns carry if opponent has no energy cards attached +EnergyRemoval_EnergyCheck: ; 2f252 (b:7252) + call SwapTurn + call CheckIfThereAreAnyEnergyCardsAttached + ldtx hl, NoEnergyAttachedToOpponentsActiveText + call SwapTurn + ret + +EnergyRemoval_PlayerSelection: ; 2f25f (b:725f) + ldtx hl, ChoosePokemonToRemoveEnergyFromText + call DrawWideTextBox_WaitForInput + call SwapTurn + call HandlePokemonAndEnergySelectionScreen + call SwapTurn + ret + +EnergyRemoval_AISelection: ; 2f26f (b:726f) + call AIPickEnergyCardToDiscardFromDefendingPokemon + ret + +EnergyRemoval_DiscardEffect: ; 2f273 (b:7273) + call SwapTurn + ldh a, [hTempPlayAreaLocation_ffa1] + call PutCardInDiscardPile + call SwapTurn + call IsPlayerTurn + ret c + +; show Player which Pokemon was affected + call SwapTurn + ldh a, [hTemp_ffa0] + call Func_2c10b + call SwapTurn + ret + +; return carry if no other card in hand to discard +; or if there are no Basic Energy cards in Discard Pile. +EnergyRetrieval_HandEnergyCheck: ; 2f28e (b:728e) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + cp 2 + ldtx hl, NotEnoughCardsInHandText + ret c ; return if doesn't have another card to discard + call CreateEnergyCardListFromDiscardPile_OnlyBasic + ldtx hl, ThereAreNoBasicEnergyCardsInDiscardPileText + ret + +EnergyRetrieval_PlayerHandSelection: ; 2f2a0 (b:72a0) + ldtx hl, ChooseCardToDiscardFromHandText + call DrawWideTextBox_WaitForInput + call CreateHandCardList + ldh a, [hTempCardIndex_ff9f] + call RemoveCardFromDuelTempList + bank1call Func_5591 + bank1call DisplayCardList + ldh a, [hTempCardIndex_ff98] + ldh [hTempList], a + ret + +EnergyRetrieval_PlayerDiscardPileSelection: ; 2f2b9 (b:72b9) + ld a, 1 ; start at 1 due to card selected from hand + ldh [hCurSelectionItem], a + ldtx hl, Choose2BasicEnergyCardsFromDiscardPileText + call DrawWideTextBox_WaitForInput + call CreateEnergyCardListFromDiscardPile_OnlyBasic + +.select_card + bank1call InitAndDrawCardListScreenLayout + ldtx hl, PleaseSelectCardText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText + bank1call DisplayCardList + jr nc, .selected + ; B was pressed + ld a, 2 + 1 ; includes the card selected from hand + call AskWhetherToQuitSelectingCards + jr c, .select_card ; player selected No + jr .done + +.selected + call GetNextPositionInTempList_TrainerEffects + ldh a, [hTempCardIndex_ff98] + ld [hl], a + call RemoveCardFromDuelTempList + jr c, .done + ldh a, [hCurSelectionItem] + cp 2 + 1 ; includes the card selected from hand + jr c, .select_card + +.done + call GetNextPositionInTempList_TrainerEffects + ld [hl], $ff ; terminating byte + or a + ret + +EnergyRetrieval_DiscardAndAddToHandEffect: ; 2f2f8 (b:72f8) + ld hl, hTempList + ld a, [hli] + call RemoveCardFromHand + call PutCardInDiscardPile + ld de, wDuelTempList +.loop + ld a, [hli] + ld [de], a + inc de + cp $ff + jr z, .done + call MoveDiscardPileCardToHand + call AddCardToHand + jr .loop +.done + call IsPlayerTurn + ret c + bank1call Func_4b38 + ret + +; return carry if no cards left in Deck. +EnergySearch_DeckCheck: ; 2f31c (b:731c) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp DECK_SIZE + ccf + ldtx hl, NoCardsLeftInTheDeckText + ret + +EnergySearch_PlayerSelection: ; 2f328 (b:7328) + ld a, $ff + ldh [hTemp_ffa0], a + call CreateDeckCardList + ldtx hl, Choose1BasicEnergyCardFromDeckText + lb de, SEARCHEFFECT_BASIC_ENERGY, 0 + ldtx bc, BasicEnergyText + call LookForCardsInDeck + ret c ; skip showing deck + + bank1call Func_5591 + ldtx hl, ChooseBasicEnergyCardText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText +.read_input + bank1call DisplayCardList + jr c, .try_exit ; B pressed? + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + call CheckIfCardIsBasicEnergy + jr c, .play_sfx + or a + ret +.play_sfx + call Func_3794 + jr .read_input + +.try_exit +; check if Player can exit without selecting anything + ld hl, wDuelTempList +.next_card + ld a, [hli] + cp $ff + jr z, .exit + call CheckIfCardIsBasicEnergy + jr c, .next_card + jr .read_input ; no, has to select Energy card +.exit + ld a, $ff + ldh [hTemp_ffa0], a + or a + ret + +EnergySearch_AddToHandEffect: ; 2f372 (b:7372) + ldh a, [hTemp_ffa0] + cp $ff + jr z, .done +; add to hand + call SearchCardInDeckAndAddToHand + call AddCardToHand + call IsPlayerTurn + jr c, .done ; done if Player played card +; display card in screen + ldh a, [hTemp_ffa0] + ldtx hl, WasPlacedInTheHandText + bank1call DisplayCardDetailScreen +.done + call Func_2c0bd + ret + +; check if card index in a is a Basic Energy card. +; returns carry in case it's not. +CheckIfCardIsBasicEnergy: ; 2f38f (b:738f) + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr c, .not_basic_energy + cp TYPE_ENERGY_DOUBLE_COLORLESS + jr nc, .not_basic_energy +; is basic energy + or a + ret +.not_basic_energy + scf + ret + +ProfessorOakEffect: ; 2f3a1 (b:73a1) +; discard hand + call CreateHandCardList + call SortCardsInDuelTempListByID + ld hl, wDuelTempList +.discard_loop + ld a, [hli] + cp $ff + jr z, .draw_cards + call RemoveCardFromHand + call PutCardInDiscardPile + jr .discard_loop + +.draw_cards + ld a, 7 + bank1call DisplayDrawNCardsScreen + ld c, 7 +.draw_loop + call DrawCardFromDeck + jr c, .done + call AddCardToHand + dec c + jr nz, .draw_loop +.done + ret + +Potion_DamageCheck: ; 2f3ca (b:73ca) + call CheckIfPlayAreaHasAnyDamage + ldtx hl, NoPokemonWithDamageCountersText + ret + +Potion_PlayerSelection: ; 2f3d1 (b:73d1) + bank1call HasAlivePokemonInPlayArea +.read_input + bank1call OpenPlayAreaScreenForSelection + ret c ; exit is B was pressed + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ld e, a + call GetCardDamageAndMaxHP + or a + jr z, .read_input ; no damage, loop back to start +; cap damage + ld c, 20 + cp 20 + jr nc, .skip_cap + ld c, a +.skip_cap + ld a, c + ldh [hTempPlayAreaLocation_ffa1], a + or a + ret + +Potion_HealEffect: ; 2f3ef (b:73ef) + ldh a, [hTemp_ffa0] + ldh [hTempPlayAreaLocation_ff9d], a + ldh a, [hTempPlayAreaLocation_ffa1] + call HealPlayAreaCardHP + ret + +GamblerEffect: ; 2f3f9 (b:73f9) + ldtx de, CardCheckIfHeads8CardsIfTails1CardText + call TossCoin_BankB + ldh [hTemp_ffa0], a +; discard Gambler card from hand + ldh a, [hTempCardIndex_ff9f] + call RemoveCardFromHand + call PutCardInDiscardPile + +; shuffle cards into deck + call CreateHandCardList + call SortCardsInDuelTempListByID + ld hl, wDuelTempList +.loop_return_deck + ld a, [hli] + cp $ff + jr z, .check_coin_toss + call RemoveCardFromHand + call ReturnCardToDeck + jr .loop_return_deck + +.check_coin_toss + call Func_2c0bd + ld c, 8 + ldh a, [hTemp_ffa0] + or a + jr nz, .draw_cards ; coin toss was heads? + ; if tails, number of cards to draw is 1 + ld c, 1 + +; correct number of cards to draw is in c +.draw_cards + ld a, c + bank1call DisplayDrawNCardsScreen +.draw_loop + call DrawCardFromDeck + jr c, .done + call AddCardToHand + dec c + jr nz, .draw_loop +.done + ret + +; return carry if not enough cards in hand to discard +; or if there are no cards in the Discard Pile +ItemFinder_HandDiscardPileCheck: ; 2f43b (b:743b) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ldtx hl, NotEnoughCardsInHandText + cp 3 + ret c + call CreateTrainerCardListFromDiscardPile + ret + +ItemFinder_PlayerSelection: ; 2f44a (b:744a) + call HandlePlayerSelection2HandCardsToDiscard + ret c ; was operation cancelled? + +; cards were selected to discard from hand. +; now to choose a Trainer card from Discard Pile. + call CreateTrainerCardListFromDiscardPile + bank1call Func_5591 + ldtx hl, ChooseCardToPlaceInHandText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText + bank1call DisplayCardList + ldh [hTempList + 2], a ; placed after the 2 cards selected to discard + ret + +ItemFinder_DiscardAddToHandEffect: ; 2f463 (b:7463) +; discard cards from hand + ld hl, hTempList + ld a, [hli] + call RemoveCardFromHand + call PutCardInDiscardPile + ld a, [hli] + call RemoveCardFromHand + call PutCardInDiscardPile + +; place card from Discard Pile to hand + ld a, [hl] + call MoveDiscardPileCardToHand + call AddCardToHand + call IsPlayerTurn + ret c +; display card in screen + ldh a, [hTempList + 2] + ldtx hl, WasPlacedInTheHandText + bank1call DisplayCardDetailScreen + ret + +Defender_PlayerSelection: ; 2f488 (b:7488) + ldtx hl, ChoosePokemonToAttachDefenderToText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInPlayArea + bank1call OpenPlayAreaScreenForSelection + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret + +Defender_AttachDefenderEffect: ; 2f499 (b:7499) +; attach Trainer card to Play Area Pokemon + ldh a, [hTemp_ffa0] + ld e, a + ldh a, [hTempCardIndex_ff9f] + call PutHandCardInPlayArea + +; increase number of Defender cards of this location by 1 + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD_ATTACHED_DEFENDER + call GetTurnDuelistVariable + inc [hl] + call IsPlayerTurn + ret c + + ldh a, [hTemp_ffa0] + call Func_2c10b + ret + +; return carry if Bench is full. +MysteriousFossil_BenchCheck: ; 2f4b3 (b:74b3) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp MAX_PLAY_AREA_POKEMON + ccf + ldtx hl, NoSpaceOnTheBenchText + ret + +MysteriousFossil_PlaceInPlayAreaEffect: ; 2f4bf (b:74bf) + ldh a, [hTempCardIndex_ff9f] + call PutHandPokemonCardInPlayArea + ret + +; return carry if Arena card has no status to heal. +FullHeal_StatusCheck: ; 2f4c5 (b:74c5) + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + or a + ret nz + ldtx hl, NotAffectedByPoisonSleepParalysisOrConfusionText + scf + ret + +FullHeal_ClearStatusEffect: ; 2f4d1 (b:74d1) + ld a, ATK_ANIM_FULL_HEAL + call Func_2fea9 + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + ld [hl], NO_STATUS + bank1call DrawDuelHUDs + ret + +ImposterProfessorOakEffect: ; 2f4e1 (b:74e1) + call SwapTurn + call CreateHandCardList + call SortCardsInDuelTempListByID + +; first return all cards in hand to the deck. + ld hl, wDuelTempList +.loop_return_deck + ld a, [hli] + cp $ff + jr z, .done_return + call RemoveCardFromHand + call ReturnCardToDeck + jr .loop_return_deck + +; then draw 7 cards from the deck. +.done_return + call Func_2c0bd + ld a, 7 + bank1call DisplayDrawNCardsScreen + ld c, 7 +.loop_draw + call DrawCardFromDeck + jr c, .done + call AddCardToHand + dec c + jr nz, .loop_draw +.done + call SwapTurn + ret + +; return carry if not enough cards in hand to discard +; or if there are no cards left in the deck. +ComputerSearch_HandDeckCheck: ; 2f513 (b:7513) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ldtx hl, NotEnoughCardsInHandText + cp 3 + ret c + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ldtx hl, NoCardsLeftInTheDeckText + cp DECK_SIZE + ccf + ret + +ComputerSearch_PlayerDiscardHandSelection: ; 2f52a (b:752a) + call HandlePlayerSelection2HandCardsToDiscard + ret + +ComputerSearch_PlayerDeckSelection: ; 2f52e (b:752e) + call CreateDeckCardList + bank1call Func_5591 + ldtx hl, ChooseCardToPlaceInHandText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText +.loop_input + bank1call DisplayCardList + jr c, .loop_input ; can't exit with B button + ldh [hTempList + 2], a + ret + +ComputerSearch_DiscardAddToHandEffect: ; 2f545 (b:7545) +; discard cards from hand + ld hl, hTempList + ld a, [hli] + call RemoveCardFromHand + call PutCardInDiscardPile + ld a, [hli] + call RemoveCardFromHand + call PutCardInDiscardPile + +; add card from deck to hand + ld a, [hl] + call SearchCardInDeckAndAddToHand + call AddCardToHand + call Func_2c0bd + ret + +; return carry if Bench is full. +ClefairyDoll_BenchCheck: ; 2f561 (b:7561) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret + +ClefairyDoll_PlaceInPlayAreaEffect: ; 2f56d (b:756d) + ldh a, [hTempCardIndex_ff9f] + call PutHandPokemonCardInPlayArea + ret + +; return carry if no Pokemon in the Bench. +MrFuji_BenchCheck: ; 2f573 (b:7573) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +MrFuji_PlayerSelection: ; 2f57e (b:757e) + ldtx hl, ChoosePokemonToReturnToTheDeckText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInBench + bank1call OpenPlayAreaScreenForSelection + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret + +MrFuji_ReturnToDeckEffect: ; 2f58f (b:758f) +; get Play Area location's card index + ldh a, [hTemp_ffa0] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff98], a + +; find all cards that are in the same location +; (previous evolutions and energy cards attached) +; and return them all to the deck. + ldh a, [hTemp_ffa0] + or CARD_LOCATION_PLAY_AREA + ld e, a + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop_cards + push de + push hl + ld a, [hl] + cp e + jr nz, .next_card + ld a, l + call ReturnCardToDeck +.next_card + pop hl + pop de + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_cards + +; clear Play Area location of card + ldh a, [hTemp_ffa0] + ld e, a + call EmptyPlayAreaSlot + ld l, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + dec [hl] + call ShiftAllPokemonToFirstPlayAreaSlots + +; if Trainer card wasn't played by the Player, +; print the selected Pokemon's name and show card on screen. + call IsPlayerTurn + jr c, .done + ldh a, [hTempCardIndex_ff98] + call LoadCardDataToBuffer1_FromDeckIndex + ld hl, wLoadedCard1Name + ld a, [hli] + ld h, [hl] + ld l, a + call LoadTxRam2 + bank1call DrawLargePictureOfCard + ldtx hl, PokemonAndAllAttachedCardsWereReturnedToDeckText + call DrawWideTextBox_WaitForInput +.done + call Func_2c0bd + ret + +PlusPowerEffect: ; 2f5e0 (b:75e0) +; attach Trainer card to Arena Pokemon + ld e, PLAY_AREA_ARENA + ldh a, [hTempCardIndex_ff9f] + call PutHandCardInPlayArea + +; increase number of Defender cards of this location by 1 + ld a, DUELVARS_ARENA_CARD_ATTACHED_PLUSPOWER + call GetTurnDuelistVariable + inc [hl] + ret + +; return carry if no Pokemon in the Bench. +Switch_BenchCheck: ; 2f5ee (b:75ee) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +Switch_PlayerSelection: ; 2f5f9 (b:75f9) + ldtx hl, SelectPkmnOnBenchToSwitchWithActiveText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInBench + bank1call OpenPlayAreaScreenForSelection + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + ret + +Switch_SwitchEffect: ; 2f60a (b:760a) + ldh a, [hTemp_ffa0] + ld e, a + call SwapArenaWithBenchPokemon + ret + +PokemonCenter_DamageCheck: ; 2f611 (b:7611) + call CheckIfPlayAreaHasAnyDamage + ldtx hl, NoPokemonWithDamageCountersText + ret + +PokemonCenter_HealDiscardEnergyEffect: ; 2f618 (b:7618) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld d, a + ld e, PLAY_AREA_ARENA + +; go through every Pokemon in the Play Area +; and heal all damage & discard their Energy cards. +.loop_play_area +; check its damage + ld a, e + ldh [hTempPlayAreaLocation_ff9d], a + call GetCardDamageAndMaxHP + or a + jr z, .next_pkmn ; if no damage, skip Pokemon + +; heal all its damage + push de + ld e, a + ld d, $00 + call HealPlayAreaCardHP + +; loop all cards in deck and for all the Energy cards +; that are attached to this Play Area location Pokemon, +; place them in the Discard Pile. + ldh a, [hTempPlayAreaLocation_ff9d] + or CARD_LOCATION_PLAY_AREA + ld e, a + ld a, $00 + call GetTurnDuelistVariable +.loop_deck + ld a, [hl] + cp e + jr nz, .next_card_deck ; not attached to card, skip + ld a, l + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + and TYPE_ENERGY + jr z, .next_card_deck ; not Energy, skip + ld a, l + call PutCardInDiscardPile +.next_card_deck + inc l + ld a, l + cp DECK_SIZE + jr c, .loop_deck + + pop de +.next_pkmn + inc e + dec d + jr nz, .loop_play_area + ret + +; return carry if non-Turn Duelist has full Bench +; or if they have no Basic Pokemon cards in Discard Pile. +PokemonFlute_BenchCheck: ; 2f659 (b:7659) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret c ; not enough space in Bench + ; check Discard Pile + call SwapTurn + call CreateBasicPokemonCardListFromDiscardPile + ldtx hl, ThereAreNoPokemonInDiscardPileText + call SwapTurn + ret + +PokemonFlute_PlayerSelection: ; 2f672 (b:7672) +; create Discard Pile list + call SwapTurn + call CreateBasicPokemonCardListFromDiscardPile + +; display selection screen and store Player's selection + bank1call Func_5591 + ldtx hl, ChoosePokemonToPlaceInPlayText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText + bank1call DisplayCardList + call SwapTurn + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + ret + +PokemonFlute_PlaceInPlayAreaText: ; 2f68f (b:768f) +; place selected card in non-Turn Duelist's Bench + call SwapTurn + ldh a, [hTemp_ffa0] + call MoveDiscardPileCardToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + call SwapTurn + +; unless it was the Player who played the card, +; display the Pokemon card on screen. + call IsPlayerTurn + ret c + call SwapTurn + ldh a, [hTemp_ffa0] + ldtx hl, CardWasChosenText + bank1call DisplayCardDetailScreen + call SwapTurn + ret + +PokemonBreeder_HandPlayAreaCheck: ; 2f6b3 (b:76b3) + call CreatePlayableStage2PokemonCardListFromHand + jr c, .cannot_evolve + bank1call IsPrehistoricPowerActive + ret +.cannot_evolve + ldtx hl, ConditionsForEvolvingToStage2NotFulfilledText + scf + ret + +PokemonBreeder_PlayerSelection: ; 2f6c1 (b:76c1) +; create hand list of playable Stage2 cards + call CreatePlayableStage2PokemonCardListFromHand + bank1call Func_5591 + +; handle Player selection of Stage2 card + ldtx hl, PleaseSelectCardText + ldtx de, DuelistHandText + bank1call SetCardListHeaderText + bank1call DisplayCardList + ret c ; exit if B was pressed + + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + ldtx hl, ChooseBasicPokemonToEvolveText + call DrawWideTextBox_WaitForInput + +; handle Player selection of Basic card to evolve + bank1call HasAlivePokemonInPlayArea +.read_input + bank1call OpenPlayAreaScreenForSelection + ret c ; exit if B was pressed + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempPlayAreaLocation_ffa1], a + ld e, a + ldh a, [hTemp_ffa0] + ld d, a + call CheckIfCanEvolveInto_BasicToStage2 + jr c, .read_input ; loop back if cannot evolve this card + or a + ret + +PokemonBreeder_EvolveEffect: ; 2f6f4 (b:76f4) + ldh a, [hTempCardIndex_ff9f] + push af + ld hl, hTemp_ffa0 + ld a, [hli] + ldh [hTempCardIndex_ff98], a + ld a, [hl] ; hTempPlayAreaLocation_ffa1 + ldh [hTempPlayAreaLocation_ff9d], a + +; load the Basic Pokemon card name to RAM + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld hl, wLoadedCard1Name + ld a, [hli] + ld h, [hl] + ld l, a + call LoadTxRam2 + +; evolve card and overwrite its stage as STAGE2_WITHOUT_STAGE1 + ldh a, [hTempCardIndex_ff98] + call EvolvePokemonCard + ld [hl], STAGE2_WITHOUT_STAGE1 + +; load Stage2 Pokemon card name to RAM + ldh a, [hTempCardIndex_ff98] + call LoadCardDataToBuffer1_FromDeckIndex + ld a, 18 + call CopyCardNameAndLevel + xor a + ld [hl], a ; $0 character + ld hl, wTxRam2_b + ld [hli], a + ld [hl], a + +; display Pokemon picture and play sfx, +; print the corresponding card names. + bank1call DrawLargePictureOfCard + ld a, $5e + call PlaySFX + ldtx hl, PokemonEvolvedIntoPokemonText + call DrawWideTextBox_WaitForInput + bank1call Func_161e + pop af + ldh [hTempCardIndex_ff9f], a + ret + +; creates list in wDuelTempList of all Stage2 Pokemon cards +; in the hand that can evolve a Basic Pokemon card in Play Area +; through use of Pokemon Breeder. +; returns carry if that list is empty. +CreatePlayableStage2PokemonCardListFromHand: ; 2f73e (b:773e) + call CreateHandCardList + ret c ; return if no hand cards + +; check if hand Stage2 Pokemon cards can be made +; to evolve a Basic Pokemon in the Play Area and, if so, +; add it to the wDuelTempList. + ld hl, wDuelTempList + ld e, l + ld d, h +.loop_hand + ld a, [hl] + cp $ff + jr z, .done + call .CheckIfCanEvolveAnyPlayAreaBasicCard + jr c, .next_hand_card + ld a, [hl] + ld [de], a + inc de +.next_hand_card + inc hl + jr .loop_hand + +.done + ld a, $ff ; terminating byte + ld [de], a + ld a, [wDuelTempList] + cp $ff + scf + ret z ; return carry if empty + ; not empty + or a + ret + +; return carry if Stage2 card in a cannot evolve any +; of the Basic Pokemon in Play Area through Pokemon Breeder. +.CheckIfCanEvolveAnyPlayAreaBasicCard + push de + ld d, a + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .set_carry ; skip if not Pokemon card + ld a, [wLoadedCard2Stage] + cp STAGE2 + jr nz, .set_carry ; skip if not Stage2 + +; check if can evolve any Play Area cards + push hl + push bc + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld e, PLAY_AREA_ARENA +.loop_play_area + push bc + push de + call CheckIfCanEvolveInto_BasicToStage2 + pop de + pop bc + jr nc, .done_play_area + inc e + dec c + jr nz, .loop_play_area +; set carry + scf +.done_play_area + pop bc + pop hl + pop de + ret +.set_carry + pop de + scf + ret + +; return carry if no cards in the Bench. +ScoopUp_BenchCheck: ; 2f795 (b:7795) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +ScoopUp_PlayerSelection: ; 2f7a0 (b:77a0) +; print text box + ldtx hl, ChoosePokemonToScoopUpText + call DrawWideTextBox_WaitForInput + +; handle Player selection + bank1call HasAlivePokemonInPlayArea + bank1call OpenPlayAreaScreenForSelection + ret c ; exit if B was pressed + + ldh [hTemp_ffa0], a + or a + ret nz ; if it wasn't the Active Pokemon, we are done + +; handle switching to a Pokemon in Bench and store location selected. + call EmptyScreen + ldtx hl, SelectPokemonToPlaceInTheArenaText + call DrawWideTextBox_WaitForInput + bank1call HasAlivePokemonInBench + bank1call OpenPlayAreaScreenForSelection + ldh [hTempPlayAreaLocation_ffa1], a + ret + +ScoopUp_ReturnToHandEffect: ; 2f7c3 (b:77c3) +; store chosen card location to Scoop Up + ldh a, [hTemp_ffa0] + or CARD_LOCATION_PLAY_AREA + ld e, a + +; find Basic Pokemon card that is in the selected Play Area location +; and add it to the hand, discarding all cards attached. + ld a, DUELVARS_CARD_LOCATIONS + call GetTurnDuelistVariable +.loop + ld a, [hl] + cp e + jr nz, .next_card ; skip if not in selected location + ld a, l + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .next_card ; skip if not Pokemon card + ld a, [wLoadedCard2Stage] + or a + jr nz, .next_card ; skip if not Basic stage +; found + ld a, l + ldh [hTempCardIndex_ff98], a + call AddCardToHand + ; optimization: break loop here, since + ; no two Basic Pokemon cards may occupy + ; the same Play Area location. +.next_card + inc l + ld a, l + cp DECK_SIZE + jr c, .loop + +; since the card has been moved to hand, +; MovePlayAreaCardToDiscardPile will take care +; of discarding every higher stage cards and other cards attached. + ldh a, [hTemp_ffa0] + ld e, a + call MovePlayAreaCardToDiscardPile + +; if the Pokemon was in the Arena, clear status + ldh a, [hTemp_ffa0] + or a + jr nz, .skip_clear_status + call ClearAllStatusConditions +.skip_clear_status + +; if card was not played by Player, show detail screen +; and print corresponding text. + call IsPlayerTurn + jr c, .shift_or_switch + ldtx hl, PokemonWasReturnedFromArenaToHandText + ldh a, [hTemp_ffa0] + or a + jr z, .display_detail_screen + ldtx hl, PokemonWasReturnedFromBenchToHandText +.display_detail_screen + ldh a, [hTempCardIndex_ff98] + bank1call DisplayCardDetailScreen + +.shift_or_switch +; if card was in Bench, simply shift Pokemon slots... + ldh a, [hTemp_ffa0] + or a + jr z, .switch + call ShiftAllPokemonToFirstPlayAreaSlots + ret + +.switch +; ...if Pokemon was in Arena, then switch it with the selected Bench card. + ldh a, [hTempPlayAreaLocation_ffa1] + ld d, a + ld e, PLAY_AREA_ARENA + call SwapPlayAreaPokemon + call ShiftAllPokemonToFirstPlayAreaSlots + ret + +; return carry if no other cards in hand, +; or if there are no Pokemon cards in hand. +PokemonTrader_HandDeckCheck: ; 2f826 (b:7826) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ldtx hl, ThereAreNoCardsInHandThatYouCanChangeText + cp 2 + ret c ; return if no other cards in hand + call CreatePokemonCardListFromHand + ldtx hl, ThereAreNoCardsInHandThatYouCanChangeText + ret + +PokemonTrader_PlayerHandSelection: ; 2f838 (b:7838) +; print text box + ldtx hl, ChooseCardFromYourHandToSwitchText + call DrawWideTextBox_WaitForInput + +; create list with all Pokemon cards in hand + call CreatePokemonCardListFromHand + bank1call Func_5591 + +; handle Player selection + ldtx hl, ChooseCardToSwitchText + ldtx de, DuelistHandText + bank1call SetCardListHeaderText + bank1call DisplayCardList + ldh [hTemp_ffa0], a + ret + +PokemonTrader_PlayerDeckSelection: ; 2f853 (b:7853) +; temporarily place chosen hand card in deck +; so it can be potentially chosen to be traded. + ldh a, [hTemp_ffa0] + call RemoveCardFromHand + call ReturnCardToDeck + +; display deck card list screen + ldtx hl, ChooseBasicOrEvolutionPokemonCardFromDeckText + call DrawWideTextBox_WaitForInput + call CreateDeckCardList + bank1call Func_5591 + ldtx hl, ChoosePokemonCardText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + +; handle Player selection +.read_input + bank1call DisplayCardList + jr c, .read_input ; pressing B loops back to selection + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .read_input ; can't select non-Pokemon cards + +; a valid card was selected, store its card index and +; place the selected hand card back to the hand. + ldh a, [hTempCardIndex_ff98] + ldh [hTempPlayAreaLocation_ffa1], a + ldh a, [hTemp_ffa0] + call SearchCardInDeckAndAddToHand + call AddCardToHand + or a + ret + +PokemonTrader_TradeCardsEffect: ; 2f88d (b:788d) +; place hand card in deck + ldh a, [hTemp_ffa0] + call RemoveCardFromHand + call ReturnCardToDeck + +; place deck card in hand + ldh a, [hTempPlayAreaLocation_ffa1] + call SearchCardInDeckAndAddToHand + call AddCardToHand + +; display cards if the Pokemon Trader wasn't played by Player + call IsPlayerTurn + jr c, .done + ldh a, [hTemp_ffa0] + ldtx hl, PokemonWasReturnedToDeckText + bank1call DisplayCardDetailScreen + ldh a, [hTempPlayAreaLocation_ffa1] + ldtx hl, WasPlacedInTheHandText + bank1call DisplayCardDetailScreen +.done + call Func_2c0bd + ret + +; makes list in wDuelTempList with all Pokemon cards +; that are in Turn Duelist's hand. +; if list turns out empty, return carry. +CreatePokemonCardListFromHand: ; 2f8b6 (b:78b6) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ld c, a + ld l, DUELVARS_HAND + ld de, wDuelTempList +.loop + ld a, [hl] + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .next_hand_card + ld a, [hl] + ld [de], a + inc de +.next_hand_card + inc l + dec c + jr nz, .loop + ld a, $ff ; terminating byte + ld [de], a + ld a, [wDuelTempList] + cp $ff + jr z, .set_carry + or a + ret +.set_carry + scf + ret + +; return carry if no cards in deck +Pokedex_DeckCheck: ; 2f8e1 (b:78e1) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ldtx hl, NoCardsLeftInTheDeckText + cp DECK_SIZE + ccf + ret + +Pokedex_PlayerSelection: ; 2f8ed (b:78ed) +; print text box + ldtx hl, RearrangeThe5CardsAtTopOfDeckText + call DrawWideTextBox_WaitForInput + +; cap the number of cards to reorder up to +; number of cards left in the deck (maximum of 5) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ld b, a + ld a, DECK_SIZE + sub [hl] + ld c, 5 + cp c + jr nc, .no_cap + ld c, a +.no_cap + +; fill wDuelTempList with cards that are going to be sorted + ld a, c + inc a + ld [wNumberOfCardsToOrder], a + ld a, b + add DUELVARS_DECK_CARDS + ld l, a + ld de, wDuelTempList +.loop_cards_to_order + ld a, [hli] + ld [de], a + inc de + dec c + jr nz, .loop_cards_to_order + ld a, $ff ; terminating byte + ld [de], a + +.clear_list +; wDuelTempList + 10 will be filled with numbers from 1 +; to 5 (or whatever the maximum order card is). +; so that the first item in that list corresponds to the first card +; the second item corresponds to the second card, etc. +; and the number in the list corresponds to the ordering number. + call CountCardsInDuelTempList + ld b, a + ld a, 1 +; fill order list with zeroes + ldh [hCurSelectionItem], a + ld hl, wDuelTempList + 10 + xor a +.loop_init + ld [hli], a + dec b + jr nz, .loop_init + ld [hl], $ff ; terminating byte + +; display card list to order + bank1call InitAndDrawCardListScreenLayout + ldtx hl, ChooseTheOrderOfTheCardsText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText + bank1call Func_5735 + +.read_input + bank1call DisplayCardList + jr c, .undo ; if B is pressed, undo last order selection + +; a card was selected, check if it's already been selected + ldh a, [hCurMenuItem] + ld e, a + ld d, $00 + ld hl, wDuelTempList + 10 + add hl, de + ld a, [hl] + or a + jr nz, .read_input ; already has an ordering number + +; hasn't been ordered yet, apply to it current ordering number +; and increase it by 1. + ldh a, [hCurSelectionItem] + ld [hl], a + inc a + ldh [hCurSelectionItem], a + +; refresh screen + push af + bank1call Func_5744 + pop af + +; check if we're done ordering + ldh a, [hCurSelectionItem] + ld hl, wNumberOfCardsToOrder + cp [hl] + jr c, .read_input ; if still more cards to select, loop back up + +; we're done selecting cards + call EraseCursor + ldtx hl, IsThisOKText + call YesOrNoMenuWithText_LeftAligned + jr c, .clear_list ; "No" was selected, start over + ; selection was confirmed + +; now wDuelTempList + 10 will be overwritten with the +; card indices in order of selection. + ld hl, wDuelTempList + 10 + ld de, wDuelTempList + ld c, 0 +.loop_write_indices + ld a, [hli] + cp $ff + jr z, .done_write_indices + push hl + push bc + ld c, a + ld b, $00 + ld hl, hTempCardIndex_ff9f + add hl, bc + ld a, [de] + ld [hl], a + pop bc + pop hl + inc de + inc c + jr .loop_write_indices + +.done_write_indices + ld b, $00 + ld hl, hTempList + add hl, bc + ld [hl], $ff ; terminating byte + or a + ret + +.undo +; undo last selection and get previous order number + ld hl, hCurSelectionItem + ld a, [hl] + cp 1 + jr z, .read_input ; already at first input, nothing to undo + dec a + ld [hl], a + ld c, a + ld hl, wDuelTempList + 10 +.asm_2f99e + ld a, [hli] + cp c + jr nz, .asm_2f99e + dec hl + ld [hl], $00 ; overwrite order number with 0 + bank1call Func_5744 + jr .read_input + +Pokedex_OrderDeckCardsEffect: ; 2f9aa (b:79aa) +; place cards in order to the hand. + ld hl, hTempList + ld c, 0 +.loop_place_hand + ld a, [hli] + cp $ff + jr z, .place_top_deck + call SearchCardInDeckAndAddToHand + inc c + jr .loop_place_hand + +.place_top_deck +; go to last card in list and iterate in decreasing order +; placing each card in top of deck. + dec hl + dec hl +.loop_place_deck + ld a, [hld] + call ReturnCardToDeck + dec c + jr nz, .loop_place_deck + ret + +BillEffect: ; 2f9c4 (b:79c4) + ld a, 2 + bank1call DisplayDrawNCardsScreen + ld c, 2 +.loop_draw + call DrawCardFromDeck + jr c, .done + ldh [hTempCardIndex_ff98], a + call AddCardToHand + call IsPlayerTurn + jr nc, .skip_display_screen + push bc + bank1call DisplayPlayerDrawCardScreen + pop bc +.skip_display_screen + dec c + jr nz, .loop_draw +.done + ret + +LassEffect: ; 2f9e3 (b:79e3) +; first discard Lass card that was used + ldh a, [hTempCardIndex_ff9f] + call RemoveCardFromHand + call PutCardInDiscardPile + + ldtx hl, PleaseCheckTheOpponentsHandText + call DrawWideTextBox_WaitForInput + + call .DisplayLinkOrCPUHand + ; do for non-Turn Duelist + call SwapTurn + call .ShuffleDuelistHandTrainerCardsInDeck + call SwapTurn + ; do for Turn Duelist +; fallthrough + +.ShuffleDuelistHandTrainerCardsInDeck + call CreateHandCardList + call SortCardsInDuelTempListByID + xor a + ldh [hCurSelectionItem], a + ld hl, wDuelTempList + +; go through all cards in hand +; and any Trainer card is returned to deck. +.loop_hand + ld a, [hli] + ldh [hTempCardIndex_ff98], a + cp $ff + jr z, .done + call GetCardIDFromDeckIndex + call GetCardType + cp TYPE_TRAINER + jr nz, .loop_hand + ldh a, [hTempCardIndex_ff98] + call RemoveCardFromHand + call ReturnCardToDeck + push hl + ld hl, hCurSelectionItem + inc [hl] + pop hl + jr .loop_hand +.done +; show card list + ldh a, [hCurSelectionItem] + or a + call nz, Func_2c0bd ; only show list if there were any Trainer cards + ret + +.DisplayLinkOrCPUHand ; 2fa31 (b:7a31) + ld a, [wDuelType] + or a + jr z, .cpu_opp + +; link duel + ldh a, [hWhoseTurn] + push af + ld a, OPPONENT_TURN + ldh [hWhoseTurn], a + call .DisplayOppHand + pop af + ldh [hWhoseTurn], a + ret + +.cpu_opp + call SwapTurn + call .DisplayOppHand + call SwapTurn + ret + +.DisplayOppHand ; 2fa4f (b:7a4f) + call CreateHandCardList + jr c, .no_cards + bank1call InitAndDrawCardListScreenLayout + ldtx hl, ChooseTheCardYouWishToExamineText + ldtx de, DuelistHandText + bank1call SetCardListHeaderText + ld a, A_BUTTON | START + ld [wNoItemSelectionMenuKeys], a + bank1call DisplayCardList + ret +.no_cards + ldtx hl, DuelistHasNoCardsInHandText + call DrawWideTextBox_WaitForInput + ret + +; return carry if not enough cards in hand for effect +Maintenance_HandCheck: ; 2fa70 (b:7a70) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ldtx hl, NotEnoughCardsInHandText + cp 3 + ret + +Maintenance_PlayerSelection: ; 2fa7b (b:7a7b) + ldtx hl, Choose2HandCardsFromHandToReturnToDeckText + ldtx de, ChooseTheCardToPutBackText + call HandlePlayerSelection2HandCards + ret + +Maintenance_ReturnToDeckAndDrawEffect: ; 2fa85 (b:7a85) +; return both selected cards to the deck + ldh a, [hTempList] + call RemoveCardFromHand + call ReturnCardToDeck + ldh a, [hTempList + 1] + call RemoveCardFromHand + call ReturnCardToDeck + call Func_2c0bd + +; draw one card + ld a, 1 + bank1call DisplayDrawNCardsScreen + call DrawCardFromDeck + ldh [hTempCardIndex_ff98], a + call AddCardToHand + call IsPlayerTurn + ret nc + ; show card on screen if played by Player + bank1call DisplayPlayerDrawCardScreen + ret + +; return carry if no cards in deck +PokeBall_DeckCheck: ; 2faad (b:7aad) + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + ldtx hl, NoCardsLeftInTheDeckText + cp DECK_SIZE + ccf + ret + +PokeBall_PlayerSelection: ; 2fab9 (b:7ab9) + ld de, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call Func_2c08a + ldh [hTempList], a ; store coin result + ret nc + +; create list of all Pokemon cards in deck to search for + call CreateDeckCardList + ldtx hl, ChooseBasicOrEvolutionPokemonCardFromDeckText + ldtx bc, EvolutionCardText + lb de, SEARCHEFFECT_POKEMON, 0 + call LookForCardsInDeck + jr c, .no_pkmn ; return if Player chose not to check deck + +; handle input + bank1call Func_5591 + ldtx hl, ChoosePokemonCardText + ldtx de, DuelistDeckText + bank1call SetCardListHeaderText +.read_input + bank1call DisplayCardList + jr c, .try_exit ; B was pressed, check if Player can cancel operation + ldh a, [hTempCardIndex_ff98] + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .play_sfx ; can't select non-Pokemon card + ldh a, [hTempCardIndex_ff98] + ldh [hTempList + 1], a + or a + ret + +.no_pkmn + ld a, $ff + ldh [hTempList + 1], a + or a + ret + +.play_sfx + call Func_3794 + jr .read_input + +.try_exit +; Player can only exit screen if there are no cards to choose + ld hl, wDuelTempList +.loop + ld a, [hli] + cp $ff + jr z, .no_pkmn + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .loop + jr .read_input + +PokeBall_AddToHandEffect: ; 2fb15 (b:7b15) + ldh a, [hTempList] + or a + ret z ; return if coin toss was tails + + ldh a, [hTempList + 1] + cp $ff + jr z, .done ; skip if no Pokemon was chosen + +; add Pokemon card to hand and show in screen if +; it wasn't the Player who played the Trainer card. + call SearchCardInDeckAndAddToHand + call AddCardToHand + call IsPlayerTurn + jr c, .done + ldh a, [hTempList + 1] + ldtx hl, WasPlacedInTheHandText + bank1call DisplayCardDetailScreen +.done + call Func_2c0bd + ret + +; return carry if no cards in the Discard Pile +Recycle_DiscardPileCheck: ; 2fb36 (b:7b36) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE + call GetTurnDuelistVariable + ldtx hl, ThereAreNoCardsInTheDiscardPileText + cp 1 + ret + +Recycle_PlayerSelection: ; 2fb41 (b:7b41) + ld de, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call Func_2c08a + jr nc, .tails + + call CreateDiscardPileCardList + bank1call Func_5591 + ldtx hl, PleaseSelectCardText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText +.read_input + bank1call DisplayCardList + jr c, .read_input ; can't cancel with B button + +; Discard Pile card was chosen + ldh a, [hTempCardIndex_ff98] + ldh [hTempList], a + ret + +.tails + ld a, $ff + ldh [hTempList], a + or a + ret + +Recycle_AddToHandEffect: ; 2fb68 (b:7b68) + ldh a, [hTempList] + cp $ff + ret z ; return if no card was selected + +; add card to hand and show in screen if +; it wasn't the Player who played the Trainer card. + call MoveDiscardPileCardToHand + call ReturnCardToDeck + call IsPlayerTurn + ret c + ldh a, [hTempList] + ldtx hl, CardWasChosenText + bank1call DisplayCardDetailScreen + ret + +; return carry if Bench is full or +; if no Basic Pokemon cards in Discard Pile. +Revive_BenchCheck: ; 2fb80 (b:7b80) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ldtx hl, NoSpaceOnTheBenchText + cp MAX_PLAY_AREA_POKEMON + ccf + ret c + call CreateBasicPokemonCardListFromDiscardPile + ldtx hl, ThereAreNoPokemonInDiscardPileText + ret + +Revive_PlayerSelection: ; 2fb93 (b:7b93) +; create Basic Pokemon card list from Discard Pile + ldtx hl, ChooseBasicPokemonToPlaceOnBenchText + call DrawWideTextBox_WaitForInput + call CreateBasicPokemonCardListFromDiscardPile + bank1call Func_5591 + +; display screen to select Pokemon + ldtx hl, PleaseSelectCardText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText + bank1call DisplayCardList + +; store selection + ldh a, [hTempCardIndex_ff98] + ldh [hTemp_ffa0], a + ret + +Revive_PlaceInPlayAreaEffect: ; 2fbb0 (b:7bb0) +; place selected Pokemon in the Bench + ldh a, [hTemp_ffa0] + call MoveDiscardPileCardToHand + call AddCardToHand + call PutHandPokemonCardInPlayArea + +; set HP to half, rounded up + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + srl a + bit 0, a + jr z, .rounded + add 5 ; round up HP to nearest 10 +.rounded + ld [hl], a + call IsPlayerTurn + ret c ; done if Player played Revive + +; display card + ldh a, [hTemp_ffa0] + ldtx hl, PlacedOnTheBenchText + bank1call DisplayCardDetailScreen + ret + +; makes list in wDuelTempList with all Basic Pokemon cards +; that are in Turn Duelist's Discard Pile. +; if list turns out empty, return carry. +CreateBasicPokemonCardListFromDiscardPile: ; 2fbd6 (b:7bd6) +; gets hl to point at end of Discard Pile cards +; and iterates the cards in reverse order. + ld a, DUELVARS_NUMBER_OF_CARDS_IN_DISCARD_PILE + call GetTurnDuelistVariable + ld b, a + add DUELVARS_DECK_CARDS + ld l, a + ld de, wDuelTempList + inc b + jr .next_discard_pile_card + +.check_card + ld a, [hl] + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Type] + cp TYPE_ENERGY + jr nc, .next_discard_pile_card ; if not Pokemon card, skip + ld a, [wLoadedCard2Stage] + or a + jr nz, .next_discard_pile_card ; if not Basic stage, skip + +; write this card's index to wDuelTempList + ld a, [hl] + ld [de], a + inc de +.next_discard_pile_card + dec l + dec b + jr nz, .check_card + +; done with the loop. + ld a, $ff ; terminating byte + ld [de], a + ld a, [wDuelTempList] + cp $ff + jr z, .set_carry + or a + ret +.set_carry + scf + ret + +; return carry if Turn Duelist has no Evolution cards in Play Area +DevolutionSpray_PlayAreaEvolutionCheck: ; 2fc0b (b:7c0b) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld c, a + ld l, DUELVARS_ARENA_CARD +.loop + ld a, [hli] + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Stage] + or a + ret nz ; found an Evolution card + dec c + jr nz, .loop + + ldtx hl, ThereAreNoStage1PokemonText + scf + ret + +DevolutionSpray_PlayerSelection: ; 2fc24 (b:7c24) +; display textbox + ldtx hl, ChooseEvolutionCardAndPressAButtonToDevolveText + call DrawWideTextBox_WaitForInput + +; have Player select an Evolution card in Play Area + ld a, 1 + ldh [hCurSelectionItem], a + bank1call HasAlivePokemonInPlayArea +.read_input + bank1call OpenPlayAreaScreenForSelection + ret c ; exit if B was pressed + bank1call GetCardOneStageBelow + jr c, .read_input ; can't select Basic cards + +; get pre-evolution card data + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + push hl + push af + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_STAGE + ld l, a + ld a, [hl] + push hl + push af + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + ld l, a + ld a, [hl] + push hl + push af + jr .update_data + +.repeat_devolution +; show Play Area screen with static cursor +; so that the Player either presses A to do one more devolution +; or presses B to finish selection. + bank1call Func_6194 + jr c, .done_selection ; if B pressed, end selection instead + ; do one more devolution + bank1call GetCardOneStageBelow + +.update_data +; overwrite the card data to new devolved stats + ld a, d + call UpdateDevolvedCardHPAndStage + call GetNextPositionInTempList_TrainerEffects + ld [hl], e + ld a, d + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Stage] + or a + jr nz, .repeat_devolution ; can do one more devolution + +.done_selection + call GetNextPositionInTempList_TrainerEffects + ld [hl], $ff ; terminating byte + +; store this Play Area location in first item of temp list + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTempList], a + +; update Play Area location display of this Pokemon + call EmptyScreen + ldh a, [hTempPlayAreaLocation_ff9d] + ld hl, wHUDEnergyAndHPBarsX + ld [hli], a + ld [hl], $00 + bank1call PrintPlayAreaCardInformationAndLocation + call EnableLCD + pop bc + pop hl + +; rewrite all duelvars from before the selection was done +; this is so that if "No" is selected in confirmation menu, +; then the Pokemon isn't devolved and remains unchanged. + ld [hl], b + ldtx hl, IsThisOKText + call YesOrNoMenuWithText + pop bc + pop hl + + ld [hl], b + pop bc + pop hl + + ld [hl], b + ret + +DevolutionSpray_DevolutionEffect: ; 2fc99 (b:7c99) +; first byte in list is Play Area location chosen + ld hl, hTempList + ld a, [hli] + ldh [hTempPlayAreaLocation_ff9d], a + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + push hl + push af + +; loop through devolutions selected + ld hl, hTempList + 1 +.loop_devolutions + ld a, [hl] + cp $ff + jr z, .check_ko ; list is over + ; devolve card to its stage below + push hl + bank1call GetCardOneStageBelow + ld a, d + call UpdateDevolvedCardHPAndStage + call ResetDevolvedCardStatus + pop hl + ld a, [hli] + call PutCardInDiscardPile + jr .loop_devolutions + +.check_ko + pop af + ld e, a + pop hl + ld d, [hl] + call PrintDevolvedCardNameAndLevelText + ldh a, [hTempList] + call PrintPlayAreaCardKnockedOutIfNoHP + bank1call Func_6e49 + ret + +; returns carry if neither duelist has any energy cards attached +SuperEnergyRemoval_EnergyCheck: ; 2fcd0 (b:7cd0) + call CheckIfThereAreAnyEnergyCardsAttached + ldtx hl, NoEnergyCardsAttachedToPokemonInYourPlayAreaText + ret c + call SwapTurn + call CheckIfThereAreAnyEnergyCardsAttached + ldtx hl, NoEnergyCardsAttachedToPokemonInOppPlayAreaText + call SwapTurn + ret + +SuperEnergyRemoval_PlayerSelection: ; 2fce4 (b:7ce4) +; handle selection of Energy to discard in own Play Area + ldtx hl, ChoosePokemonInYourAreaThenPokemonInYourOppText + call DrawWideTextBox_WaitForInput + call HandlePokemonAndEnergySelectionScreen + ret c ; return if operation was cancelled + + ldtx hl, ChoosePokemonToRemoveEnergyFromText + call DrawWideTextBox_WaitForInput + + call SwapTurn + ld a, 3 + ldh [hCurSelectionItem], a +.select_opp_pkmn + bank1call HasAlivePokemonInPlayArea + bank1call OpenPlayAreaScreenForSelection + jr nc, .opp_pkmn_selected + ; B was pressed + call SwapTurn + ret ; return if operation was cancelled +.opp_pkmn_selected + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr nz, .has_energy ; has any energy cards attached? + ; no energy, loop back + ldtx hl, NoEnergyCardsText + call DrawWideTextBox_WaitForInput + jr .select_opp_pkmn + +.has_energy +; store this Pokemon's Play Area location + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hPlayAreaEffectTarget], a +; store which energy card to discard from it + bank1call CreateArenaOrBenchEnergyCardList + ldh a, [hTempPlayAreaLocation_ff9d] + bank1call DisplayEnergyDiscardScreen + ld a, 2 + ld [wEnergyDiscardMenuDenominator], a + +.loop_discard_energy_selection + bank1call HandleEnergyDiscardMenuInput + jr nc, .energy_selected + ; B pressed + ld a, 5 + call AskWhetherToQuitSelectingCards + jr nc, .done ; finish operation + ; player selected to continue selection + ld a, [wEnergyDiscardMenuNumerator] + push af + ldh a, [hTempPlayAreaLocation_ff9d] + bank1call DisplayEnergyDiscardScreen + ld a, 2 + ld [wEnergyDiscardMenuDenominator], a + pop af + ld [wEnergyDiscardMenuNumerator], a + jr .loop_discard_energy_selection + +.energy_selected +; store energy cards to discard from opponent + call GetNextPositionInTempList_TrainerEffects + ldh a, [hTempCardIndex_ff98] + ld [hl], a + call RemoveCardFromDuelTempList + ld hl, wEnergyDiscardMenuNumerator + inc [hl] + ldh a, [hCurSelectionItem] + cp 5 + jr nc, .done ; no more energy cards to select + ld a, [wDuelTempList] + cp $ff + jr z, .done ; no more energy cards to select + bank1call DisplayEnergyDiscardMenu + jr .loop_discard_energy_selection + +.done + call GetNextPositionInTempList_TrainerEffects + ld [hl], $ff + call SwapTurn + or a + ret + +SuperEnergyRemoval_DiscardEffect: ; 2fd73 (b:7d73) + ld hl, hTempList + 1 + +; discard energy card of own Play Area + ld a, [hli] + call PutCardInDiscardPile + +; iterate and discard opponent's energy cards + inc hl + call SwapTurn +.loop + ld a, [hli] + cp $ff + jr z, .done_discard + call PutCardInDiscardPile + jr .loop + +.done_discard +; if it's Player's turn, return... + call SwapTurn + call IsPlayerTurn + ret c +; ...otherwise show Play Area of affected Pokemon +; in opponent's Play Area + ldh a, [hTemp_ffa0] + call Func_2c10b +; in player's Play Area + xor a + ld [wDuelDisplayedScreen], a + call SwapTurn + ldh a, [hPlayAreaEffectTarget] + call Func_2c10b + call SwapTurn + ret + +; return carry if not enough cards in hand to +; discard for Super Energy Retrieval effect +; or if the Discard Pile has no basic Energy cards +SuperEnergyRetrieval_HandEnergyCheck: ; 2fda4 (b:7da4) + ld a, DUELVARS_NUMBER_OF_CARDS_IN_HAND + call GetTurnDuelistVariable + ldtx hl, NotEnoughCardsInHandText + cp 3 + ret c + call CreateEnergyCardListFromDiscardPile_OnlyBasic + ldtx hl, ThereAreNoBasicEnergyCardsInDiscardPileText + ret + +SuperEnergyRetrieval_PlayerHandSelection: ; 2fdb6 (b:7db6) + call HandlePlayerSelection2HandCardsToDiscard + ret + +SuperEnergyRetrieval_PlayerDiscardPileSelection: ; 2fdba (b:7dba) + ldtx hl, ChooseUpTo4FromDiscardPileText + call DrawWideTextBox_WaitForInput + call CreateEnergyCardListFromDiscardPile_OnlyBasic + +.loop_discard_pile_selection + bank1call InitAndDrawCardListScreenLayout + ldtx hl, PleaseSelectCardText + ldtx de, PlayerDiscardPileText + bank1call SetCardListHeaderText + bank1call DisplayCardList + jr nc, .store_selected_card + ; B pressed + ld a, 6 + call AskWhetherToQuitSelectingCards + jr c, .loop_discard_pile_selection ; player selected to continue + jr .done + +.store_selected_card + ldh a, [hTempCardIndex_ff98] + call GetTurnDuelistVariable + call GetNextPositionInTempList_TrainerEffects + ldh a, [hTempCardIndex_ff98] + ld [hl], a ; store selected energy card + call RemoveCardFromDuelTempList + jr c, .done + ; this shouldn't happen + ldh a, [hCurSelectionItem] + cp 6 + jr c, .loop_discard_pile_selection + +.done +; insert terminating byte + call GetNextPositionInTempList_TrainerEffects + ld [hl], $ff + or a + ret + +SuperEnergyRetrieval_DiscardAndAddToHandEffect: ; 2fdfa (b:7dfa) +; discard 2 cards selected from the hand + ld hl, hTemp_ffa0 + ld a, [hli] + call RemoveCardFromHand + call PutCardInDiscardPile + ld a, [hli] + call RemoveCardFromHand + call PutCardInDiscardPile + +; put selected cards in hand + ld de, wDuelTempList +.loop + ld a, [hli] + ld [de], a + inc de + cp $ff + jr z, .done + call MoveDiscardPileCardToHand + call AddCardToHand + jr .loop + +.done +; if Player played the card, exit + call IsPlayerTurn + ret c +; if not, show card list selected by Opponent + bank1call Func_4b38 + ret + +; outputs in hl the next position +; in hTempList to place a new card, +; and increments hCurSelectionItem. +; identical to GetNextPositionInTempList. +GetNextPositionInTempList_TrainerEffects: ; 2fe25 (b:7e25) + push de + ld hl, hCurSelectionItem + ld a, [hl] + inc [hl] + ld e, a + ld d, $00 + ld hl, hTempList + add hl, de + pop de + ret + +; handles screen for Player to select 2 cards from the hand to discard. +; first prints text informing Player to choose cards to discard +; then runs HandlePlayerSelection2HandCards routine. +HandlePlayerSelection2HandCardsToDiscard: ; 2fe34 (b:7e34) + ldtx hl, Choose2CardsFromHandToDiscardText + ldtx de, ChooseTheCardToDiscardText +; fallthrough + +; handles screen for Player to select 2 cards from the hand +; to activate some Trainer card effect. +; assumes Trainer card index being used is in [hTempCardIndex_ff9f]. +; stores selection of cards in hTempList. +; returns carry if Player cancels operation. +; input: +; hl = text to print in text box; +; de = text to print in screen header. +HandlePlayerSelection2HandCards: ; 2fe3a (b:7e3a) + push de + call DrawWideTextBox_WaitForInput + +; remove the Trainer card being used from list +; of cards to select from hand. + call CreateHandCardList + ldh a, [hTempCardIndex_ff9f] + call RemoveCardFromDuelTempList + + xor a + ldh [hCurSelectionItem], a + pop hl +.loop + push hl + bank1call Func_5591 + pop hl + bank1call SetCardListInfoBoxText + push hl + bank1call DisplayCardList + pop hl + jr c, .set_carry ; was B pressed? + push hl + call GetNextPositionInTempList_TrainerEffects + ldh a, [hTempCardIndex_ff98] + ld [hl], a + call RemoveCardFromDuelTempList + pop hl + ldh a, [hCurSelectionItem] + cp 2 + jr c, .loop ; is selection over? + or a + ret +.set_carry + scf + ret + +; return carry if non-turn duelist has no benched Pokemon +GustOfWind_BenchCheck: ; 2fe6e (b:7e6e) + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetNonTurnDuelistVariable + ldtx hl, EffectNoPokemonOnTheBenchText + cp 2 + ret + +GustOfWind_PlayerSelection: ; 2fe79 (b:7e79) + ldtx hl, ChooseAPokemonToSwitchWithActivePokemonText + call DrawWideTextBox_WaitForInput + call SwapTurn + bank1call HasAlivePokemonInBench + bank1call OpenPlayAreaScreenForSelection + ldh a, [hTempPlayAreaLocation_ff9d] + ldh [hTemp_ffa0], a + call SwapTurn + ret + +GustOfWind_SwitchEffect: ; 2fe90 (b:7e90) +; play whirlwind animation + ld a, ATK_ANIM_GUST_OF_WIND + call Func_2fea9 + +; switch Arena card + call SwapTurn + ldh a, [hTemp_ffa0] + ld e, a + call SwapArenaWithBenchPokemon + call SwapTurn + call ClearDamageReductionSubstatus2 + xor a + ld [wDuelDisplayedScreen], a + ret + +; input: +; a = attack animation to play +Func_2fea9: ; 2fea9 (b:7ea9) + ld [wLoadedAttackAnimation], a + bank1call Func_7415 + ld bc, $0 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation + ret + +; heals amount of damage in register e for card in +; Play Area location in [hTempPlayAreaLocation_ff9d]. +; plays healing animation and prints text with card's name. +; input: +; e = amount of HP to heal +; [hTempPlayAreaLocation_ff9d] = Play Area location of card to heal +HealPlayAreaCardHP: ; 2febc (b:7ebc) + ld e, a + ld d, $00 + +; play heal animation + push de + bank1call Func_7415 + ld a, ATK_ANIM_HEALING_WIND_PLAY_AREA + ld [wLoadedAttackAnimation], a + ldh a, [hTempPlayAreaLocation_ff9d] + ld b, a + ld c, $01 + ldh a, [hWhoseTurn] + ld h, a + bank1call PlayAttackAnimation + bank1call WaitAttackAnimation + pop hl + +; print Pokemon card name and damage healed + push hl + call LoadTxRam3 + ld hl, $0000 + call LoadTxRam2 + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld a, 18 + call CopyCardNameAndLevel + ld [hl], $00 ; terminating character on end of the name + ldtx hl, PokemonHealedDamageText + call DrawWideTextBox_WaitForInput + pop de + +; heal the target Pokemon + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + add e + ld [hl], a + ret |
