From 09239933ed98358ec3aff77d5eed90e5daba20a1 Mon Sep 17 00:00:00 2001 From: ElectroDeoxys Date: Mon, 7 Jun 2021 16:33:15 +0100 Subject: Split bank 8 --- bugs_and_glitches.md | 6 +- src/data/ai_trainer_card_logic.asm | 47 + src/engine/ai/common.asm | 970 +++ src/engine/ai/deck_ai.asm | 82 + src/engine/ai/decks/fire_charge.asm | 80 + src/engine/ai/decks/first_strike.asm | 76 + src/engine/ai/decks/flower_power.asm | 75 + src/engine/ai/decks/general.asm | 194 + src/engine/ai/decks/general_no_retreat.asm | 140 + src/engine/ai/decks/go_go_rain_dance.asm | 79 + src/engine/ai/decks/im_ronald.asm | 80 + src/engine/ai/decks/invincible_ronald.asm | 78 + src/engine/ai/decks/legendary_articuno.asm | 209 + src/engine/ai/decks/legendary_dragonite.asm | 166 + src/engine/ai/decks/legendary_moltres.asm | 176 + src/engine/ai/decks/legendary_ronald.asm | 203 + src/engine/ai/decks/legendary_zapdos.asm | 153 + src/engine/ai/decks/powerful_ronald.asm | 92 + src/engine/ai/decks/rock_crusher.asm | 74 + src/engine/ai/decks/sams_practice.asm | 205 + src/engine/ai/decks/strange_psyshock.asm | 81 + src/engine/ai/decks/wonders_of_science.asm | 77 + src/engine/ai/decks/zapping_selfdestruct.asm | 75 + src/engine/ai/pkmn_powers.asm | 1228 +++ src/engine/ai/trainer_cards.asm | 6073 +++++++++++++++ src/engine/bank05.asm | 2 +- src/engine/bank08.asm | 8319 --------------------- src/engine/deck_ai/deck_ai.asm | 82 - src/engine/deck_ai/decks/fire_charge.asm | 80 - src/engine/deck_ai/decks/first_strike.asm | 76 - src/engine/deck_ai/decks/flower_power.asm | 75 - src/engine/deck_ai/decks/general.asm | 194 - src/engine/deck_ai/decks/general_no_retreat.asm | 140 - src/engine/deck_ai/decks/go_go_rain_dance.asm | 79 - src/engine/deck_ai/decks/im_ronald.asm | 80 - src/engine/deck_ai/decks/invincible_ronald.asm | 78 - src/engine/deck_ai/decks/legendary_articuno.asm | 209 - src/engine/deck_ai/decks/legendary_dragonite.asm | 166 - src/engine/deck_ai/decks/legendary_moltres.asm | 176 - src/engine/deck_ai/decks/legendary_ronald.asm | 203 - src/engine/deck_ai/decks/legendary_zapdos.asm | 153 - src/engine/deck_ai/decks/powerful_ronald.asm | 92 - src/engine/deck_ai/decks/rock_crusher.asm | 74 - src/engine/deck_ai/decks/sams_practice.asm | 205 - src/engine/deck_ai/decks/strange_psyshock.asm | 81 - src/engine/deck_ai/decks/wonders_of_science.asm | 77 - src/engine/deck_ai/decks/zapping_selfdestruct.asm | 75 - src/engine/home.asm | 2 +- src/layout.link | 2 +- src/main.asm | 6 +- 50 files changed, 10723 insertions(+), 10722 deletions(-) create mode 100644 src/data/ai_trainer_card_logic.asm create mode 100644 src/engine/ai/common.asm create mode 100644 src/engine/ai/deck_ai.asm create mode 100644 src/engine/ai/decks/fire_charge.asm create mode 100644 src/engine/ai/decks/first_strike.asm create mode 100644 src/engine/ai/decks/flower_power.asm create mode 100644 src/engine/ai/decks/general.asm create mode 100644 src/engine/ai/decks/general_no_retreat.asm create mode 100644 src/engine/ai/decks/go_go_rain_dance.asm create mode 100644 src/engine/ai/decks/im_ronald.asm create mode 100644 src/engine/ai/decks/invincible_ronald.asm create mode 100644 src/engine/ai/decks/legendary_articuno.asm create mode 100644 src/engine/ai/decks/legendary_dragonite.asm create mode 100644 src/engine/ai/decks/legendary_moltres.asm create mode 100644 src/engine/ai/decks/legendary_ronald.asm create mode 100644 src/engine/ai/decks/legendary_zapdos.asm create mode 100644 src/engine/ai/decks/powerful_ronald.asm create mode 100644 src/engine/ai/decks/rock_crusher.asm create mode 100644 src/engine/ai/decks/sams_practice.asm create mode 100644 src/engine/ai/decks/strange_psyshock.asm create mode 100644 src/engine/ai/decks/wonders_of_science.asm create mode 100644 src/engine/ai/decks/zapping_selfdestruct.asm create mode 100644 src/engine/ai/pkmn_powers.asm create mode 100644 src/engine/ai/trainer_cards.asm delete mode 100644 src/engine/bank08.asm delete mode 100644 src/engine/deck_ai/deck_ai.asm delete mode 100644 src/engine/deck_ai/decks/fire_charge.asm delete mode 100644 src/engine/deck_ai/decks/first_strike.asm delete mode 100644 src/engine/deck_ai/decks/flower_power.asm delete mode 100644 src/engine/deck_ai/decks/general.asm delete mode 100644 src/engine/deck_ai/decks/general_no_retreat.asm delete mode 100644 src/engine/deck_ai/decks/go_go_rain_dance.asm delete mode 100644 src/engine/deck_ai/decks/im_ronald.asm delete mode 100644 src/engine/deck_ai/decks/invincible_ronald.asm delete mode 100644 src/engine/deck_ai/decks/legendary_articuno.asm delete mode 100644 src/engine/deck_ai/decks/legendary_dragonite.asm delete mode 100644 src/engine/deck_ai/decks/legendary_moltres.asm delete mode 100644 src/engine/deck_ai/decks/legendary_ronald.asm delete mode 100644 src/engine/deck_ai/decks/legendary_zapdos.asm delete mode 100644 src/engine/deck_ai/decks/powerful_ronald.asm delete mode 100644 src/engine/deck_ai/decks/rock_crusher.asm delete mode 100644 src/engine/deck_ai/decks/sams_practice.asm delete mode 100644 src/engine/deck_ai/decks/strange_psyshock.asm delete mode 100644 src/engine/deck_ai/decks/wonders_of_science.asm delete mode 100644 src/engine/deck_ai/decks/zapping_selfdestruct.asm diff --git a/bugs_and_glitches.md b/bugs_and_glitches.md index 96e3c40..f9704f8 100644 --- a/bugs_and_glitches.md +++ b/bugs_and_glitches.md @@ -134,7 +134,7 @@ Each deck AI lists some Pokémon card IDs that have an associated score for retr However, the game never actually stores the pointer to these lists (a notable expection being the Legendary Moltres deck), so the AI cannot access these score modifiers. -**Fix:** Edit all applicable decks in `src/engine/deck_ai/decks/`, uncommenting the following line: +**Fix:** Edit all applicable decks in `src/engine/ai/decks/`, uncommenting the following line: ```diff .store_list_pointers store_list_pointer wAICardListAvoidPrize, .list_prize @@ -283,7 +283,7 @@ AIDecide_PokemonTrader_PowerGenerator: ; 2200b (8:600b) ## AI never uses Energy Trans in order to retreat Arena card -There is a mistake in the AI retreat logic, in [src/engine/deck_ai/decks/general.asm](https://github.com/pret/poketcg/blob/master/src/engine/deck_ai/decks/general.asm): +There is a mistake in the AI retreat logic, in [src/engine/ai/decks/general.asm](https://github.com/pret/poketcg/blob/master/src/engine/ai/decks/general.asm): ``` .used_switch ; if AI used switch, unset its AI flag @@ -308,7 +308,7 @@ There is a mistake in the AI retreat logic, in [src/engine/deck_ai/decks/general ## Sam's practice deck does wrong card ID check -There is a mistake in the AI logic for deciding which Pokémon for Sam to switch to, in [src/engine/deck_ai/decks/sams_practice.asm](https://github.com/pret/poketcg/blob/master/src/engine/deck_ai/decks/sams_practice.asm): +There is a mistake in the AI logic for deciding which Pokémon for Sam to switch to, in [src/engine/ai/decks/sams_practice.asm](https://github.com/pret/poketcg/blob/master/src/engine/ai/decks/sams_practice.asm): ``` ; 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 diff --git a/src/data/ai_trainer_card_logic.asm b/src/data/ai_trainer_card_logic.asm new file mode 100644 index 0000000..57bf90f --- /dev/null +++ b/src/data/ai_trainer_card_logic.asm @@ -0,0 +1,47 @@ +ai_trainer_card_logic: MACRO + db \1 ; AI_TRAINER_CARD_PHASE_* constant + db \2 ; ID of trainer card + dw \3 ; function for AI decision to play card + dw \4 ; function for AI playing the card +ENDM + +AITrainerCardLogic: ; 20000 (8:4000) + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_07, POTION, AIDecide_Potion1, AIPlay_Potion + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_10, POTION, AIDecide_Potion2, AIPlay_Potion + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_08, SUPER_POTION, AIDecide_SuperPotion1, AIPlay_SuperPotion + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_11, SUPER_POTION, AIDecide_SuperPotion2, AIPlay_SuperPotion + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_13, DEFENDER, AIDecide_Defender1, AIPlay_Defender + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_14, DEFENDER, AIDecide_Defender2, AIPlay_Defender + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_13, PLUSPOWER, AIDecide_Pluspower1, AIPlay_Pluspower + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_14, PLUSPOWER, AIDecide_Pluspower2, AIPlay_Pluspower + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_09, SWITCH, AIDecide_Switch, AIPlay_Switch + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_07, GUST_OF_WIND, AIDecide_GustOfWind, AIPlay_GustOfWind + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_10, GUST_OF_WIND, AIDecide_GustOfWind, AIPlay_GustOfWind + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_04, BILL, AIDecide_Bill, AIPlay_Bill + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_05, ENERGY_REMOVAL, AIDecide_EnergyRemoval, AIPlay_EnergyRemoval + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_05, SUPER_ENERGY_REMOVAL, AIDecide_SuperEnergyRemoval, AIPlay_SuperEnergyRemoval + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_07, POKEMON_BREEDER, AIDecide_PokemonBreeder, AIPlay_PokemonBreeder + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_15, PROFESSOR_OAK, AIDecide_ProfessorOak, AIPlay_ProfessorOak + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_10, ENERGY_RETRIEVAL, AIDecide_EnergyRetrieval, AIPlay_EnergyRetrieval + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_11, SUPER_ENERGY_RETRIEVAL, AIDecide_SuperEnergyRetrieval, AIPlay_SuperEnergyRetrieval + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_06, POKEMON_CENTER, AIDecide_PokemonCenter, AIPlay_PokemonCenter + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_07, IMPOSTER_PROFESSOR_OAK, AIDecide_ImposterProfessorOak, AIPlay_ImposterProfessorOak + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_12, ENERGY_SEARCH, AIDecide_EnergySearch, AIPlay_EnergySearch + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_03, POKEDEX, AIDecide_Pokedex, AIPlay_Pokedex + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_07, FULL_HEAL, AIDecide_FullHeal, AIPlay_FullHeal + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_10, MR_FUJI, AIDecide_MrFuji, AIPlay_MrFuji + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_10, SCOOP_UP, AIDecide_ScoopUp, AIPlay_ScoopUp + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_02, MAINTENANCE, AIDecide_Maintenance, AIPlay_Maintenance + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_03, RECYCLE, AIDecide_Recycle, AIPlay_Recycle + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_13, LASS, AIDecide_Lass, AIPlay_Lass + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_04, ITEM_FINDER, AIDecide_ItemFinder, AIPlay_ItemFinder + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_01, IMAKUNI_CARD, AIDecide_Imakuni, AIPlay_Imakuni + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_01, GAMBLER, AIDecide_Gambler, AIPlay_Gambler + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_05, REVIVE, AIDecide_Revive, AIPlay_Revive + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_13, POKEMON_FLUTE, AIDecide_PokemonFlute, AIPlay_PokemonFlute + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_05, CLEFAIRY_DOLL, AIDecide_ClefairyDollOrMysteriousFossil, AIPlay_ClefairyDollOrMysteriousFossil + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_05, MYSTERIOUS_FOSSIL, AIDecide_ClefairyDollOrMysteriousFossil, AIPlay_ClefairyDollOrMysteriousFossil + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_02, POKE_BALL, AIDecide_Pokeball, AIPlay_Pokeball + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_02, COMPUTER_SEARCH, AIDecide_ComputerSearch, AIPlay_ComputerSearch + ai_trainer_card_logic AI_TRAINER_CARD_PHASE_02, POKEMON_TRADER, AIDecide_PokemonTrader, AIPlay_PokemonTrader + db $ff diff --git a/src/engine/ai/common.asm b/src/engine/ai/common.asm new file mode 100644 index 0000000..d4f1da4 --- /dev/null +++ b/src/engine/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/ai/deck_ai.asm b/src/engine/ai/deck_ai.asm new file mode 100644 index 0000000..c330418 --- /dev/null +++ b/src/engine/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/ai/decks/general.asm" +INCLUDE "engine/ai/decks/sams_practice.asm" +INCLUDE "engine/ai/decks/general_no_retreat.asm" +INCLUDE "engine/ai/decks/legendary_moltres.asm" +INCLUDE "engine/ai/decks/legendary_zapdos.asm" +INCLUDE "engine/ai/decks/legendary_articuno.asm" +INCLUDE "engine/ai/decks/legendary_dragonite.asm" +INCLUDE "engine/ai/decks/first_strike.asm" +INCLUDE "engine/ai/decks/rock_crusher.asm" +INCLUDE "engine/ai/decks/go_go_rain_dance.asm" +INCLUDE "engine/ai/decks/zapping_selfdestruct.asm" +INCLUDE "engine/ai/decks/flower_power.asm" +INCLUDE "engine/ai/decks/strange_psyshock.asm" +INCLUDE "engine/ai/decks/wonders_of_science.asm" +INCLUDE "engine/ai/decks/fire_charge.asm" +INCLUDE "engine/ai/decks/im_ronald.asm" +INCLUDE "engine/ai/decks/powerful_ronald.asm" +INCLUDE "engine/ai/decks/invincible_ronald.asm" +INCLUDE "engine/ai/decks/legendary_ronald.asm" diff --git a/src/engine/ai/decks/fire_charge.asm b/src/engine/ai/decks/fire_charge.asm new file mode 100644 index 0000000..f5b347b --- /dev/null +++ b/src/engine/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/ai/decks/first_strike.asm b/src/engine/ai/decks/first_strike.asm new file mode 100644 index 0000000..2e636e1 --- /dev/null +++ b/src/engine/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/ai/decks/flower_power.asm b/src/engine/ai/decks/flower_power.asm new file mode 100644 index 0000000..4d423a3 --- /dev/null +++ b/src/engine/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/ai/decks/general.asm b/src/engine/ai/decks/general.asm new file mode 100644 index 0000000..039e101 --- /dev/null +++ b/src/engine/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/ai/decks/general_no_retreat.asm b/src/engine/ai/decks/general_no_retreat.asm new file mode 100644 index 0000000..20d84e3 --- /dev/null +++ b/src/engine/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/ai/decks/go_go_rain_dance.asm b/src/engine/ai/decks/go_go_rain_dance.asm new file mode 100644 index 0000000..23547e2 --- /dev/null +++ b/src/engine/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/ai/decks/im_ronald.asm b/src/engine/ai/decks/im_ronald.asm new file mode 100644 index 0000000..b002d83 --- /dev/null +++ b/src/engine/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/ai/decks/invincible_ronald.asm b/src/engine/ai/decks/invincible_ronald.asm new file mode 100644 index 0000000..463560b --- /dev/null +++ b/src/engine/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/ai/decks/legendary_articuno.asm b/src/engine/ai/decks/legendary_articuno.asm new file mode 100644 index 0000000..6409330 --- /dev/null +++ b/src/engine/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/ai/decks/legendary_dragonite.asm b/src/engine/ai/decks/legendary_dragonite.asm new file mode 100644 index 0000000..597f72c --- /dev/null +++ b/src/engine/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/ai/decks/legendary_moltres.asm b/src/engine/ai/decks/legendary_moltres.asm new file mode 100644 index 0000000..d07afb9 --- /dev/null +++ b/src/engine/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 NINETAILS2, 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/ai/decks/legendary_ronald.asm b/src/engine/ai/decks/legendary_ronald.asm new file mode 100644 index 0000000..3356838 --- /dev/null +++ b/src/engine/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/ai/decks/legendary_zapdos.asm b/src/engine/ai/decks/legendary_zapdos.asm new file mode 100644 index 0000000..cc99f0c --- /dev/null +++ b/src/engine/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/ai/decks/powerful_ronald.asm b/src/engine/ai/decks/powerful_ronald.asm new file mode 100644 index 0000000..096fbea --- /dev/null +++ b/src/engine/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/ai/decks/rock_crusher.asm b/src/engine/ai/decks/rock_crusher.asm new file mode 100644 index 0000000..41a50fa --- /dev/null +++ b/src/engine/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/ai/decks/sams_practice.asm b/src/engine/ai/decks/sams_practice.asm new file mode 100644 index 0000000..dddce61 --- /dev/null +++ b/src/engine/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/ai/decks/strange_psyshock.asm b/src/engine/ai/decks/strange_psyshock.asm new file mode 100644 index 0000000..ef378b0 --- /dev/null +++ b/src/engine/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/ai/decks/wonders_of_science.asm b/src/engine/ai/decks/wonders_of_science.asm new file mode 100644 index 0000000..706a7e6 --- /dev/null +++ b/src/engine/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/ai/decks/zapping_selfdestruct.asm b/src/engine/ai/decks/zapping_selfdestruct.asm new file mode 100644 index 0000000..da5e7c6 --- /dev/null +++ b/src/engine/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/ai/pkmn_powers.asm b/src/engine/ai/pkmn_powers.asm new file mode 100644 index 0000000..52a8036 --- /dev/null +++ b/src/engine/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, wcda5 + 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/ai/trainer_cards.asm b/src/engine/ai/trainer_cards.asm new file mode 100644 index 0000000..cd87a4b --- /dev/null +++ b/src/engine/ai/trainer_cards.asm @@ -0,0 +1,6073 @@ +INCLUDE "data/ai_trainer_card_logic.asm" + +_AIProcessHandTrainerCards: ; 200e5 (8:40e5) + ld [wce18], 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, [wce18] + 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, NINETAILS1 + call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea + jr c, .find_duplicates + ld a, VULPIX + ld b, NINETAILS1 + 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/bank05.asm b/src/engine/bank05.asm index 75ca6b9..4845d40 100644 --- a/src/engine/bank05.asm +++ b/src/engine/bank05.asm @@ -1205,7 +1205,7 @@ AIProcessHandTrainerCards: ; 14663 (5:4663) farcall _AIProcessHandTrainerCards ret -INCLUDE "engine/deck_ai/deck_ai.asm" +INCLUDE "engine/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 diff --git a/src/engine/bank08.asm b/src/engine/bank08.asm deleted file mode 100644 index 4b4444c..0000000 --- a/src/engine/bank08.asm +++ /dev/null @@ -1,8319 +0,0 @@ -; unknown byte / card ID / function pointer 1 / function pointer 2 -unknown_data_20000: MACRO - db \1, \2 - dw \3 - dw \4 -ENDM - -Data_20000: ; 20000 (8:4000) - unknown_data_20000 AI_TRAINER_CARD_PHASE_07, POTION, AIDecide_Potion1, AIPlay_Potion - unknown_data_20000 AI_TRAINER_CARD_PHASE_10, POTION, AIDecide_Potion2, AIPlay_Potion - unknown_data_20000 AI_TRAINER_CARD_PHASE_08, SUPER_POTION, AIDecide_SuperPotion1, AIPlay_SuperPotion - unknown_data_20000 AI_TRAINER_CARD_PHASE_11, SUPER_POTION, AIDecide_SuperPotion2, AIPlay_SuperPotion - unknown_data_20000 AI_TRAINER_CARD_PHASE_13, DEFENDER, AIDecide_Defender1, AIPlay_Defender - unknown_data_20000 AI_TRAINER_CARD_PHASE_14, DEFENDER, AIDecide_Defender2, AIPlay_Defender - unknown_data_20000 AI_TRAINER_CARD_PHASE_13, PLUSPOWER, AIDecide_Pluspower1, AIPlay_Pluspower - unknown_data_20000 AI_TRAINER_CARD_PHASE_14, PLUSPOWER, AIDecide_Pluspower2, AIPlay_Pluspower - unknown_data_20000 AI_TRAINER_CARD_PHASE_09, SWITCH, AIDecide_Switch, AIPlay_Switch - unknown_data_20000 AI_TRAINER_CARD_PHASE_07, GUST_OF_WIND, AIDecide_GustOfWind, AIPlay_GustOfWind - unknown_data_20000 AI_TRAINER_CARD_PHASE_10, GUST_OF_WIND, AIDecide_GustOfWind, AIPlay_GustOfWind - unknown_data_20000 AI_TRAINER_CARD_PHASE_04, BILL, AIDecide_Bill, AIPlay_Bill - unknown_data_20000 AI_TRAINER_CARD_PHASE_05, ENERGY_REMOVAL, AIDecide_EnergyRemoval, AIPlay_EnergyRemoval - unknown_data_20000 AI_TRAINER_CARD_PHASE_05, SUPER_ENERGY_REMOVAL, AIDecide_SuperEnergyRemoval, AIPlay_SuperEnergyRemoval - unknown_data_20000 AI_TRAINER_CARD_PHASE_07, POKEMON_BREEDER, AIDecide_PokemonBreeder, AIPlay_PokemonBreeder - unknown_data_20000 AI_TRAINER_CARD_PHASE_15, PROFESSOR_OAK, AIDecide_ProfessorOak, AIPlay_ProfessorOak - unknown_data_20000 AI_TRAINER_CARD_PHASE_10, ENERGY_RETRIEVAL, AIDecide_EnergyRetrieval, AIPlay_EnergyRetrieval - unknown_data_20000 AI_TRAINER_CARD_PHASE_11, SUPER_ENERGY_RETRIEVAL, AIDecide_SuperEnergyRetrieval, AIPlay_SuperEnergyRetrieval - unknown_data_20000 AI_TRAINER_CARD_PHASE_06, POKEMON_CENTER, AIDecide_PokemonCenter, AIPlay_PokemonCenter - unknown_data_20000 AI_TRAINER_CARD_PHASE_07, IMPOSTER_PROFESSOR_OAK, AIDecide_ImposterProfessorOak, AIPlay_ImposterProfessorOak - unknown_data_20000 AI_TRAINER_CARD_PHASE_12, ENERGY_SEARCH, AIDecide_EnergySearch, AIPlay_EnergySearch - unknown_data_20000 AI_TRAINER_CARD_PHASE_03, POKEDEX, AIDecide_Pokedex, AIPlay_Pokedex - unknown_data_20000 AI_TRAINER_CARD_PHASE_07, FULL_HEAL, AIDecide_FullHeal, AIPlay_FullHeal - unknown_data_20000 AI_TRAINER_CARD_PHASE_10, MR_FUJI, AIDecide_MrFuji, AIPlay_MrFuji - unknown_data_20000 AI_TRAINER_CARD_PHASE_10, SCOOP_UP, AIDecide_ScoopUp, AIPlay_ScoopUp - unknown_data_20000 AI_TRAINER_CARD_PHASE_02, MAINTENANCE, AIDecide_Maintenance, AIPlay_Maintenance - unknown_data_20000 AI_TRAINER_CARD_PHASE_03, RECYCLE, AIDecide_Recycle, AIPlay_Recycle - unknown_data_20000 AI_TRAINER_CARD_PHASE_13, LASS, AIDecide_Lass, AIPlay_Lass - unknown_data_20000 AI_TRAINER_CARD_PHASE_04, ITEM_FINDER, AIDecide_ItemFinder, AIPlay_ItemFinder - unknown_data_20000 AI_TRAINER_CARD_PHASE_01, IMAKUNI_CARD, AIDecide_Imakuni, AIPlay_Imakuni - unknown_data_20000 AI_TRAINER_CARD_PHASE_01, GAMBLER, AIDecide_Gambler, AIPlay_Gambler - unknown_data_20000 AI_TRAINER_CARD_PHASE_05, REVIVE, AIDecide_Revive, AIPlay_Revive - unknown_data_20000 AI_TRAINER_CARD_PHASE_13, POKEMON_FLUTE, AIDecide_PokemonFlute, AIPlay_PokemonFlute - unknown_data_20000 AI_TRAINER_CARD_PHASE_05, CLEFAIRY_DOLL, AIDecide_ClefairyDollOrMysteriousFossil, AIPlay_ClefairyDollOrMysteriousFossil - unknown_data_20000 AI_TRAINER_CARD_PHASE_05, MYSTERIOUS_FOSSIL, AIDecide_ClefairyDollOrMysteriousFossil, AIPlay_ClefairyDollOrMysteriousFossil - unknown_data_20000 AI_TRAINER_CARD_PHASE_02, POKE_BALL, AIDecide_Pokeball, AIPlay_Pokeball - unknown_data_20000 AI_TRAINER_CARD_PHASE_02, COMPUTER_SEARCH, AIDecide_ComputerSearch, AIPlay_ComputerSearch - unknown_data_20000 AI_TRAINER_CARD_PHASE_02, POKEMON_TRADER, AIDecide_PokemonTrader, AIPlay_PokemonTrader - db $ff - -_AIProcessHandTrainerCards: ; 200e5 (8:40e5) - ld [wce18], 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, [wce18] - ld d, a - ld hl, Data_20000 -.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 Func_22bad - 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 Hgh Recoil. -.active_card - push de - call Func_22bad - 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 Func_22bad - 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 Func_22bad - 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, NINETAILS1 - call LookForCardIDInDeck_GivenCardIDInHandAndPlayArea - jr c, .find_duplicates - ld a, VULPIX - ld b, NINETAILS1 - 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 - -; 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, wcda5 - 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 - -; 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. -Func_22bad: ; 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/deck_ai/deck_ai.asm b/src/engine/deck_ai/deck_ai.asm deleted file mode 100644 index 97093c7..0000000 --- a/src/engine/deck_ai/deck_ai.asm +++ /dev/null @@ -1,82 +0,0 @@ -; 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/deck_ai/decks/general.asm" -INCLUDE "engine/deck_ai/decks/sams_practice.asm" -INCLUDE "engine/deck_ai/decks/general_no_retreat.asm" -INCLUDE "engine/deck_ai/decks/legendary_moltres.asm" -INCLUDE "engine/deck_ai/decks/legendary_zapdos.asm" -INCLUDE "engine/deck_ai/decks/legendary_articuno.asm" -INCLUDE "engine/deck_ai/decks/legendary_dragonite.asm" -INCLUDE "engine/deck_ai/decks/first_strike.asm" -INCLUDE "engine/deck_ai/decks/rock_crusher.asm" -INCLUDE "engine/deck_ai/decks/go_go_rain_dance.asm" -INCLUDE "engine/deck_ai/decks/zapping_selfdestruct.asm" -INCLUDE "engine/deck_ai/decks/flower_power.asm" -INCLUDE "engine/deck_ai/decks/strange_psyshock.asm" -INCLUDE "engine/deck_ai/decks/wonders_of_science.asm" -INCLUDE "engine/deck_ai/decks/fire_charge.asm" -INCLUDE "engine/deck_ai/decks/im_ronald.asm" -INCLUDE "engine/deck_ai/decks/powerful_ronald.asm" -INCLUDE "engine/deck_ai/decks/invincible_ronald.asm" -INCLUDE "engine/deck_ai/decks/legendary_ronald.asm" diff --git a/src/engine/deck_ai/decks/fire_charge.asm b/src/engine/deck_ai/decks/fire_charge.asm deleted file mode 100644 index f5b347b..0000000 --- a/src/engine/deck_ai/decks/fire_charge.asm +++ /dev/null @@ -1,80 +0,0 @@ -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/deck_ai/decks/first_strike.asm b/src/engine/deck_ai/decks/first_strike.asm deleted file mode 100644 index 2e636e1..0000000 --- a/src/engine/deck_ai/decks/first_strike.asm +++ /dev/null @@ -1,76 +0,0 @@ -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/deck_ai/decks/flower_power.asm b/src/engine/deck_ai/decks/flower_power.asm deleted file mode 100644 index 4d423a3..0000000 --- a/src/engine/deck_ai/decks/flower_power.asm +++ /dev/null @@ -1,75 +0,0 @@ -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/deck_ai/decks/general.asm b/src/engine/deck_ai/decks/general.asm deleted file mode 100644 index 039e101..0000000 --- a/src/engine/deck_ai/decks/general.asm +++ /dev/null @@ -1,194 +0,0 @@ -; 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/deck_ai/decks/general_no_retreat.asm b/src/engine/deck_ai/decks/general_no_retreat.asm deleted file mode 100644 index 20d84e3..0000000 --- a/src/engine/deck_ai/decks/general_no_retreat.asm +++ /dev/null @@ -1,140 +0,0 @@ -; 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/deck_ai/decks/go_go_rain_dance.asm b/src/engine/deck_ai/decks/go_go_rain_dance.asm deleted file mode 100644 index 23547e2..0000000 --- a/src/engine/deck_ai/decks/go_go_rain_dance.asm +++ /dev/null @@ -1,79 +0,0 @@ -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/deck_ai/decks/im_ronald.asm b/src/engine/deck_ai/decks/im_ronald.asm deleted file mode 100644 index b002d83..0000000 --- a/src/engine/deck_ai/decks/im_ronald.asm +++ /dev/null @@ -1,80 +0,0 @@ -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/deck_ai/decks/invincible_ronald.asm b/src/engine/deck_ai/decks/invincible_ronald.asm deleted file mode 100644 index 463560b..0000000 --- a/src/engine/deck_ai/decks/invincible_ronald.asm +++ /dev/null @@ -1,78 +0,0 @@ -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/deck_ai/decks/legendary_articuno.asm b/src/engine/deck_ai/decks/legendary_articuno.asm deleted file mode 100644 index 6409330..0000000 --- a/src/engine/deck_ai/decks/legendary_articuno.asm +++ /dev/null @@ -1,209 +0,0 @@ -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/deck_ai/decks/legendary_dragonite.asm b/src/engine/deck_ai/decks/legendary_dragonite.asm deleted file mode 100644 index 597f72c..0000000 --- a/src/engine/deck_ai/decks/legendary_dragonite.asm +++ /dev/null @@ -1,166 +0,0 @@ -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/deck_ai/decks/legendary_moltres.asm b/src/engine/deck_ai/decks/legendary_moltres.asm deleted file mode 100644 index d07afb9..0000000 --- a/src/engine/deck_ai/decks/legendary_moltres.asm +++ /dev/null @@ -1,176 +0,0 @@ -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 NINETAILS2, 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/deck_ai/decks/legendary_ronald.asm b/src/engine/deck_ai/decks/legendary_ronald.asm deleted file mode 100644 index 3356838..0000000 --- a/src/engine/deck_ai/decks/legendary_ronald.asm +++ /dev/null @@ -1,203 +0,0 @@ -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/deck_ai/decks/legendary_zapdos.asm b/src/engine/deck_ai/decks/legendary_zapdos.asm deleted file mode 100644 index cc99f0c..0000000 --- a/src/engine/deck_ai/decks/legendary_zapdos.asm +++ /dev/null @@ -1,153 +0,0 @@ -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/deck_ai/decks/powerful_ronald.asm b/src/engine/deck_ai/decks/powerful_ronald.asm deleted file mode 100644 index 096fbea..0000000 --- a/src/engine/deck_ai/decks/powerful_ronald.asm +++ /dev/null @@ -1,92 +0,0 @@ -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/deck_ai/decks/rock_crusher.asm b/src/engine/deck_ai/decks/rock_crusher.asm deleted file mode 100644 index 41a50fa..0000000 --- a/src/engine/deck_ai/decks/rock_crusher.asm +++ /dev/null @@ -1,74 +0,0 @@ -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/deck_ai/decks/sams_practice.asm b/src/engine/deck_ai/decks/sams_practice.asm deleted file mode 100644 index dddce61..0000000 --- a/src/engine/deck_ai/decks/sams_practice.asm +++ /dev/null @@ -1,205 +0,0 @@ -; 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/deck_ai/decks/strange_psyshock.asm b/src/engine/deck_ai/decks/strange_psyshock.asm deleted file mode 100644 index ef378b0..0000000 --- a/src/engine/deck_ai/decks/strange_psyshock.asm +++ /dev/null @@ -1,81 +0,0 @@ -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/deck_ai/decks/wonders_of_science.asm b/src/engine/deck_ai/decks/wonders_of_science.asm deleted file mode 100644 index 706a7e6..0000000 --- a/src/engine/deck_ai/decks/wonders_of_science.asm +++ /dev/null @@ -1,77 +0,0 @@ -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/deck_ai/decks/zapping_selfdestruct.asm b/src/engine/deck_ai/decks/zapping_selfdestruct.asm deleted file mode 100644 index da5e7c6..0000000 --- a/src/engine/deck_ai/decks/zapping_selfdestruct.asm +++ /dev/null @@ -1,75 +0,0 @@ -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/home.asm b/src/engine/home.asm index 87deaaa..670557f 100644 --- a/src/engine/home.asm +++ b/src/engine/home.asm @@ -8258,7 +8258,7 @@ AIDoAction_TakePrize: ; 2bd7 (0:2bd7) jr AIDoAction ; this line is not needed ; calls the appropriate AI routine to handle action, -; depending on the deck ID (see engine/deck_ai/deck_ai.asm) +; depending on the deck ID (see engine/ai/deck_ai.asm) ; input: ; - a = AIACTION_* constant AIDoAction: ; 2bdb (0:2bdb) diff --git a/src/layout.link b/src/layout.link index 413f9b8..7a0a42a 100644 --- a/src/layout.link +++ b/src/layout.link @@ -46,7 +46,7 @@ ROMX $07 "Credits Sequence" "Booster Packs" ROMX $08 - "Bank 8" + "AI Logic" ROMX $0b "Effect Functions" ROMX $0c diff --git a/src/main.asm b/src/main.asm index b331dc8..e16c9ab 100644 --- a/src/main.asm +++ b/src/main.asm @@ -31,8 +31,10 @@ INCLUDE "data/sequences/credits_sequence.asm" SECTION "Booster Packs", ROMX INCLUDE "engine/booster_packs.asm" -SECTION "Bank 8", ROMX -INCLUDE "engine/bank08.asm" +SECTION "AI Logic", ROMX +INCLUDE "engine/ai/trainer_cards.asm" +INCLUDE "engine/ai/pkmn_powers.asm" +INCLUDE "engine/ai/common.asm" SECTION "Effect Functions", ROMX INCLUDE "engine/effect_functions.asm" -- cgit v1.2.3 From 119507e28423014d1027d284ec711c68e1dbe71e Mon Sep 17 00:00:00 2001 From: ElectroDeoxys Date: Mon, 7 Jun 2021 21:13:39 +0100 Subject: Split bank 5 --- src/constants/card_data_constants.asm | 4 +- src/data/cards.asm | 50 +- src/engine/ai/attacks.asm | 721 ++++ src/engine/ai/boss_deck_set_up.asm | 167 + src/engine/ai/core.asm | 2770 ++++++++++++ src/engine/ai/damage_calculation.asm | 450 ++ src/engine/ai/decks/unreferenced.asm | 42 + src/engine/ai/energy.asm | 1048 +++++ src/engine/ai/hand_pokemon.asm | 627 +++ src/engine/ai/init.asm | 98 + src/engine/ai/retreat.asm | 1009 +++++ src/engine/ai/special_attacks.asm | 481 +++ src/engine/bank05.asm | 7406 --------------------------------- src/layout.link | 4 +- src/main.asm | 7 +- 15 files changed, 7447 insertions(+), 7437 deletions(-) create mode 100644 src/engine/ai/attacks.asm create mode 100644 src/engine/ai/boss_deck_set_up.asm create mode 100644 src/engine/ai/core.asm create mode 100644 src/engine/ai/damage_calculation.asm create mode 100644 src/engine/ai/decks/unreferenced.asm create mode 100644 src/engine/ai/energy.asm create mode 100644 src/engine/ai/hand_pokemon.asm create mode 100644 src/engine/ai/init.asm create mode 100644 src/engine/ai/retreat.asm create mode 100644 src/engine/ai/special_attacks.asm delete mode 100644 src/engine/bank05.asm diff --git a/src/constants/card_data_constants.asm b/src/constants/card_data_constants.asm index 2fc0bfc..15c3e58 100644 --- a/src/constants/card_data_constants.asm +++ b/src/constants/card_data_constants.asm @@ -211,7 +211,7 @@ FLAG_2_BIT_7_F EQU %111 ; bit 1 covers a wide variety of effects ; bits 2-7 are unused BOOST_IF_TAKEN_DAMAGE_F EQU %000 -FLAG_3_BIT_1_F EQU %001 +SPECIAL_AI_HANDLING_F EQU %001 ; CARD_DATA_ATTACK*_FLAG1_F constants INFLICT_POISON EQU $1 << INFLICT_POISON_F @@ -238,7 +238,7 @@ FLAG_2_BIT_7 EQU $1 << FLAG_2_BIT_7_F ; bit 1 covers a wide variety of effects ; bits 2-7 are unused BOOST_IF_TAKEN_DAMAGE EQU $1 << BOOST_IF_TAKEN_DAMAGE_F -FLAG_3_BIT_1 EQU $1 << FLAG_3_BIT_1_F +SPECIAL_AI_HANDLING EQU $1 << SPECIAL_AI_HANDLING_F ; special CARD_DATA_RETREAT_COST values UNABLE_RETREAT EQU $64 diff --git a/src/data/cards.asm b/src/data/cards.asm index c66702b..8f6654e 100644 --- a/src/data/cards.asm +++ b/src/data/cards.asm @@ -880,7 +880,7 @@ NidoranFCard: ; 31134 (c:5134) dw NidoranFCallForFamilyEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -1288,7 +1288,7 @@ OddishCard: ; 3133c (c:533c) dw OddishSproutEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -1645,7 +1645,7 @@ BellsproutCard: ; 31503 (c:5503) dw BellsproutCallForFamilyEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -1937,7 +1937,7 @@ ExeggutorCard: ; 31689 (c:5689) dw ExeggutorTeleportEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -2192,7 +2192,7 @@ ScytherCard: ; 317ce (c:57ce) dw ScytherSwordsDanceEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -2549,7 +2549,7 @@ Ninetails2Card: ; 31995 (c:5995) dw NinetailsMixUpEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -3379,7 +3379,7 @@ GolduckCard: ; 31da5 (c:5da5) dw GolduckHyperBeamEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_HYPER_BEAM ; animation @@ -3875,7 +3875,7 @@ KrabbyCard: ; 3202f (c:602f) dw KrabbyCallForFamilyEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -4436,7 +4436,7 @@ Vaporeon1Card: ; 322fa (c:62fa) dw VaporeonFocusEnergyEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -5470,7 +5470,7 @@ Electrode1Card: ; 3280e (c:680e) dw ElectrodeEnergySpikeEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -5521,7 +5521,7 @@ Electrode2Card: ; 3284f (c:684f) dw ElectrodeChainLightningEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_CHAIN_LIGHTNING ; animation @@ -5878,7 +5878,7 @@ Zapdos3Card: ; 32a16 (c:6a16) dw ZapdosBigThunderEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_BIG_THUNDER ; animation @@ -6082,7 +6082,7 @@ DugtrioCard: ; 32b1a (c:6b1a) dw DugtrioEarthquakeEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_HIT ; animation @@ -6643,7 +6643,7 @@ Marowak1Card: ; 32de5 (c:6de5) dw MarowakCallforFriendEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -6694,7 +6694,7 @@ Marowak2Card: ; 32e26 (c:6e26) dw MarowakWailEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_CRY ; animation @@ -7408,7 +7408,7 @@ Gastly1Card: ; 331b4 (c:71b4) dw GastlyDestinyBondEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 3 db ATK_ANIM_GLOW_EFFECT ; animation @@ -7459,7 +7459,7 @@ Gastly2Card: ; 331f5 (c:71f5) dw GastlyEnergyConversionEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 10 db ATK_ANIM_ENERGY_CONVERSION ; animation @@ -7904,7 +7904,7 @@ Mewtwo2Card: ; 3343e (c:743e) dw Mewtwo2EnergyAbsorptionEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -7955,7 +7955,7 @@ Mewtwo3Card: ; 3347f (c:747f) dw Mewtwo3EnergyAbsorptionEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -8122,7 +8122,7 @@ Mew3Card: ; 33542 (c:7542) dw MewDevolutionBeamEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_NONE ; animation @@ -8720,7 +8720,7 @@ Jigglypuff2Card: ; 3384e (c:784e) dw JigglypuffFriendshipSongEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_NONE ; animation @@ -9281,7 +9281,7 @@ KangaskhanCard: ; 33b19 (c:7b19) dw KangaskhanFetchEffectCommands ; effect commands db DRAW_CARD ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -9485,7 +9485,7 @@ PorygonCard: ; 33c1d (c:7c1d) dw PorygonConversion1EffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -9499,7 +9499,7 @@ PorygonCard: ; 33c1d (c:7c1d) dw PorygonConversion2EffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_GLOW_EFFECT ; animation @@ -9652,7 +9652,7 @@ DragonairCard: ; 33ce0 (c:7ce0) dw DragonairHyperBeamEffectCommands ; effect commands db NONE ; flags 1 db NONE ; flags 2 - db FLAG_3_BIT_1 ; flags 3 + db SPECIAL_AI_HANDLING ; flags 3 db 0 db ATK_ANIM_HYPER_BEAM ; animation diff --git a/src/engine/ai/attacks.asm b/src/engine/ai/attacks.asm new file mode 100644 index 0000000..9f93c33 --- /dev/null +++ b/src/engine/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, $01 + ld [wcddb], a + call AITryUseAttack + scf + ret + +.dont_attack + ld a, [wAIExecuteProcessedAttack] + or a + jr z, .failed_to_use + +; reset Play Area AI score +; to the previous values. + jp RetrievePlayAreaAIScoreFromBackup2 + +; return no carry if no viable attack. +.failed_to_use + ld hl, wcdb4 + inc [hl] + or a + ret + +; determines the AI score of attack index in a +; of card in Play Area location hTempPlayAreaLocation_ff9d. +GetAIScoreOfAttack: ; 16a86 (5:6a86) +; initialize AI score. + ld [wSelectedAttack], a + ld a, $50 + ld [wAIScore], a + + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call CheckIfSelectedAttackIsUnusable + jr nc, .usable + +; return zero AI score. +.unusable + xor a + ld [wAIScore], a + jp .done + +; load arena card IDs +.usable + xor a + ld [wAICannotDamage], a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempTurnDuelistCardID], a + call SwapTurn + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempNonTurnDuelistCardID], a + +; handle the case where the player has No Damage substatus. +; in the case the player does, check if this attack +; has a residual effect, or if it can damage the opposing bench. +; If none of those are true, render the attack unusable. +; also if it's a PKMN power, consider it unusable as well. + bank1call HandleNoDamageOrEffectSubstatus + call SwapTurn + jr nc, .check_if_can_ko + + ; player is under No Damage substatus + ld a, $01 + ld [wAICannotDamage], a + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + jr z, .unusable + and RESIDUAL + jr nz, .check_if_can_ko + ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F + call CheckLoadedAttackFlag + jr nc, .unusable + +; calculate damage to player to check if attack can KO. +; encourage attack if it's able to KO. +.check_if_can_ko + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, DUELVARS_ARENA_CARD_HP + call GetNonTurnDuelistVariable + ld hl, wDamage + sub [hl] + jr c, .can_ko + jr z, .can_ko + jr .check_damage +.can_ko + ld a, 20 + call AddToAIScore + +; raise AI score by the number of damage counters that this attack deals. +; if no damage is dealt, subtract AI score. in case wDamage is zero +; but wMaxDamage is not, then encourage attack afterwards. +; otherwise, if wMaxDamage is also zero, check for damage against +; player's bench, and encourage attack in case there is. +.check_damage + xor a + ld [wAIAttackIsNonDamaging], a + ld a, [wDamage] + ld [wTempAI], a + or a + jr z, .no_damage + call CalculateByteTensDigit + call AddToAIScore + jr .check_recoil +.no_damage + ld a, $01 + ld [wAIAttackIsNonDamaging], a + call SubFromAIScore + ld a, [wAIMaxDamage] + or a + jr z, .no_max_damage + ld a, 2 + call AddToAIScore + xor a + ld [wAIAttackIsNonDamaging], a +.no_max_damage + ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F + call CheckLoadedAttackFlag + jr nc, .check_recoil + ld a, 2 + call AddToAIScore + +; handle recoil attacks (low and high recoil). +.check_recoil + ld a, ATTACK_FLAG1_ADDRESS | LOW_RECOIL_F + call CheckLoadedAttackFlag + jr c, .is_recoil + ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F + call CheckLoadedAttackFlag + jp nc, .check_defending_can_ko +.is_recoil + ; sub from AI score number of damage counters + ; that attack deals to itself. + ld a, [wLoadedAttackEffectParam] + or a + jp z, .check_defending_can_ko + ld [wDamage], a + call ApplyDamageModifiers_DamageToSelf + ld a, e + call CalculateByteTensDigit + call SubFromAIScore + + push de + ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F + call CheckLoadedAttackFlag + pop de + jr c, .high_recoil + + ; if LOW_RECOIL KOs self, decrease AI score + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + cp e + jr c, .kos_self + jp nz, .check_defending_can_ko +.kos_self + ld a, 10 + call SubFromAIScore + +.high_recoil + ; dismiss this attack if no benched Pokémon + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + jr c, .dismiss_high_recoil_atk + ; has benched Pokémon + +; here the AI handles high recoil attacks differently +; depending on what deck it's playing. + ld a, [wOpponentDeckID] + cp ROCK_CRUSHER_DECK_ID + jr z, .rock_crusher_deck + cp ZAPPING_SELFDESTRUCT_DECK_ID + jr z, .zapping_selfdestruct_deck + cp BOOM_BOOM_SELFDESTRUCT_DECK_ID + jr z, .encourage_high_recoil_atk + ; Boom Boom Selfdestruct deck always encourages + cp POWER_GENERATOR_DECK_ID + jr nz, .high_recoil_generic_checks + ; Power Generator deck always dismisses + +.dismiss_high_recoil_atk + xor a + ld [wAIScore], a + jp .done + +.encourage_high_recoil_atk + ld a, 20 + call AddToAIScore + jp .done + +; Zapping Selfdestruct deck only uses this attack +; if number of cards in deck >= 30 and +; HP of active card is < half max HP. +.zapping_selfdestruct_deck + ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK + call GetTurnDuelistVariable + cp 31 + jr nc, .high_recoil_generic_checks + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + sla a + cp c + jr c, .high_recoil_generic_checks + ld b, 0 + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp MAGNEMITE1 + jr z, .magnemite1 + ld b, 10 ; bench damage +.magnemite1 + ld a, 10 + add b + ld b, a ; 20 bench damage if not Magnemite1 + +; if this attack causes player to win the duel by +; knocking out own Pokémon, dismiss attack. + ld a, 1 ; count active Pokémon as KO'd + call .check_if_kos_bench + jr c, .dismiss_high_recoil_atk + jr .encourage_high_recoil_atk + +; Rock Crusher Deck only uses this attack if +; prize count is below 4 and attack wins (or potentially draws) the duel, +; (i.e. at least gets KOs equal to prize cards left). +.rock_crusher_deck + call CountPrizes + cp 4 + jr nc, .dismiss_high_recoil_atk + ; prize count < 4 + ld b, 20 ; damage dealt to bench + call SwapTurn + xor a + call .check_if_kos_bench + call SwapTurn + jr c, .encourage_high_recoil_atk + +; generic checks for all other deck IDs. +; encourage attack if it wins (or potentially draws) the duel, +; (i.e. at least gets KOs equal to prize cards left). +; dismiss it if it causes the player to win. +.high_recoil_generic_checks + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp CHANSEY + jr z, .chansey + cp MAGNEMITE1 + jr z, .magnemite1_or_weezing + cp WEEZING + jr z, .magnemite1_or_weezing + ld b, 20 ; bench damage + jr .check_bench_kos +.magnemite1_or_weezing + ld b, 10 ; bench damage + jr .check_bench_kos +.chansey + ld b, 0 ; no bench damage + +.check_bench_kos + push bc + call SwapTurn + xor a + call .check_if_kos_bench + call SwapTurn + pop bc + jr c, .wins_the_duel + push de + ld a, 1 + call .check_if_kos_bench + pop bc + jr nc, .count_own_ko_bench + +; attack causes player to draw all prize cards + xor a + ld [wAIScore], a + jp .done + +; attack causes CPU to draw all prize cards +.wins_the_duel + ld a, 20 + call AddToAIScore + jp .done + +; subtract from AI score number of own benched Pokémon KO'd +.count_own_ko_bench + push bc + ld a, d + or a + jr z, .count_player_ko_bench + dec a + call SubFromAIScore + +; add to AI score number of player benched Pokémon KO'd +.count_player_ko_bench + pop bc + ld a, b + call AddToAIScore + jr .check_defending_can_ko + +; local function that gets called to determine damage to +; benched Pokémon caused by a HIGH_RECOIL attack. +; return carry if using attack causes number of benched Pokémon KOs +; equal to or larger than remaining prize cards. +; this function is independent on duelist turn, so whatever +; turn it is when this is called, it's that duelist's +; bench/prize cards that get checked. +; input: +; a = initial number of KO's beside benched Pokémon, +; so that if the active Pokémon is KO'd by the attack, +; this counts towards the prize cards collected +; b = damage dealt to bench Pokémon +.check_if_kos_bench + ld d, a + ld a, DUELVARS_BENCH + call GetTurnDuelistVariable + ld e, PLAY_AREA_ARENA +.loop + inc e + ld a, [hli] + cp $ff + jr z, .exit_loop + ld a, e + add DUELVARS_ARENA_CARD_HP + push hl + call GetTurnDuelistVariable + pop hl + cp b + jr z, .increase_count + jr nc, .loop +.increase_count + ; increase d if damage dealt KOs + inc d + jr .loop +.exit_loop + push de + call SwapTurn + call CountPrizes + call SwapTurn + pop de + cp d + jp c, .set_carry + jp z, .set_carry + or a + ret +.set_carry + scf + ret + +; if defending card can KO, encourage attack +; unless attack is non-damaging. +.check_defending_can_ko + ld a, [wSelectedAttack] + push af + call CheckIfDefendingPokemonCanKnockOut + pop bc + ld a, b + ld [wSelectedAttack], a + jr nc, .check_discard + ld a, 5 + call AddToAIScore + ld a, [wAIAttackIsNonDamaging] + or a + jr z, .check_discard + ld a, 5 + call SubFromAIScore + +; subtract from AI score if this attack requires +; discarding any energy cards. +.check_discard + ld a, [wSelectedAttack] + ld e, a + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ld d, a + call CopyAttackDataAndDamage_FromDeckIndex + ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F + call CheckLoadedAttackFlag + jr nc, .asm_16ca6 + ld a, 1 + call SubFromAIScore + ld a, [wLoadedAttackEffectParam] + call SubFromAIScore + +.asm_16ca6 + ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_6_F + call CheckLoadedAttackFlag + jr nc, .check_nullify_flag + ld a, [wLoadedAttackEffectParam] + call AddToAIScore + +; encourage attack if it has a nullify or weaken attack effect. +.check_nullify_flag + ld a, ATTACK_FLAG2_ADDRESS | NULLIFY_OR_WEAKEN_ATTACK_F + call CheckLoadedAttackFlag + jr nc, .check_draw_flag + ld a, 1 + call AddToAIScore + +; encourage attack if it has an effect to draw a card. +.check_draw_flag + ld a, ATTACK_FLAG1_ADDRESS | DRAW_CARD_F + call CheckLoadedAttackFlag + jr nc, .check_heal_flag + ld a, 1 + call AddToAIScore + +.check_heal_flag + ld a, ATTACK_FLAG2_ADDRESS | HEAL_USER_F + call CheckLoadedAttackFlag + jr nc, .check_status_effect + ld a, [wLoadedAttackEffectParam] + cp 1 + jr z, .tally_heal_score + ld a, [wTempAI] + call CalculateByteTensDigit + ld b, a + ld a, [wLoadedAttackEffectParam] + cp 3 + jr z, .asm_16cec + srl b + jr nc, .asm_16cec + inc b +.asm_16cec + ld a, DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + call CalculateByteTensDigit + cp b + jr c, .tally_heal_score + ld a, b +.tally_heal_score + push af + ld e, PLAY_AREA_ARENA + call GetCardDamageAndMaxHP + call CalculateByteTensDigit + pop bc + cp b ; wLoadedAttackEffectParam + jr c, .add_heal_score + ld a, b +.add_heal_score + call AddToAIScore + +.check_status_effect + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + ; skip if player has Snorlax + cp SNORLAX + jp z, .handle_special_atks + + ld a, DUELVARS_ARENA_CARD_STATUS + call GetNonTurnDuelistVariable + ld [wTempAI], a + +; encourage a poison inflicting attack if opposing Pokémon +; isn't (doubly) poisoned already. +; if opposing Pokémon is only poisoned and not double poisoned, +; and this attack has FLAG_2_BIT_6 set, discourage it +; (possibly to make Nidoking's Toxic attack less likely to be chosen +; if the other Pokémon is poisoned.) + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_POISON_F + call CheckLoadedAttackFlag + jr nc, .check_sleep + ld a, [wTempAI] + and DOUBLE_POISONED + jr z, .add_poison_score + and $40 ; only double poisoned? + jr z, .check_sleep + ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_6_F + call CheckLoadedAttackFlag + jr nc, .check_sleep + ld a, 2 + call SubFromAIScore + jr .check_sleep +.add_poison_score + ld a, 2 + call AddToAIScore + +; encourage sleep-inducing attack if other Pokémon isn't asleep. +.check_sleep + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_SLEEP_F + call CheckLoadedAttackFlag + jr nc, .check_paralysis + ld a, [wTempAI] + and CNF_SLP_PRZ + cp ASLEEP + jr z, .check_paralysis + ld a, 1 + call AddToAIScore + +; encourage paralysis-inducing attack if other Pokémon isn't asleep. +; otherwise, if other Pokémon is asleep, discourage attack. +.check_paralysis + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_PARALYSIS_F + call CheckLoadedAttackFlag + jr nc, .check_confusion + ld a, [wTempAI] + and CNF_SLP_PRZ + cp ASLEEP + jr z, .sub_prz_score + ld a, 1 + call AddToAIScore + jr .check_confusion +.sub_prz_score + ld a, 1 + call SubFromAIScore + +; encourage confuse-inducing attack if other Pokémon isn't asleep +; or confused already. +; otherwise, if other Pokémon is asleep or confused, +; discourage attack instead. +.check_confusion + ld a, ATTACK_FLAG1_ADDRESS | INFLICT_CONFUSION_F + call CheckLoadedAttackFlag + jr nc, .check_if_confused + ld a, [wTempAI] + and CNF_SLP_PRZ + cp ASLEEP + jr z, .sub_cnf_score + ld a, [wTempAI] + and CNF_SLP_PRZ + cp CONFUSED + jr z, .check_if_confused + ld a, 1 + call AddToAIScore + jr .check_if_confused +.sub_cnf_score + ld a, 1 + call SubFromAIScore + +; if this Pokémon is confused, subtract from score. +.check_if_confused + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + cp CONFUSED + jr nz, .handle_special_atks + ld a, 1 + call SubFromAIScore + +; SPECIAL_AI_HANDLING marks attacks that the AI handles individually. +; each attack has its own checks and modifies AI score accordingly. +.handle_special_atks + ld a, ATTACK_FLAG3_ADDRESS | SPECIAL_AI_HANDLING_F + call CheckLoadedAttackFlag + jr nc, .done + call HandleSpecialAIAttacks + cp $80 + jr c, .negative_score + sub $80 + call AddToAIScore + jr .done +.negative_score + ld b, a + ld a, $80 + sub b + call SubFromAIScore + +.done + ret diff --git a/src/engine/ai/boss_deck_set_up.asm b/src/engine/ai/boss_deck_set_up.asm new file mode 100644 index 0000000..fa3a262 --- /dev/null +++ b/src/engine/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/ai/core.asm b/src/engine/ai/core.asm new file mode 100644 index 0000000..f182375 --- /dev/null +++ b/src/engine/ai/core.asm @@ -0,0 +1,2770 @@ +INCLUDE "engine/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/ai/damage_calculation.asm" + +AIProcessHandTrainerCards: ; 14663 (5:4663) + farcall _AIProcessHandTrainerCards + ret + +INCLUDE "engine/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/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/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/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/ai/energy.asm" + +INCLUDE "engine/ai/attacks.asm" + +INCLUDE "engine/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/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/ai/damage_calculation.asm b/src/engine/ai/damage_calculation.asm new file mode 100644 index 0000000..a4fcd27 --- /dev/null +++ b/src/engine/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/ai/decks/unreferenced.asm b/src/engine/ai/decks/unreferenced.asm new file mode 100644 index 0000000..3cd56c3 --- /dev/null +++ b/src/engine/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/ai/energy.asm b/src/engine/ai/energy.asm new file mode 100644 index 0000000..af1aa32 --- /dev/null +++ b/src/engine/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/ai/hand_pokemon.asm b/src/engine/ai/hand_pokemon.asm new file mode 100644 index 0000000..28ff6b1 --- /dev/null +++ b/src/engine/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/ai/init.asm b/src/engine/ai/init.asm new file mode 100644 index 0000000..cda2387 --- /dev/null +++ b/src/engine/ai/init.asm @@ -0,0 +1,98 @@ +InitAIDuelVars: ; 15636 (5:5636) + ld a, $10 + ld hl, wcda5 + call ClearMemory_Bank5 + ld a, 5 + ld [wAIPokedexCounter], a + ld a, $ff + ld [wcda5], a + ret + +; initializes some variables and sets value of wAIBarrierFlagCounter. +; if Player uses Barrier 3 times in a row, AI checks if Player's deck +; has only Mewtwo1 Pokemon cards (running a Mewtwo1 mill deck). +InitAITurnVars: ; 15649 (5:5649) +; increase Pokedex counter by 1 + ld a, [wAIPokedexCounter] + inc a + ld [wAIPokedexCounter], a + + xor a + ld [wPreviousAIFlags], a + ld [wcddb], a + ld [wcddc], a + ld [wAIRetreatedThisTurn], a + +; checks if the Player used an attack last turn +; and if it was the second attack of their card. + ld a, [wPlayerAttackingAttackIndex] + cp $ff + jr z, .check_flag + or a + jr z, .check_flag + ld a, [wPlayerAttackingCardIndex] + cp $ff + jr z, .check_flag + +; if the card is Mewtwo1, it means the Player +; used its second attack, Barrier. + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MEWTWO1 + jr nz, .check_flag + ; Player used Barrier last turn + +; check if flag was already set, if so, +; reset wAIBarrierFlagCounter to $80. + ld a, [wAIBarrierFlagCounter] + bit AI_MEWTWO_MILL_F, a + jr nz, .set_flag + +; if not, increase it by 1 and check if it exceeds 2. + inc a + ld [wAIBarrierFlagCounter], a + cp 3 + jr c, .done + +; this means that the Player used Barrier +; at least 3 turns in a row. +; check if Player is running Mewtwo1-only deck, +; if so, set wAIBarrierFlagCounter flag. + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call GetCardIDFromDeckIndex + call SwapTurn + ld a, e + cp MEWTWO1 + jr nz, .reset_1 + farcall CheckIfPlayerHasPokemonOtherThanMewtwo1 + jr nc, .set_flag +.reset_1 +; reset wAIBarrierFlagCounter + xor a + ld [wAIBarrierFlagCounter], a + jr .done + +.set_flag + ld a, AI_MEWTWO_MILL + ld [wAIBarrierFlagCounter], a + jr .done + +.check_flag +; increase counter by 1 if flag is set + ld a, [wAIBarrierFlagCounter] + bit AI_MEWTWO_MILL_F, a + jr z, .reset_2 + inc a + ld [wAIBarrierFlagCounter], a + jr .done + +.reset_2 +; reset wAIBarrierFlagCounter + xor a + ld [wAIBarrierFlagCounter], a +.done + ret diff --git a/src/engine/ai/retreat.asm b/src/engine/ai/retreat.asm new file mode 100644 index 0000000..618e859 --- /dev/null +++ b/src/engine/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 wcddb == 0 +; set wcdda's bit 7 flag +Func_15b54: ; 15b54 (5:5b54) + xor a + ld [wcdda], a + ld a, [wWhoseTurn] + cp OPPONENT_TURN + jr z, .opponent + +; player + ld a, [wLoadedAttackCategory] + cp POKEMON_POWER + ret z + jr .set_flag + +.opponent + ld a, [wcddb] + or a + ret nz + +.set_flag + ld a, %10000000 + ld [wcdda], a + ret + +; calculates AI score for bench Pokémon +; returns in a and [hTempPlayAreaLocation_ff9d] the +; Play Area location of best card to switch to. +; returns carry if no Bench Pokemon. +AIDecideBenchPokemonToSwitchTo: ; 15b72 (5:5b72) + xor a + ldh [hTempPlayAreaLocation_ff9d], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + ret c + +; has at least 2 Pokémon in Play Area + call Func_15b54 + call LoadDefendingPokemonColorWRAndPrizeCards + ld a, 50 + ld [wAIScore], a + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + ld b, a + ld c, PLAY_AREA_ARENA + push bc + jp .store_score + +.next_bench + push bc + ld a, c + ldh [hTempPlayAreaLocation_ff9d], a + ld a, 50 + ld [wAIScore], a + +; check if card can KO defending Pokémon +; if it can, raise AI score +; if on last prize card, raise AI score again + call CheckIfAnyAttackKnocksOutDefendingCard + jr nc, .check_can_use_atks + call CheckIfSelectedAttackIsUnusable + jr c, .check_can_use_atks + ld a, 10 + call AddToAIScore + ld a, [wcdda] + or %00000001 + ld [wcdda], a + call CountPrizes + cp 2 + jp nc, .check_defending_weak + ld a, 10 + call AddToAIScore + +; calculates damage of both attacks +; to raise AI score accordingly +.check_can_use_atks + xor a + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + call nc, .HandleAttackDamageScore + ld a, $01 + ld [wSelectedAttack], a + call CheckIfSelectedAttackIsUnusable + call nc, .HandleAttackDamageScore + jr .check_energy_card + +; adds to AI score depending on amount of damage +; it can inflict to the defending Pokémon +; AI score += floor(Damage / 10) + 1 +.HandleAttackDamageScore + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + call CalculateByteTensDigit + inc a + call AddToAIScore + ret + +; if an energy card that is needed is found in hand +; calculate damage of the move and raise AI score +; AI score += floor(Damage / 20) +.check_energy_card + call LookForEnergyNeededInHand + jr nc, .check_attached_energy + ld a, [wSelectedAttack] + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + call CalculateByteTensDigit + srl a + call AddToAIScore + +; if no energies attached to card, lower AI score +.check_attached_energy + ldh a, [hTempPlayAreaLocation_ff9d] + ld e, a + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + or a + jr nz, .check_mr_mime + ld a, 1 + call SubFromAIScore + +; if can damage Mr Mime, raise AI score +.check_mr_mime + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call SwapTurn + call LoadCardDataToBuffer2_FromDeckIndex + call SwapTurn + cp MR_MIME + jr nz, .check_defending_weak + xor a + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr nz, .can_damage + ld a, $01 + call EstimateDamage_VersusDefendingCard + ld a, [wDamage] + or a + jr z, .check_defending_weak +.can_damage + ld a, 5 + call AddToAIScore + +; if defending card is weak to this card, raise AI score +.check_defending_weak + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + ld a, [wLoadedCard1Type] + call TranslateColorToWR + ld c, a + ld hl, wAIPlayerWeakness + and [hl] + jr z, .check_defending_resist + ld a, 3 + call AddToAIScore + +; if defending card is resistant to this card, lower AI score +.check_defending_resist + ld a, c + ld hl, wAIPlayerResistance + and [hl] + jr z, .check_resistance + ld a, 2 + call SubFromAIScore + +; if this card is resistant to defending Pokémon, raise AI score +.check_resistance + ld a, [wAIPlayerColor] + ld hl, wLoadedCard1Resistance + and [hl] + jr z, .check_weakness + ld a, 2 + call AddToAIScore + +; if this card is weak to defending Pokémon, lower AI score +.check_weakness + ld a, [wAIPlayerColor] + ld hl, wLoadedCard1Weakness + and [hl] + jr z, .check_retreat_cost + ld a, 3 + call SubFromAIScore + +; if this card's retreat cost < 2, raise AI score +; if this card's retreat cost > 2, lower AI score +.check_retreat_cost + call GetPlayAreaCardRetreatCost + cp 2 + jr c, .one_or_none + jr z, .check_player_prize_count + ld a, 1 + call SubFromAIScore + jr .check_player_prize_count +.one_or_none + ld a, 1 + call AddToAIScore + +; if wcdda != $81 +; if defending Pokémon can KO this card +; if player is not at last prize card, lower 3 from AI score +; if player is at last prize card, lower 10 from AI score +.check_player_prize_count + ld a, [wcdda] + cp %10000000 | %00000001 + jr z, .check_hp + call CheckIfDefendingPokemonCanKnockOut + jr nc, .check_hp + ld e, 3 + ld a, [wAIPlayerPrizeCount] + cp 1 + jr nz, .lower_score_1 + ld e, 10 +.lower_score_1 + ld a, e + call SubFromAIScore + +; if this card's HP is 0, make AI score 0 +.check_hp + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD_HP + call GetTurnDuelistVariable + or a + jr nz, .add_hp_score + ld [wAIScore], a + jr .store_score + +; AI score += floor(HP/40) +.add_hp_score + ld b, a + ld a, 4 + call CalculateBDividedByA_Bank5 + call CalculateByteTensDigit + call AddToAIScore + +; raise AI score if +; - is a Mr Mime OR +; - is a Mew1 and defending card is not basic stage + ldh a, [hTempPlayAreaLocation_ff9d] + add DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call LoadCardDataToBuffer1_FromDeckIndex + cp MR_MIME + jr z, .raise_score + cp MEW1 + jr nz, .asm_15cf0 + ld a, DUELVARS_ARENA_CARD + call GetNonTurnDuelistVariable + call LoadCardDataToBuffer2_FromDeckIndex + ld a, [wLoadedCard2Stage] + or a + jr z, .asm_15cf0 +.raise_score + ld a, 5 + call AddToAIScore + +; if wLoadedCard1Unknown2 == $01, lower AI score +.asm_15cf0 + ld a, [wLoadedCard1Unknown2] + cp $01 + jr nz, .mysterious_fossil_or_clefairy_doll + ld a, 2 + call SubFromAIScore + +; if card is Mysterious Fossil or Clefairy Doll, +; lower AI score +.mysterious_fossil_or_clefairy_doll + ld a, [wLoadedCard1ID] + cp MYSTERIOUS_FOSSIL + jr z, .lower_score_2 + cp CLEFAIRY_DOLL + jr nz, .ai_score_bonus +.lower_score_2 + ld a, 10 + call SubFromAIScore + +.ai_score_bonus + ld b, a + ld a, [wAICardListRetreatBonus + 1] + or a + jr z, .store_score + ld h, a + ld a, [wAICardListRetreatBonus] + ld l, a + +.loop_ids + ld a, [hli] + or a + jr z, .store_score ; list is over + cp b + jr nz, .next_id + ld a, [hl] + cp $80 + jr c, .subtract_score + sub $80 + call AddToAIScore + jr .next_id +.subtract_score + ld c, a + ld a, $80 + sub c + call SubFromAIScore +.next_id + inc hl + jr .loop_ids + +.store_score + ldh a, [hTempPlayAreaLocation_ff9d] + ld c, a + ld b, $00 + ld hl, wPlayAreaAIScore + add hl, bc + ld a, [wAIScore] + ld [hl], a + pop bc + inc c + dec b + jp nz, .next_bench + +; done + xor a + ld [wcdb4], a + jp FindHighestBenchScore + +; handles AI action of retreating Arena Pokémon +; and chooses which energy cards to discard. +; if card can't discard, return carry. +; in case it's Clefairy Doll or Mysterious Fossil, +; handle its effect to discard itself instead of retreating. +; input: +; - a = Play Area location (PLAY_AREA_*) of card to retreat to. +AITryToRetreat: ; 15d4f (5:5d4f) + push af + ld a, [wAIPlayEnergyCardForRetreat] + or a + jr z, .check_id + +; AI is allowed to play an energy card +; from the hand in order to provide +; the necessary energy for retreat cost + +; check status + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + and CNF_SLP_PRZ + cp ASLEEP + jp z, .check_id + cp PARALYZED + jp z, .check_id + +; if an energy card hasn't been played yet, +; checks if the Pokémon needs just one more energy to retreat +; if it does, check if there are any energy cards in hand +; and if there are, play that energy card + ld a, [wAlreadyPlayedEnergy] + or a + jr nz, .check_id + ld e, PLAY_AREA_ARENA + call CountNumberOfEnergyCardsAttached + push af + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + pop bc + cp b + jr c, .check_id + jr z, .check_id + ; energy attached < retreat cost + sub b + cp 1 + jr nz, .check_id + call CreateEnergyCardListFromHand + jr c, .check_id + ld a, [wDuelTempList] + ldh [hTemp_ffa0], a + xor a + ldh [hTempPlayAreaLocation_ffa1], a + ld a, OPPACTION_PLAY_ENERGY + bank1call AIMakeDecision + +.check_id + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + cp MYSTERIOUS_FOSSIL + jp z, .mysterious_fossil_or_clefairy_doll + cp CLEFAIRY_DOLL + jp z, .mysterious_fossil_or_clefairy_doll + +; if card is Asleep or Paralyzed, set carry and exit +; else, load the status in hTemp_ffa0 + pop af + ldh [hTempPlayAreaLocation_ffa1], a + ld a, DUELVARS_ARENA_CARD_STATUS + call GetTurnDuelistVariable + ld b, a + and CNF_SLP_PRZ + cp ASLEEP + jp z, .set_carry + cp PARALYZED + jp z, .set_carry + ld a, b + ldh [hTemp_ffa0], a + ld a, $ff + ldh [hTempRetreatCostCards], a + +; check energy required to retreat +; if the cost is 0, retreat right away + xor a + ldh [hTempPlayAreaLocation_ff9d], a + call GetPlayAreaCardRetreatCost + ld [wTempCardRetreatCost], a + or a + jp z, .retreat + +; if cost > 0 and number of energy cards attached == cost +; discard them all + xor a + call CreateArenaOrBenchEnergyCardList + ld e, PLAY_AREA_ARENA + call GetPlayAreaCardAttachedEnergies + ld a, [wTotalAttachedEnergies] + ld c, a + ld a, [wTempCardRetreatCost] + cp c + jr nz, .choose_energy_discard + + ld hl, hTempRetreatCostCards + ld de, wDuelTempList +.loop_1 + ld a, [de] + inc de + ld [hli], a + cp $ff + jr nz, .loop_1 + jp .retreat + +; if cost > 0 and number of energy cards attached > cost +; choose energy cards to discard according to color +.choose_energy_discard + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + call GetCardIDFromDeckIndex + ld a, e + ld [wTempCardID], a + call LoadCardDataToBuffer1_FromCardID + ld a, [wLoadedCard1Type] + or TYPE_ENERGY + ld [wTempCardType], a + ld a, [wTempCardRetreatCost] + ld c, a + +; first, look for and discard double colorless energy +; if retreat cost is >= 2 + ld hl, wDuelTempList + ld de, hTempRetreatCostCards +.loop_2 + ld a, c + cp 2 + jr c, .energy_not_same_color + ld a, [hli] + cp $ff + jr z, .energy_not_same_color + ld [de], a + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp DOUBLE_COLORLESS_ENERGY + jr nz, .loop_2 + ld a, [de] + call RemoveCardFromDuelTempList + dec hl + inc de + dec c + dec c + jr nz, .loop_2 + jr .end_retreat_list + +; second, shuffle attached cards and discard energy cards +; that are not of the same type as the Pokémon +; the exception for this are cards that are needed for +; some attacks but are not of the same color as the Pokémon +; (i.e. Psyduck's Headache attack) +; and energy cards attached to Eevee corresponding to a +; color of any of its evolutions (water, fire, lightning) +.energy_not_same_color + ld hl, wDuelTempList + call CountCardsInDuelTempList + call ShuffleCards +.loop_3 + ld a, [hli] + cp $ff + jr z, .any_energy + ld [de], a + call CheckIfEnergyIsUseful + jr c, .loop_3 + ld a, [de] + call RemoveCardFromDuelTempList + dec hl + inc de + dec c + jr nz, .loop_3 + jr .end_retreat_list + +; third, discard any card until +; cost requirement is met +.any_energy + ld hl, wDuelTempList +.loop_4 + ld a, [hli] + cp $ff + jr z, .set_carry + ld [de], a + inc de + push de + call GetCardIDFromDeckIndex + ld a, e + pop de + cp DOUBLE_COLORLESS_ENERGY + jr nz, .not_double_colorless + dec c + jr z, .end_retreat_list +.not_double_colorless + dec c + jr nz, .loop_4 + +.end_retreat_list + ld a, $ff + ld [de], a + +.retreat + ld a, OPPACTION_ATTEMPT_RETREAT + bank1call AIMakeDecision + or a + ret +.set_carry + scf + ret + +; handle Mysterious Fossil and Clefairy Doll +; if there are bench Pokémon, use effect to discard card +; this is equivalent to using its Pokémon Power +.mysterious_fossil_or_clefairy_doll + ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA + call GetTurnDuelistVariable + cp 2 + jr nc, .has_bench + ; doesn't have any bench + pop af + jr .set_carry + +.has_bench + ld a, DUELVARS_ARENA_CARD + call GetTurnDuelistVariable + ldh [hTempCardIndex_ff9f], a + xor a + ldh [hTemp_ffa0], a + ld a, OPPACTION_USE_PKMN_POWER + bank1call AIMakeDecision + pop af + ldh [hAIPkmnPowerEffectParam], a + ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT + bank1call AIMakeDecision + ld a, OPPACTION_DUEL_MAIN_SCENE + bank1call AIMakeDecision + or a + ret diff --git a/src/engine/ai/special_attacks.asm b/src/engine/ai/special_attacks.asm new file mode 100644 index 0000000..0ed3c8e --- /dev/null +++ b/src/engine/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 NINETAILS2 + 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/bank05.asm b/src/engine/bank05.asm deleted file mode 100644 index 4845d40..0000000 --- a/src/engine/bank05.asm +++ /dev/null @@ -1,7406 +0,0 @@ -INCLUDE "data/deck_ai_pointers.asm" - -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 ; 14078 (5:4078) - 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 ; 1409e (5:409e) - call AIPlayInitialBasicCards - ret - -.forced_switch ; 140a2 (5:40a2) - call AIDecideBenchPokemonToSwitchTo - ret - -.ko_switch ; 140a6 (5:40a6) - call AIDecideBenchPokemonToSwitchTo - ret - -.take_prize ; 140aa (5:40aa) - call AIPickPrizeCards - ret - -; 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 - -; 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 - -AIProcessHandTrainerCards: ; 14663 (5:4663) - farcall _AIProcessHandTrainerCards - ret - -INCLUDE "engine/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 - -InitAIDuelVars: ; 15636 (5:5636) - ld a, $10 - ld hl, wcda5 - call ClearMemory_Bank5 - ld a, 5 - ld [wAIPokedexCounter], a - ld a, $ff - ld [wcda5], a - ret - -; initializes some variables and sets value of wAIBarrierFlagCounter. -; if Player uses Barrier 3 times in a row, AI checks if Player's deck -; has only Mewtwo1 Pokemon cards (running a Mewtwo1 mill deck). -InitAITurnVars: ; 15649 (5:5649) -; increase Pokedex counter by 1 - ld a, [wAIPokedexCounter] - inc a - ld [wAIPokedexCounter], a - - xor a - ld [wPreviousAIFlags], a - ld [wcddb], a - ld [wcddc], a - ld [wAIRetreatedThisTurn], a - -; checks if the Player used an attack last turn -; and if it was the second attack of their card. - ld a, [wPlayerAttackingAttackIndex] - cp $ff - jr z, .check_flag - or a - jr z, .check_flag - ld a, [wPlayerAttackingCardIndex] - cp $ff - jr z, .check_flag - -; if the card is Mewtwo1, it means the Player -; used its second attack, Barrier. - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - cp MEWTWO1 - jr nz, .check_flag - ; Player used Barrier last turn - -; check if flag was already set, if so, -; reset wAIBarrierFlagCounter to $80. - ld a, [wAIBarrierFlagCounter] - bit AI_MEWTWO_MILL_F, a - jr nz, .set_flag - -; if not, increase it by 1 and check if it exceeds 2. - inc a - ld [wAIBarrierFlagCounter], a - cp 3 - jr c, .done - -; this means that the Player used Barrier -; at least 3 turns in a row. -; check if Player is running Mewtwo1-only deck, -; if so, set wAIBarrierFlagCounter flag. - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - cp MEWTWO1 - jr nz, .reset_1 - farcall CheckIfPlayerHasPokemonOtherThanMewtwo1 - jr nc, .set_flag -.reset_1 -; reset wAIBarrierFlagCounter - xor a - ld [wAIBarrierFlagCounter], a - jr .done - -.set_flag - ld a, AI_MEWTWO_MILL - ld [wAIBarrierFlagCounter], a - jr .done - -.check_flag -; increase counter by 1 if flag is set - ld a, [wAIBarrierFlagCounter] - bit AI_MEWTWO_MILL_F, a - jr z, .reset_2 - inc a - ld [wAIBarrierFlagCounter], a - jr .done - -.reset_2 -; reset wAIBarrierFlagCounter - xor a - ld [wAIBarrierFlagCounter], a -.done - ret - -; 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 - -; 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, 128 ; initial retreat score - ld [wAIScore], a - ld a, [wcdb4] - or a - jr z, .check_status - srl a - srl a - sla a - call AddToAIScore - -.check_status - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - or a - jr z, .check_ko_1 ; no status - and DOUBLE_POISONED - jr z, .check_cnf ; no poison - ld a, 2 - call AddToAIScore -.check_cnf - ld a, [hl] - and CNF_SLP_PRZ - cp CONFUSED - jr nz, .check_ko_1 - ld a, 1 - call AddToAIScore - -.check_ko_1 - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .active_cant_ko_1 - call CheckIfSelectedAttackIsUnusable - jp nc, .active_cant_use_atk - call LookForEnergyNeededForAttackInHand - jr nc, .active_cant_ko_1 - -.active_cant_use_atk - ld a, 5 - call SubFromAIScore - ld a, [wAIOpponentPrizeCount] - cp 2 - jr nc, .active_cant_ko_1 - ld a, 35 - call SubFromAIScore - -.active_cant_ko_1 - call CheckIfDefendingPokemonCanKnockOut - jr nc, .defending_cant_ko - ld a, 2 - call AddToAIScore - - call CheckIfNotABossDeckID - jr c, .check_resistance_1 - ld a, [wAIPlayerPrizeCount] - cp 2 - jr nc, .check_prize_count - ld a, $01 - ld [wAIPlayEnergyCardForRetreat], a - -.defending_cant_ko - call CheckIfNotABossDeckID - jr c, .check_resistance_1 - ld a, [wAIPlayerPrizeCount] - cp 2 - jr nc, .check_prize_count - ld a, 2 - call AddToAIScore - -.check_prize_count - ld a, [wAIOpponentPrizeCount] - cp 2 - jr nc, .check_resistance_1 - ld a, 2 - call SubFromAIScore - -.check_resistance_1 - call GetArenaCardColor - call TranslateColorToWR - ld b, a - ld a, [wAIPlayerResistance] - and b - jr z, .check_weakness_1 - ld a, 1 - call AddToAIScore - -; check bench for Pokémon that -; the defending card is not resistant to -; if one is found, skip SubFromAIScore - ld a, [wAIPlayerResistance] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop_resistance_1 - ld a, [hli] - cp $ff - jr z, .exit_loop_resistance_1 - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - call TranslateColorToWR - and b - jr nz, .loop_resistance_1 - jr .check_weakness_1 -.exit_loop_resistance_1 - ld a, 2 - call SubFromAIScore - -.check_weakness_1 - ld a, [wAIPlayerColor] - ld b, a - call GetArenaCardWeakness - and b - jr z, .check_resistance_2 - ld a, 2 - call AddToAIScore - -; check bench for Pokémon that -; is not weak to defending Pokémon -; if one is found, skip SubFromAIScore - ld a, [wAIPlayerColor] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop_weakness_1 - ld a, [hli] - cp $ff - jr z, .exit_loop_weakness_1 - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Weakness] - and b - jr nz, .loop_weakness_1 - jr .check_resistance_2 -.exit_loop_weakness_1 - ld a, 3 - call SubFromAIScore - -.check_resistance_2 - ld a, [wAIPlayerColor] - ld b, a - call GetArenaCardResistance - and b - jr z, .check_weakness_2 - ld a, 3 - call SubFromAIScore - -; check bench for Pokémon that -; is the defending Pokémon's weakness -; if none is found, skip AddToAIScore -.check_weakness_2 - ld a, [wAIPlayerWeakness] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld e, $00 -.loop_weakness_2 - inc e - ld a, [hli] - cp $ff - jr z, .check_resistance_3 - push de - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - call TranslateColorToWR - pop de - and b - jr z, .loop_weakness_2 - ld a, 2 - call AddToAIScore - - push de - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - pop de - cp PORYGON - jr nz, .check_weakness_3 - -; handle Porygon - ld a, e - call CheckIfCanDamageDefendingPokemon - jr nc, .check_weakness_3 - ld a, 10 - call AddToAIScore - jr .check_resistance_3 - -.check_weakness_3 - call GetArenaCardColor - call TranslateColorToWR - ld b, a - ld a, [wAIPlayerWeakness] - and b - jr z, .check_resistance_3 - ld a, 3 - call SubFromAIScore - -; check bench for Pokémon that -; is resistant to defending Pokémon -; if none is found, skip AddToAIScore -.check_resistance_3 - ld a, [wAIPlayerColor] - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop_resistance_2 - ld a, [hli] - cp $ff - jr z, .check_ko_2 - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Resistance] - and b - jr z, .loop_resistance_2 - ld a, 1 - call AddToAIScore - -; check bench for Pokémon that -; can KO defending Pokémon -; if none is found, skip AddToAIScore -.check_ko_2 - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld c, 0 -.loop_ko_1 - inc c - ld a, [hli] - cp $ff - jr z, .check_defending_id - ld a, c - ldh [hTempPlayAreaLocation_ff9d], a - push hl - push bc - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .no_ko - call CheckIfSelectedAttackIsUnusable - jr nc, .success - call LookForEnergyNeededForAttackInHand - jr c, .success -.no_ko - pop bc - pop hl - jr .loop_ko_1 -.success - pop bc - pop hl - ld a, 2 - call AddToAIScore - -; a bench Pokémon was found that can KO -; if this is a boss deck and it's at last prize card -; if arena Pokémon cannot KO, add to AI score -; and set wAIPlayEnergyCardForRetreat to $01 - - ld a, [wAIOpponentPrizeCount] - cp 2 - jr nc, .check_defending_id - call CheckIfNotABossDeckID - jr c, .check_defending_id - - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .active_cant_ko_2 - call CheckIfSelectedAttackIsUnusable - jp nc, .check_defending_id -.active_cant_ko_2 - ld a, 40 - call AddToAIScore - ld a, $01 - ld [wAIPlayEnergyCardForRetreat], a - -.check_defending_id - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - cp MR_MIME - jr z, .mr_mime_or_hitmonlee - cp HITMONLEE ; ?? - jr nz, .check_retreat_cost - -; check bench if there's any Pokémon -; that can damage defending Pokémon -; this is done because of Mr. Mime's PKMN PWR -; but why Hitmonlee ($87) as well? -.mr_mime_or_hitmonlee - xor a - call CheckIfCanDamageDefendingPokemon - jr c, .check_retreat_cost - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld c, 0 -.loop_damage - inc c - ld a, [hli] - cp $ff - jr z, .check_retreat_cost - ld a, c - push hl - push bc - call CheckIfCanDamageDefendingPokemon - jr c, .can_damage - pop bc - pop hl - jr .loop_damage -.can_damage - pop bc - pop hl - ld a, 5 - call AddToAIScore - ld a, $01 - ld [wAIPlayEnergyCardForRetreat], a - -; subtract from wAIScore if retreat cost is larger than 1 -; then check if any cards have at least half HP, -; are final evolutions and can use second attack in the bench -; and adds to wAIScore if the active Pokémon doesn't meet -; these conditions -.check_retreat_cost - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call GetPlayAreaCardRetreatCost - cp 2 - jr c, .one_or_none - cp 3 - jr nc, .three_or_more - ; exactly two - ld a, 1 - call SubFromAIScore - jr .one_or_none - -.three_or_more - ld a, 2 - call SubFromAIScore - -.one_or_none - call CheckIfArenaCardIsAtHalfHPCanEvolveAndUseSecondAttack - jr c, .check_defending_can_ko - call CountNumberOfSetUpBenchPokemon - cp 2 - jr c, .check_defending_can_ko - call AddToAIScore - -; check bench for Pokémon that -; the defending Pokémon can't knock out -; if none is found, skip SubFromAIScore -.check_defending_can_ko - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld e, 0 -.loop_ko_2 - inc e - ld a, [hli] - cp $ff - jr z, .exit_loop_ko - push de - push hl - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2ID] - pop hl - pop de - cp MYSTERIOUS_FOSSIL - jr z, .loop_ko_2 - cp CLEFAIRY_DOLL - jr z, .loop_ko_2 - ld a, e - ldh [hTempPlayAreaLocation_ff9d], a - push de - push hl - call CheckIfDefendingPokemonCanKnockOut - pop hl - pop de - jr c, .loop_ko_2 - jr .check_active_id -.exit_loop_ko - ld a, 20 - call SubFromAIScore - -.check_active_id - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp MYSTERIOUS_FOSSIL - jr z, .mysterious_fossil_or_clefairy_doll - cp CLEFAIRY_DOLL - jr z, .mysterious_fossil_or_clefairy_doll - -; if wAIScore is at least 131, set carry - ld a, [wAIScore] - cp 131 - jr nc, .set_carry -.no_carry - or a - ret -.set_carry - scf - ret - -; set carry regardless if active card is -; either Mysterious Fossil or Clefairy Doll -; and there's a bench Pokémon who is not KO'd -; by defending Pokémon and can damage it -.mysterious_fossil_or_clefairy_doll - ld e, 0 -.loop_ko_3 - inc e - ld a, e - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - cp $ff - jr z, .no_carry - ld a, e - ldh [hTempPlayAreaLocation_ff9d], a - push de - call CheckIfDefendingPokemonCanKnockOut - pop de - jr c, .loop_ko_3 - ld a, e - push de - call CheckIfCanDamageDefendingPokemon - pop de - jr nc, .loop_ko_3 - jr .set_carry - -; if player's turn and loaded attack is not a Pokémon Power OR -; if opponent's turn and wcddb == 0 -; set wcdda's bit 7 flag -Func_15b54: ; 15b54 (5:5b54) - xor a - ld [wcdda], a - ld a, [wWhoseTurn] - cp OPPONENT_TURN - jr z, .opponent - -; player - ld a, [wLoadedAttackCategory] - cp POKEMON_POWER - ret z - jr .set_flag - -.opponent - ld a, [wcddb] - or a - ret nz - -.set_flag - ld a, %10000000 - ld [wcdda], a - ret - -; calculates AI score for bench Pokémon -; returns in a and [hTempPlayAreaLocation_ff9d] the -; Play Area location of best card to switch to. -; returns carry if no Bench Pokemon. -AIDecideBenchPokemonToSwitchTo: ; 15b72 (5:5b72) - xor a - ldh [hTempPlayAreaLocation_ff9d], a - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 2 - ret c - -; has at least 2 Pokémon in Play Area - call Func_15b54 - call LoadDefendingPokemonColorWRAndPrizeCards - ld a, 50 - ld [wAIScore], a - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - ld b, a - ld c, PLAY_AREA_ARENA - push bc - jp .store_score - -.next_bench - push bc - ld a, c - ldh [hTempPlayAreaLocation_ff9d], a - ld a, 50 - ld [wAIScore], a - -; check if card can KO defending Pokémon -; if it can, raise AI score -; if on last prize card, raise AI score again - call CheckIfAnyAttackKnocksOutDefendingCard - jr nc, .check_can_use_atks - call CheckIfSelectedAttackIsUnusable - jr c, .check_can_use_atks - ld a, 10 - call AddToAIScore - ld a, [wcdda] - or %00000001 - ld [wcdda], a - call CountPrizes - cp 2 - jp nc, .check_defending_weak - ld a, 10 - call AddToAIScore - -; calculates damage of both attacks -; to raise AI score accordingly -.check_can_use_atks - xor a - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - call nc, .HandleAttackDamageScore - ld a, $01 - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - call nc, .HandleAttackDamageScore - jr .check_energy_card - -; adds to AI score depending on amount of damage -; it can inflict to the defending Pokémon -; AI score += floor(Damage / 10) + 1 -.HandleAttackDamageScore - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - call CalculateByteTensDigit - inc a - call AddToAIScore - ret - -; if an energy card that is needed is found in hand -; calculate damage of the move and raise AI score -; AI score += floor(Damage / 20) -.check_energy_card - call LookForEnergyNeededInHand - jr nc, .check_attached_energy - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - call CalculateByteTensDigit - srl a - call AddToAIScore - -; if no energies attached to card, lower AI score -.check_attached_energy - ldh a, [hTempPlayAreaLocation_ff9d] - ld e, a - call GetPlayAreaCardAttachedEnergies - ld a, [wTotalAttachedEnergies] - or a - jr nz, .check_mr_mime - ld a, 1 - call SubFromAIScore - -; if can damage Mr Mime, raise AI score -.check_mr_mime - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call LoadCardDataToBuffer2_FromDeckIndex - call SwapTurn - cp MR_MIME - jr nz, .check_defending_weak - xor a - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - or a - jr nz, .can_damage - ld a, $01 - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - or a - jr z, .check_defending_weak -.can_damage - ld a, 5 - call AddToAIScore - -; if defending card is weak to this card, raise AI score -.check_defending_weak - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer1_FromDeckIndex - ld a, [wLoadedCard1Type] - call TranslateColorToWR - ld c, a - ld hl, wAIPlayerWeakness - and [hl] - jr z, .check_defending_resist - ld a, 3 - call AddToAIScore - -; if defending card is resistant to this card, lower AI score -.check_defending_resist - ld a, c - ld hl, wAIPlayerResistance - and [hl] - jr z, .check_resistance - ld a, 2 - call SubFromAIScore - -; if this card is resistant to defending Pokémon, raise AI score -.check_resistance - ld a, [wAIPlayerColor] - ld hl, wLoadedCard1Resistance - and [hl] - jr z, .check_weakness - ld a, 2 - call AddToAIScore - -; if this card is weak to defending Pokémon, lower AI score -.check_weakness - ld a, [wAIPlayerColor] - ld hl, wLoadedCard1Weakness - and [hl] - jr z, .check_retreat_cost - ld a, 3 - call SubFromAIScore - -; if this card's retreat cost < 2, raise AI score -; if this card's retreat cost > 2, lower AI score -.check_retreat_cost - call GetPlayAreaCardRetreatCost - cp 2 - jr c, .one_or_none - jr z, .check_player_prize_count - ld a, 1 - call SubFromAIScore - jr .check_player_prize_count -.one_or_none - ld a, 1 - call AddToAIScore - -; if wcdda != $81 -; if defending Pokémon can KO this card -; if player is not at last prize card, lower 3 from AI score -; if player is at last prize card, lower 10 from AI score -.check_player_prize_count - ld a, [wcdda] - cp %10000000 | %00000001 - jr z, .check_hp - call CheckIfDefendingPokemonCanKnockOut - jr nc, .check_hp - ld e, 3 - ld a, [wAIPlayerPrizeCount] - cp 1 - jr nz, .lower_score_1 - ld e, 10 -.lower_score_1 - ld a, e - call SubFromAIScore - -; if this card's HP is 0, make AI score 0 -.check_hp - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - or a - jr nz, .add_hp_score - ld [wAIScore], a - jr .store_score - -; AI score += floor(HP/40) -.add_hp_score - ld b, a - ld a, 4 - call CalculateBDividedByA_Bank5 - call CalculateByteTensDigit - call AddToAIScore - -; raise AI score if -; - is a Mr Mime OR -; - is a Mew1 and defending card is not basic stage - ldh a, [hTempPlayAreaLocation_ff9d] - add DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call LoadCardDataToBuffer1_FromDeckIndex - cp MR_MIME - jr z, .raise_score - cp MEW1 - jr nz, .asm_15cf0 - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call LoadCardDataToBuffer2_FromDeckIndex - ld a, [wLoadedCard2Stage] - or a - jr z, .asm_15cf0 -.raise_score - ld a, 5 - call AddToAIScore - -; if wLoadedCard1Unknown2 == $01, lower AI score -.asm_15cf0 - ld a, [wLoadedCard1Unknown2] - cp $01 - jr nz, .mysterious_fossil_or_clefairy_doll - ld a, 2 - call SubFromAIScore - -; if card is Mysterious Fossil or Clefairy Doll, -; lower AI score -.mysterious_fossil_or_clefairy_doll - ld a, [wLoadedCard1ID] - cp MYSTERIOUS_FOSSIL - jr z, .lower_score_2 - cp CLEFAIRY_DOLL - jr nz, .ai_score_bonus -.lower_score_2 - ld a, 10 - call SubFromAIScore - -.ai_score_bonus - ld b, a - ld a, [wAICardListRetreatBonus + 1] - or a - jr z, .store_score - ld h, a - ld a, [wAICardListRetreatBonus] - ld l, a - -.loop_ids - ld a, [hli] - or a - jr z, .store_score ; list is over - cp b - jr nz, .next_id - ld a, [hl] - cp $80 - jr c, .subtract_score - sub $80 - call AddToAIScore - jr .next_id -.subtract_score - ld c, a - ld a, $80 - sub c - call SubFromAIScore -.next_id - inc hl - jr .loop_ids - -.store_score - ldh a, [hTempPlayAreaLocation_ff9d] - ld c, a - ld b, $00 - ld hl, wPlayAreaAIScore - add hl, bc - ld a, [wAIScore] - ld [hl], a - pop bc - inc c - dec b - jp nz, .next_bench - -; done - xor a - ld [wcdb4], a - jp FindHighestBenchScore - -; handles AI action of retreating Arena Pokémon -; and chooses which energy cards to discard. -; if card can't discard, return carry. -; in case it's Clefairy Doll or Mysterious Fossil, -; handle its effect to discard itself instead of retreating. -; input: -; - a = Play Area location (PLAY_AREA_*) of card to retreat to. -AITryToRetreat: ; 15d4f (5:5d4f) - push af - ld a, [wAIPlayEnergyCardForRetreat] - or a - jr z, .check_id - -; AI is allowed to play an energy card -; from the hand in order to provide -; the necessary energy for retreat cost - -; check status - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and CNF_SLP_PRZ - cp ASLEEP - jp z, .check_id - cp PARALYZED - jp z, .check_id - -; if an energy card hasn't been played yet, -; checks if the Pokémon needs just one more energy to retreat -; if it does, check if there are any energy cards in hand -; and if there are, play that energy card - ld a, [wAlreadyPlayedEnergy] - or a - jr nz, .check_id - ld e, PLAY_AREA_ARENA - call CountNumberOfEnergyCardsAttached - push af - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call GetPlayAreaCardRetreatCost - pop bc - cp b - jr c, .check_id - jr z, .check_id - ; energy attached < retreat cost - sub b - cp 1 - jr nz, .check_id - call CreateEnergyCardListFromHand - jr c, .check_id - ld a, [wDuelTempList] - ldh [hTemp_ffa0], a - xor a - ldh [hTempPlayAreaLocation_ffa1], a - ld a, OPPACTION_PLAY_ENERGY - bank1call AIMakeDecision - -.check_id - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp MYSTERIOUS_FOSSIL - jp z, .mysterious_fossil_or_clefairy_doll - cp CLEFAIRY_DOLL - jp z, .mysterious_fossil_or_clefairy_doll - -; if card is Asleep or Paralyzed, set carry and exit -; else, load the status in hTemp_ffa0 - pop af - ldh [hTempPlayAreaLocation_ffa1], a - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - ld b, a - and CNF_SLP_PRZ - cp ASLEEP - jp z, .set_carry - cp PARALYZED - jp z, .set_carry - ld a, b - ldh [hTemp_ffa0], a - ld a, $ff - ldh [hTempRetreatCostCards], a - -; check energy required to retreat -; if the cost is 0, retreat right away - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call GetPlayAreaCardRetreatCost - ld [wTempCardRetreatCost], a - or a - jp z, .retreat - -; if cost > 0 and number of energy cards attached == cost -; discard them all - xor a - call CreateArenaOrBenchEnergyCardList - ld e, PLAY_AREA_ARENA - call GetPlayAreaCardAttachedEnergies - ld a, [wTotalAttachedEnergies] - ld c, a - ld a, [wTempCardRetreatCost] - cp c - jr nz, .choose_energy_discard - - ld hl, hTempRetreatCostCards - ld de, wDuelTempList -.loop_1 - ld a, [de] - inc de - ld [hli], a - cp $ff - jr nz, .loop_1 - jp .retreat - -; if cost > 0 and number of energy cards attached > cost -; choose energy cards to discard according to color -.choose_energy_discard - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - ld [wTempCardID], a - call LoadCardDataToBuffer1_FromCardID - ld a, [wLoadedCard1Type] - or TYPE_ENERGY - ld [wTempCardType], a - ld a, [wTempCardRetreatCost] - ld c, a - -; first, look for and discard double colorless energy -; if retreat cost is >= 2 - ld hl, wDuelTempList - ld de, hTempRetreatCostCards -.loop_2 - ld a, c - cp 2 - jr c, .energy_not_same_color - ld a, [hli] - cp $ff - jr z, .energy_not_same_color - ld [de], a - push de - call GetCardIDFromDeckIndex - ld a, e - pop de - cp DOUBLE_COLORLESS_ENERGY - jr nz, .loop_2 - ld a, [de] - call RemoveCardFromDuelTempList - dec hl - inc de - dec c - dec c - jr nz, .loop_2 - jr .end_retreat_list - -; second, shuffle attached cards and discard energy cards -; that are not of the same type as the Pokémon -; the exception for this are cards that are needed for -; some attacks but are not of the same color as the Pokémon -; (i.e. Psyduck's Headache attack) -; and energy cards attached to Eevee corresponding to a -; color of any of its evolutions (water, fire, lightning) -.energy_not_same_color - ld hl, wDuelTempList - call CountCardsInDuelTempList - call ShuffleCards -.loop_3 - ld a, [hli] - cp $ff - jr z, .any_energy - ld [de], a - call CheckIfEnergyIsUseful - jr c, .loop_3 - ld a, [de] - call RemoveCardFromDuelTempList - dec hl - inc de - dec c - jr nz, .loop_3 - jr .end_retreat_list - -; third, discard any card until -; cost requirement is met -.any_energy - ld hl, wDuelTempList -.loop_4 - ld a, [hli] - cp $ff - jr z, .set_carry - ld [de], a - inc de - push de - call GetCardIDFromDeckIndex - ld a, e - pop de - cp DOUBLE_COLORLESS_ENERGY - jr nz, .not_double_colorless - dec c - jr z, .end_retreat_list -.not_double_colorless - dec c - jr nz, .loop_4 - -.end_retreat_list - ld a, $ff - ld [de], a - -.retreat - ld a, OPPACTION_ATTEMPT_RETREAT - bank1call AIMakeDecision - or a - ret -.set_carry - scf - ret - -; handle Mysterious Fossil and Clefairy Doll -; if there are bench Pokémon, use effect to discard card -; this is equivalent to using its Pokémon Power -.mysterious_fossil_or_clefairy_doll - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 2 - jr nc, .has_bench - ; doesn't have any bench - pop af - jr .set_carry - -.has_bench - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ldh [hTempCardIndex_ff9f], a - xor a - ldh [hTemp_ffa0], a - ld a, OPPACTION_USE_PKMN_POWER - bank1call AIMakeDecision - pop af - ldh [hAIPkmnPowerEffectParam], a - ld a, OPPACTION_EXECUTE_PKMN_POWER_EFFECT - bank1call AIMakeDecision - ld a, OPPACTION_DUEL_MAIN_SCENE - bank1call AIMakeDecision - or a - ret - -; 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 - -; 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 Func_161d5 - -; 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, 128 - ld [wAIScore], a - call Func_16120 - -; 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 -Func_16120: ; 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 -Func_161d5: ; 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 - -; 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 - -; 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 - -; have AI choose an attack to use, but do not execute it. -; return carry if an attack is chosen. -AIProcessButDontUseAttack: ; 169ca (5:69ca) - ld a, $01 - ld [wAIExecuteProcessedAttack], a - -; backup wPlayAreaAIScore in wTempPlayAreaAIScore. - ld de, wTempPlayAreaAIScore - ld hl, wPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - -; copies wAIScore to wTempAIScore - ld a, [wAIScore] - ld [de], a - jr AIProcessAttacks - -; copies wTempPlayAreaAIScore to wPlayAreaAIScore -; and loads wAIScore with value in wTempAIScore. -; identical to RetrievePlayAreaAIScoreFromBackup1. -RetrievePlayAreaAIScoreFromBackup2: ; 169e3 (5:69e3) - push af - ld de, wPlayAreaAIScore - ld hl, wTempPlayAreaAIScore - ld b, MAX_PLAY_AREA_POKEMON -.loop - ld a, [hli] - ld [de], a - inc de - dec b - jr nz, .loop - - ld a, [hl] - ld [wAIScore], a - pop af - ret - -; have AI choose and execute an attack. -; return carry if an attack was chosen and attempted. -AIProcessAndTryToUseAttack: ; 169f8 (5:69f8) - xor a - ld [wAIExecuteProcessedAttack], a - ; fallthrough - -; checks which of the Active card's attacks for AI to use. -; If any of the attacks has enough AI score to be used, -; AI will use it if wAIExecuteProcessedAttack is 0. -; in either case, return carry if an attack is chosen to be used. -AIProcessAttacks: ; 169fc (5:69fc) -; if AI used Pluspower, load its attack index - ld a, [wPreviousAIFlags] - and AI_FLAG_USED_PLUSPOWER - jr z, .no_pluspower - ld a, [wAIPluspowerAttack] - ld [wSelectedAttack], a - jr .attack_chosen - -.no_pluspower -; if Player is running Mewtwo1 mill deck, -; skip attack if Barrier counter is 0. - ld a, [wAIBarrierFlagCounter] - cp AI_MEWTWO_MILL + 0 - jp z, .dont_attack - -; determine AI score of both attacks. - xor a ; FIRST_ATTACK_OR_PKMN_POWER - call GetAIScoreOfAttack - ld a, [wAIScore] - ld [wFirstAttackAIScore], a - ld a, SECOND_ATTACK - call GetAIScoreOfAttack - -; compare both attack scores - ld c, SECOND_ATTACK - ld a, [wFirstAttackAIScore] - ld b, a - ld a, [wAIScore] - cp b - jr nc, .check_score - ; first attack has higher score - dec c - ld a, b - -; c holds the attack index chosen by AI, -; and a holds its AI score. -; first check if chosen attack has at least minimum score. -; then check if first attack is better than second attack -; in case the second one was chosen. -.check_score - cp $50 ; minimum score to use attack - jr c, .dont_attack - ; enough score, proceed - - ld a, c - ld [wSelectedAttack], a - or a - jr z, .attack_chosen - call CheckWhetherToSwitchToFirstAttack - -.attack_chosen -; check whether to execute the attack chosen - ld a, [wAIExecuteProcessedAttack] - or a - jr z, .execute - -; set carry and reset Play Area AI score -; to the previous values. - scf - jp RetrievePlayAreaAIScoreFromBackup2 - -.execute - ld a, AI_TRAINER_CARD_PHASE_14 - call AIProcessHandTrainerCards - -; load this attack's damage output against -; the current Defending Pokemon. - xor a ; PLAY_AREA_ARENA - ldh [hTempPlayAreaLocation_ff9d], a - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - - or a - jr z, .check_damage_bench - ; if damage is not 0, fallthrough - -.can_damage - xor a - ld [wcdb4], a - jr .use_attack - -.check_damage_bench -; check if it can otherwise damage player's bench - ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F - call CheckLoadedAttackFlag - jr c, .can_damage - -; cannot damage either Defending Pokemon or Bench - ld hl, wcdb4 - inc [hl] - -; return carry if attack is chosen -; and AI tries to use it. -.use_attack - ld a, $01 - ld [wcddb], a - call AITryUseAttack - scf - ret - -.dont_attack - ld a, [wAIExecuteProcessedAttack] - or a - jr z, .failed_to_use - -; reset Play Area AI score -; to the previous values. - jp RetrievePlayAreaAIScoreFromBackup2 - -; return no carry if no viable attack. -.failed_to_use - ld hl, wcdb4 - inc [hl] - or a - ret - -; determines the AI score of attack index in a -; of card in Play Area location hTempPlayAreaLocation_ff9d. -GetAIScoreOfAttack: ; 16a86 (5:6a86) -; initialize AI score. - ld [wSelectedAttack], a - ld a, $50 - ld [wAIScore], a - - xor a - ldh [hTempPlayAreaLocation_ff9d], a - call CheckIfSelectedAttackIsUnusable - jr nc, .usable - -; return zero AI score. -.unusable - xor a - ld [wAIScore], a - jp .done - -; load arena card IDs -.usable - xor a - ld [wAICannotDamage], a - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - ld [wTempTurnDuelistCardID], a - call SwapTurn - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - ld [wTempNonTurnDuelistCardID], a - -; handle the case where the player has No Damage substatus. -; in the case the player does, check if this attack -; has a residual effect, or if it can damage the opposing bench. -; If none of those are true, render the attack unusable. -; also if it's a PKMN power, consider it unusable as well. - bank1call HandleNoDamageOrEffectSubstatus - call SwapTurn - jr nc, .check_if_can_ko - - ; player is under No Damage substatus - ld a, $01 - ld [wAICannotDamage], a - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, [wLoadedAttackCategory] - cp POKEMON_POWER - jr z, .unusable - and RESIDUAL - jr nz, .check_if_can_ko - ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F - call CheckLoadedAttackFlag - jr nc, .unusable - -; calculate damage to player to check if attack can KO. -; encourage attack if it's able to KO. -.check_if_can_ko - ld a, [wSelectedAttack] - call EstimateDamage_VersusDefendingCard - ld a, DUELVARS_ARENA_CARD_HP - call GetNonTurnDuelistVariable - ld hl, wDamage - sub [hl] - jr c, .can_ko - jr z, .can_ko - jr .check_damage -.can_ko - ld a, 20 - call AddToAIScore - -; raise AI score by the number of damage counters that this attack deals. -; if no damage is dealt, subtract AI score. in case wDamage is zero -; but wMaxDamage is not, then encourage attack afterwards. -; otherwise, if wMaxDamage is also zero, check for damage against -; player's bench, and encourage attack in case there is. -.check_damage - xor a - ld [wAIAttackIsNonDamaging], a - ld a, [wDamage] - ld [wTempAI], a - or a - jr z, .no_damage - call CalculateByteTensDigit - call AddToAIScore - jr .check_recoil -.no_damage - ld a, $01 - ld [wAIAttackIsNonDamaging], a - call SubFromAIScore - ld a, [wAIMaxDamage] - or a - jr z, .no_max_damage - ld a, 2 - call AddToAIScore - xor a - ld [wAIAttackIsNonDamaging], a -.no_max_damage - ld a, ATTACK_FLAG1_ADDRESS | DAMAGE_TO_OPPONENT_BENCH_F - call CheckLoadedAttackFlag - jr nc, .check_recoil - ld a, 2 - call AddToAIScore - -; handle recoil attacks (low and high recoil). -.check_recoil - ld a, ATTACK_FLAG1_ADDRESS | LOW_RECOIL_F - call CheckLoadedAttackFlag - jr c, .is_recoil - ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F - call CheckLoadedAttackFlag - jp nc, .check_defending_can_ko -.is_recoil - ; sub from AI score number of damage counters - ; that attack deals to itself. - ld a, [wLoadedAttackEffectParam] - or a - jp z, .check_defending_can_ko - ld [wDamage], a - call ApplyDamageModifiers_DamageToSelf - ld a, e - call CalculateByteTensDigit - call SubFromAIScore - - push de - ld a, ATTACK_FLAG1_ADDRESS | HIGH_RECOIL_F - call CheckLoadedAttackFlag - pop de - jr c, .high_recoil - - ; if LOW_RECOIL KOs self, decrease AI score - ld a, DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - cp e - jr c, .kos_self - jp nz, .check_defending_can_ko -.kos_self - ld a, 10 - call SubFromAIScore - -.high_recoil - ; dismiss this attack if no benched Pokémon - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp 2 - jr c, .dismiss_high_recoil_atk - ; has benched Pokémon - -; here the AI handles high recoil attacks differently -; depending on what deck it's playing. - ld a, [wOpponentDeckID] - cp ROCK_CRUSHER_DECK_ID - jr z, .rock_crusher_deck - cp ZAPPING_SELFDESTRUCT_DECK_ID - jr z, .zapping_selfdestruct_deck - cp BOOM_BOOM_SELFDESTRUCT_DECK_ID - jr z, .encourage_high_recoil_atk - ; Boom Boom Selfdestruct deck always encourages - cp POWER_GENERATOR_DECK_ID - jr nz, .high_recoil_generic_checks - ; Power Generator deck always dismisses - -.dismiss_high_recoil_atk - xor a - ld [wAIScore], a - jp .done - -.encourage_high_recoil_atk - ld a, 20 - call AddToAIScore - jp .done - -; Zapping Selfdestruct deck only uses this attack -; if number of cards in deck >= 30 and -; HP of active card is < half max HP. -.zapping_selfdestruct_deck - ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK - call GetTurnDuelistVariable - cp 31 - jr nc, .high_recoil_generic_checks - ld e, PLAY_AREA_ARENA - call GetCardDamageAndMaxHP - sla a - cp c - jr c, .high_recoil_generic_checks - ld b, 0 - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp MAGNEMITE1 - jr z, .magnemite1 - ld b, 10 ; bench damage -.magnemite1 - ld a, 10 - add b - ld b, a ; 20 bench damage if not Magnemite1 - -; if this attack causes player to win the duel by -; knocking out own Pokémon, dismiss attack. - ld a, 1 ; count active Pokémon as KO'd - call .check_if_kos_bench - jr c, .dismiss_high_recoil_atk - jr .encourage_high_recoil_atk - -; Rock Crusher Deck only uses this attack if -; prize count is below 4 and attack wins (or potentially draws) the duel, -; (i.e. at least gets KOs equal to prize cards left). -.rock_crusher_deck - call CountPrizes - cp 4 - jr nc, .dismiss_high_recoil_atk - ; prize count < 4 - ld b, 20 ; damage dealt to bench - call SwapTurn - xor a - call .check_if_kos_bench - call SwapTurn - jr c, .encourage_high_recoil_atk - -; generic checks for all other deck IDs. -; encourage attack if it wins (or potentially draws) the duel, -; (i.e. at least gets KOs equal to prize cards left). -; dismiss it if it causes the player to win. -.high_recoil_generic_checks - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - call GetCardIDFromDeckIndex - ld a, e - cp CHANSEY - jr z, .chansey - cp MAGNEMITE1 - jr z, .magnemite1_or_weezing - cp WEEZING - jr z, .magnemite1_or_weezing - ld b, 20 ; bench damage - jr .check_bench_kos -.magnemite1_or_weezing - ld b, 10 ; bench damage - jr .check_bench_kos -.chansey - ld b, 0 ; no bench damage - -.check_bench_kos - push bc - call SwapTurn - xor a - call .check_if_kos_bench - call SwapTurn - pop bc - jr c, .wins_the_duel - push de - ld a, 1 - call .check_if_kos_bench - pop bc - jr nc, .count_own_ko_bench - -; attack causes player to draw all prize cards - xor a - ld [wAIScore], a - jp .done - -; attack causes CPU to draw all prize cards -.wins_the_duel - ld a, 20 - call AddToAIScore - jp .done - -; subtract from AI score number of own benched Pokémon KO'd -.count_own_ko_bench - push bc - ld a, d - or a - jr z, .count_player_ko_bench - dec a - call SubFromAIScore - -; add to AI score number of player benched Pokémon KO'd -.count_player_ko_bench - pop bc - ld a, b - call AddToAIScore - jr .check_defending_can_ko - -; local function that gets called to determine damage to -; benched Pokémon caused by a HIGH_RECOIL attack. -; return carry if using attack causes number of benched Pokémon KOs -; equal to or larger than remaining prize cards. -; this function is independent on duelist turn, so whatever -; turn it is when this is called, it's that duelist's -; bench/prize cards that get checked. -; input: -; a = initial number of KO's beside benched Pokémon, -; so that if the active Pokémon is KO'd by the attack, -; this counts towards the prize cards collected -; b = damage dealt to bench Pokémon -.check_if_kos_bench - ld d, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - ld e, PLAY_AREA_ARENA -.loop - inc e - ld a, [hli] - cp $ff - jr z, .exit_loop - ld a, e - add DUELVARS_ARENA_CARD_HP - push hl - call GetTurnDuelistVariable - pop hl - cp b - jr z, .increase_count - jr nc, .loop -.increase_count - ; increase d if damage dealt KOs - inc d - jr .loop -.exit_loop - push de - call SwapTurn - call CountPrizes - call SwapTurn - pop de - cp d - jp c, .set_carry - jp z, .set_carry - or a - ret -.set_carry - scf - ret - -; if defending card can KO, encourage attack -; unless attack is non-damaging. -.check_defending_can_ko - ld a, [wSelectedAttack] - push af - call CheckIfDefendingPokemonCanKnockOut - pop bc - ld a, b - ld [wSelectedAttack], a - jr nc, .check_discard - ld a, 5 - call AddToAIScore - ld a, [wAIAttackIsNonDamaging] - or a - jr z, .check_discard - ld a, 5 - call SubFromAIScore - -; subtract from AI score if this attack requires -; discarding any energy cards. -.check_discard - ld a, [wSelectedAttack] - ld e, a - ld a, DUELVARS_ARENA_CARD - call GetTurnDuelistVariable - ld d, a - call CopyAttackDataAndDamage_FromDeckIndex - ld a, ATTACK_FLAG2_ADDRESS | DISCARD_ENERGY_F - call CheckLoadedAttackFlag - jr nc, .asm_16ca6 - ld a, 1 - call SubFromAIScore - ld a, [wLoadedAttackEffectParam] - call SubFromAIScore - -.asm_16ca6 - ld a, ATTACK_FLAG2_ADDRESS | FLAG_2_BIT_6_F - call CheckLoadedAttackFlag - jr nc, .check_nullify_flag - ld a, [wLoadedAttackEffectParam] - call AddToAIScore - -; encourage attack if it has a nullify or weaken attack effect. -.check_nullify_flag - ld a, ATTACK_FLAG2_ADDRESS | NULLIFY_OR_WEAKEN_ATTACK_F - call CheckLoadedAttackFlag - jr nc, .check_draw_flag - ld a, 1 - call AddToAIScore - -; encourage attack if it has an effect to draw a card. -.check_draw_flag - ld a, ATTACK_FLAG1_ADDRESS | DRAW_CARD_F - call CheckLoadedAttackFlag - jr nc, .check_heal_flag - ld a, 1 - call AddToAIScore - -.check_heal_flag - ld a, ATTACK_FLAG2_ADDRESS | HEAL_USER_F - call CheckLoadedAttackFlag - jr nc, .check_status_effect - ld a, [wLoadedAttackEffectParam] - cp 1 - jr z, .tally_heal_score - ld a, [wTempAI] - call CalculateByteTensDigit - ld b, a - ld a, [wLoadedAttackEffectParam] - cp 3 - jr z, .asm_16cec - srl b - jr nc, .asm_16cec - inc b -.asm_16cec - ld a, DUELVARS_ARENA_CARD_HP - call GetTurnDuelistVariable - call CalculateByteTensDigit - cp b - jr c, .tally_heal_score - ld a, b -.tally_heal_score - push af - ld e, PLAY_AREA_ARENA - call GetCardDamageAndMaxHP - call CalculateByteTensDigit - pop bc - cp b ; wLoadedAttackEffectParam - jr c, .add_heal_score - ld a, b -.add_heal_score - call AddToAIScore - -.check_status_effect - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable - call SwapTurn - call GetCardIDFromDeckIndex - call SwapTurn - ld a, e - ; skip if player has Snorlax - cp SNORLAX - jp z, .handle_flag3_bit1 - - 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_flag3_bit1 - ld a, 1 - call SubFromAIScore - -; flag3_bit1 marks attacks that the AI handles individually. -; each attack has its own checks and modifies AI score accordingly. -.handle_flag3_bit1 - ld a, ATTACK_FLAG3_ADDRESS | FLAG_3_BIT_1_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 - -; this function handles attacks with the FLAG_3_BIT_1 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, HandleNidoranFCallForFamily - cp ODDISH - jr z, HandleCallForFamily - cp BELLSPROUT - jr z, HandleCallForFamily - cp EXEGGUTOR - jp z, HandleExeggutorTeleport - cp SCYTHER - jp z, HandleSwordsDanceAndFocusEnergy - cp KRABBY - jr z, HandleCallForFamily - cp VAPOREON1 - jp z, HandleSwordsDanceAndFocusEnergy - cp ELECTRODE2 - jp z, HandleElectrode2ChainLightning - cp MAROWAK1 - jr z, HandleMarowak1CallForFriend - cp MEW3 - jp z, HandleMew3DevolutionBeam - cp JIGGLYPUFF2 - jp z, HandleJigglypuff2FriendshipSong - cp PORYGON - jp z, HandlePorygonConversion - cp MEWTWO3 - jp z, HandleEnergyAbsorption - cp MEWTWO2 - jp z, HandleEnergyAbsorption - cp NINETAILS2 - jp z, HandleNinetalesMixUp - cp ZAPDOS3 - jp z, HandleZapdos3BigThunder - cp KANGASKHAN - jp z, HandleKangaskhanFetch - cp DUGTRIO - jp z, HandleDugtrioEarthquake - cp ELECTRODE1 - jp z, HandleElectrode1EnergySpike - cp GOLDUCK - jp z, HandleHyperBeam - cp DRAGONAIR - jp z, HandleHyperBeam - -; return zero score. -.zero - xor a - ret - -; if any of card ID in a is found in deck, -; return a score of $80 + slots available in bench. -HandleCallForFamily: ; 16e3e (5:6e3e) - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr nc, HandleSpecialAIAttacks.zero - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_BENCH_POKEMON - jr nc, HandleSpecialAIAttacks.zero - 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. -HandleNidoranFCallForFamily: ; 16e55 (5:6e55) - ld e, NIDORANM - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found - ld e, NIDORANF - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr nc, HandleSpecialAIAttacks.zero -.found - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_PLAY_AREA_POKEMON - jr nc, HandleSpecialAIAttacks.zero - 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. -HandleMarowak1CallForFriend: ; 16e77 (5:6e77) - ld e, GEODUDE - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found - ld e, ONIX - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found - ld e, CUBONE - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found - ld e, RHYHORN - ld a, CARD_LOCATION_DECK - call CheckIfAnyCardIDinLocation - jr c, .found - jr HandleSpecialAIAttacks.zero -.found - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_BENCH_POKEMON - jr nc, HandleSpecialAIAttacks.zero - 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. -HandleJigglypuff2FriendshipSong: ; 16ead (5:6ead) - call CheckIfAnyBasicPokemonInDeck - jr nc, HandleSpecialAIAttacks.zero - ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA - call GetTurnDuelistVariable - cp MAX_PLAY_AREA_POKEMON - jr nc, HandleSpecialAIAttacks.zero - 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. -HandleExeggutorTeleport: ; 16ec2 (5:6ec2) - call AIDecideWhetherToRetreat - jp nc, HandleSpecialAIAttacks.zero - 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. -HandleSwordsDanceAndFocusEnergy: ; 16ecb (5:6ecb) - ld a, [wAICannotDamage] - or a - jr nz, .success - ld a, SECOND_ATTACK - ld [wSelectedAttack], a - call CheckIfSelectedAttackIsUnusable - jr c, .success - ld a, SECOND_ATTACK - call EstimateDamage_VersusDefendingCard - ld a, [wDamage] - or a - jp nz, HandleSpecialAIAttacks.zero -.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. -HandleElectrode2ChainLightning: ; 16eea (5:6eea) - call SwapTurn - call GetArenaCardColor - call SwapTurn - ld b, a - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable -.loop - ld a, [hli] - cp $ff - jr z, .success - push bc - call GetCardIDFromDeckIndex - call GetCardType - pop bc - cp b - jr nz, .loop - jp HandleSpecialAIAttacks.zero -.success - ld a, $82 - ret - -HandleMew3DevolutionBeam: ; 16f0f (5:6f0f) - call LookForCardThatIsKnockedOutOnDevolution - jp nc, HandleSpecialAIAttacks.zero - 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. -HandlePorygonConversion: ; 16f18 (5:6f18) - ld a, DUELVARS_ARENA_CARD_STATUS - call GetTurnDuelistVariable - and CNF_SLP_PRZ - cp CONFUSED - jp z, HandleSpecialAIAttacks.zero - - ld a, [wSelectedAttack] - or a - jr nz, .conversion_2 - -; conversion 1 - call CountNumberOfSetUpBenchPokemon - cp 2 - jr c, .low_score - ld a, $82 - ret - -.conversion_2 - call CountNumberOfSetUpBenchPokemon - cp 2 - jr nc, .low_score - ld a, $82 - ret - -.low_score - ld a, $81 - ret - -; if any Psychic Energy is found in the Discard Pile, -; return a score of $80 + 2. -HandleEnergyAbsorption: ; 16f41 (5:6f41) - ld e, PSYCHIC_ENERGY - ld a, CARD_LOCATION_DISCARD_PILE - call CheckIfAnyCardIDinLocation - jp nc, HandleSpecialAIAttacks.zero - 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. -HandleNinetalesMixUp: ; 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 - 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, .check_play_area - - ld hl, wDuelTempList - ld b, 0 -.loop_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_hand - ld a, [wLoadedCard2Stage] - or a - jr nz, .loop_hand - ; is a basic Pokémon card - inc b - jr .loop_hand -.tally_basic_cards - ld a, b - cp 2 - jr nc, .encourage - -; less than 2 basic cards in hand -.check_play_area - ld a, DUELVARS_ARENA_CARD - call GetNonTurnDuelistVariable -.loop_play_area - ld a, [hli] - cp $ff - jp z, HandleSpecialAIAttacks.zero - push hl - call SwapTurn - call CheckForEvolutionInList - call SwapTurn - pop hl - jr nc, .loop_play_area - -.encourage - ld a, $83 - ret - -; return score of $80 + 3. -HandleZapdos3BigThunder: ; 16fb8 (5:6fb8) - ld a, $83 - ret - -; dismiss attack if cards in deck <= 20. -; otherwise return a score of $80 + 0. -HandleKangaskhanFetch: ; 16fbb (5:6fbb) - ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK - call GetTurnDuelistVariable - cp 41 - jp nc, HandleSpecialAIAttacks.zero - 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. -HandleDugtrioEarthquake: ; 16fc8 (5:6fc8) - ld a, DUELVARS_BENCH - call GetTurnDuelistVariable - - lb de, 0, 0 -.loop - 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 - inc d - jr .loop - -.count_prizes - push de - call CountPrizes - pop de - cp d - jp c, HandleSpecialAIAttacks.zero - jp z, HandleSpecialAIAttacks.zero - ld a, $80 - ret - -; if there's any lightning energy cards in deck, -; return a score of $80 + 3. -HandleElectrode1EnergySpike: ; 16ff2 (5:6ff2) - ld a, CARD_LOCATION_DECK - ld e, LIGHTNING_ENERGY - call CheckIfAnyCardIDinLocation - jp nc, HandleSpecialAIAttacks.zero - call AIProcessButDontPlayEnergy_SkipEvolution - jp nc, HandleSpecialAIAttacks.zero - 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. -HandleHyperBeam: ; 17005 (5:7005) - call SwapTurn - ld e, PLAY_AREA_ARENA - call CountNumberOfEnergyCardsAttached - call SwapTurn - or a - jr z, .keep_score - ld a, $83 - ret -.keep_score - 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 - -; 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, .devolution_beam - cp MEWTWO3 - jr z, .energy_absorption - cp MEWTWO2 - jr z, .energy_absorption - cp EXEGGUTOR - jr z, .teleport - cp ELECTRODE1 - jr z, .energy_spike - ; fallthrough - -.no_carry - or a - ret - -.devolution_beam -; 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 - -.energy_absorption -; 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 - -.energy_spike -; 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 - -; 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 - -; 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/layout.link b/src/layout.link index 7a0a42a..75f199e 100644 --- a/src/layout.link +++ b/src/layout.link @@ -39,6 +39,8 @@ ROMX $03 "Bank 3" ROMX $04 "Bank 4" +ROMX $05 + "AI Logic 1" ROMX $06 "Bank 6" ROMX $07 @@ -46,7 +48,7 @@ ROMX $07 "Credits Sequence" "Booster Packs" ROMX $08 - "AI Logic" + "AI Logic 2" ROMX $0b "Effect Functions" ROMX $0c diff --git a/src/main.asm b/src/main.asm index e16c9ab..8c36282 100644 --- a/src/main.asm +++ b/src/main.asm @@ -15,8 +15,9 @@ INCLUDE "engine/bank03.asm" SECTION "Bank 4", ROMX INCLUDE "engine/bank04.asm" -SECTION "Bank 5", ROMX -INCLUDE "engine/bank05.asm" +SECTION "AI Logic 1", ROMX +INCLUDE "data/deck_ai_pointers.asm" +INCLUDE "engine/ai/core.asm" SECTION "Bank 6", ROMX INCLUDE "engine/bank06.asm" @@ -31,7 +32,7 @@ INCLUDE "data/sequences/credits_sequence.asm" SECTION "Booster Packs", ROMX INCLUDE "engine/booster_packs.asm" -SECTION "AI Logic", ROMX +SECTION "AI Logic 2", ROMX INCLUDE "engine/ai/trainer_cards.asm" INCLUDE "engine/ai/pkmn_powers.asm" INCLUDE "engine/ai/common.asm" -- cgit v1.2.3