diff options
Diffstat (limited to 'src/engine/ai/core.asm')
-rw-r--r-- | src/engine/ai/core.asm | 2770 |
1 files changed, 2770 insertions, 0 deletions
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 |