summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Harding <33dannye@gmail.com>2021-06-07 19:52:55 -0500
committerGitHub <noreply@github.com>2021-06-07 19:52:55 -0500
commit38c1766a56e183f036c1be795e3b5bf47ed9d67f (patch)
tree2221978851cadddf4343c16b277efd0ac6ae7b50
parent36ef80903a5cb821ddf4660276709144df67f8b6 (diff)
parent119507e28423014d1027d284ec711c68e1dbe71e (diff)
Merge pull request #108 from ElectroDeoxys/master
Split banks 5 and 8
-rw-r--r--bugs_and_glitches.md6
-rw-r--r--src/constants/card_data_constants.asm4
-rw-r--r--src/data/ai_trainer_card_logic.asm47
-rw-r--r--src/data/cards.asm50
-rw-r--r--src/engine/ai/attacks.asm721
-rw-r--r--src/engine/ai/boss_deck_set_up.asm167
-rw-r--r--src/engine/ai/common.asm970
-rw-r--r--src/engine/ai/core.asm2770
-rw-r--r--src/engine/ai/damage_calculation.asm450
-rw-r--r--src/engine/ai/deck_ai.asm (renamed from src/engine/deck_ai/deck_ai.asm)38
-rw-r--r--src/engine/ai/decks/fire_charge.asm (renamed from src/engine/deck_ai/decks/fire_charge.asm)0
-rw-r--r--src/engine/ai/decks/first_strike.asm (renamed from src/engine/deck_ai/decks/first_strike.asm)0
-rw-r--r--src/engine/ai/decks/flower_power.asm (renamed from src/engine/deck_ai/decks/flower_power.asm)0
-rw-r--r--src/engine/ai/decks/general.asm (renamed from src/engine/deck_ai/decks/general.asm)0
-rw-r--r--src/engine/ai/decks/general_no_retreat.asm (renamed from src/engine/deck_ai/decks/general_no_retreat.asm)0
-rw-r--r--src/engine/ai/decks/go_go_rain_dance.asm (renamed from src/engine/deck_ai/decks/go_go_rain_dance.asm)0
-rw-r--r--src/engine/ai/decks/im_ronald.asm (renamed from src/engine/deck_ai/decks/im_ronald.asm)0
-rw-r--r--src/engine/ai/decks/invincible_ronald.asm (renamed from src/engine/deck_ai/decks/invincible_ronald.asm)0
-rw-r--r--src/engine/ai/decks/legendary_articuno.asm (renamed from src/engine/deck_ai/decks/legendary_articuno.asm)0
-rw-r--r--src/engine/ai/decks/legendary_dragonite.asm (renamed from src/engine/deck_ai/decks/legendary_dragonite.asm)0
-rw-r--r--src/engine/ai/decks/legendary_moltres.asm (renamed from src/engine/deck_ai/decks/legendary_moltres.asm)0
-rw-r--r--src/engine/ai/decks/legendary_ronald.asm (renamed from src/engine/deck_ai/decks/legendary_ronald.asm)0
-rw-r--r--src/engine/ai/decks/legendary_zapdos.asm (renamed from src/engine/deck_ai/decks/legendary_zapdos.asm)0
-rw-r--r--src/engine/ai/decks/powerful_ronald.asm (renamed from src/engine/deck_ai/decks/powerful_ronald.asm)0
-rw-r--r--src/engine/ai/decks/rock_crusher.asm (renamed from src/engine/deck_ai/decks/rock_crusher.asm)0
-rw-r--r--src/engine/ai/decks/sams_practice.asm (renamed from src/engine/deck_ai/decks/sams_practice.asm)0
-rw-r--r--src/engine/ai/decks/strange_psyshock.asm (renamed from src/engine/deck_ai/decks/strange_psyshock.asm)0
-rw-r--r--src/engine/ai/decks/unreferenced.asm42
-rw-r--r--src/engine/ai/decks/wonders_of_science.asm (renamed from src/engine/deck_ai/decks/wonders_of_science.asm)0
-rw-r--r--src/engine/ai/decks/zapping_selfdestruct.asm (renamed from src/engine/deck_ai/decks/zapping_selfdestruct.asm)0
-rw-r--r--src/engine/ai/energy.asm1048
-rw-r--r--src/engine/ai/hand_pokemon.asm627
-rw-r--r--src/engine/ai/init.asm98
-rw-r--r--src/engine/ai/pkmn_powers.asm1228
-rw-r--r--src/engine/ai/retreat.asm1009
-rw-r--r--src/engine/ai/special_attacks.asm481
-rw-r--r--src/engine/ai/trainer_cards.asm (renamed from src/engine/bank08.asm)2260
-rw-r--r--src/engine/bank05.asm7406
-rw-r--r--src/engine/home.asm2
-rw-r--r--src/layout.link4
-rw-r--r--src/main.asm11
41 files changed, 9725 insertions, 9714 deletions
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/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/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/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/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/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/deck_ai/deck_ai.asm b/src/engine/ai/deck_ai.asm
index 97093c7..c330418 100644
--- a/src/engine/deck_ai/deck_ai.asm
+++ b/src/engine/ai/deck_ai.asm
@@ -61,22 +61,22 @@ ENDM
; 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"
+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/deck_ai/decks/fire_charge.asm b/src/engine/ai/decks/fire_charge.asm
index f5b347b..f5b347b 100644
--- a/src/engine/deck_ai/decks/fire_charge.asm
+++ b/src/engine/ai/decks/fire_charge.asm
diff --git a/src/engine/deck_ai/decks/first_strike.asm b/src/engine/ai/decks/first_strike.asm
index 2e636e1..2e636e1 100644
--- a/src/engine/deck_ai/decks/first_strike.asm
+++ b/src/engine/ai/decks/first_strike.asm
diff --git a/src/engine/deck_ai/decks/flower_power.asm b/src/engine/ai/decks/flower_power.asm
index 4d423a3..4d423a3 100644
--- a/src/engine/deck_ai/decks/flower_power.asm
+++ b/src/engine/ai/decks/flower_power.asm
diff --git a/src/engine/deck_ai/decks/general.asm b/src/engine/ai/decks/general.asm
index 039e101..039e101 100644
--- a/src/engine/deck_ai/decks/general.asm
+++ b/src/engine/ai/decks/general.asm
diff --git a/src/engine/deck_ai/decks/general_no_retreat.asm b/src/engine/ai/decks/general_no_retreat.asm
index 20d84e3..20d84e3 100644
--- a/src/engine/deck_ai/decks/general_no_retreat.asm
+++ b/src/engine/ai/decks/general_no_retreat.asm
diff --git a/src/engine/deck_ai/decks/go_go_rain_dance.asm b/src/engine/ai/decks/go_go_rain_dance.asm
index 23547e2..23547e2 100644
--- a/src/engine/deck_ai/decks/go_go_rain_dance.asm
+++ b/src/engine/ai/decks/go_go_rain_dance.asm
diff --git a/src/engine/deck_ai/decks/im_ronald.asm b/src/engine/ai/decks/im_ronald.asm
index b002d83..b002d83 100644
--- a/src/engine/deck_ai/decks/im_ronald.asm
+++ b/src/engine/ai/decks/im_ronald.asm
diff --git a/src/engine/deck_ai/decks/invincible_ronald.asm b/src/engine/ai/decks/invincible_ronald.asm
index 463560b..463560b 100644
--- a/src/engine/deck_ai/decks/invincible_ronald.asm
+++ b/src/engine/ai/decks/invincible_ronald.asm
diff --git a/src/engine/deck_ai/decks/legendary_articuno.asm b/src/engine/ai/decks/legendary_articuno.asm
index 6409330..6409330 100644
--- a/src/engine/deck_ai/decks/legendary_articuno.asm
+++ b/src/engine/ai/decks/legendary_articuno.asm
diff --git a/src/engine/deck_ai/decks/legendary_dragonite.asm b/src/engine/ai/decks/legendary_dragonite.asm
index 597f72c..597f72c 100644
--- a/src/engine/deck_ai/decks/legendary_dragonite.asm
+++ b/src/engine/ai/decks/legendary_dragonite.asm
diff --git a/src/engine/deck_ai/decks/legendary_moltres.asm b/src/engine/ai/decks/legendary_moltres.asm
index d07afb9..d07afb9 100644
--- a/src/engine/deck_ai/decks/legendary_moltres.asm
+++ b/src/engine/ai/decks/legendary_moltres.asm
diff --git a/src/engine/deck_ai/decks/legendary_ronald.asm b/src/engine/ai/decks/legendary_ronald.asm
index 3356838..3356838 100644
--- a/src/engine/deck_ai/decks/legendary_ronald.asm
+++ b/src/engine/ai/decks/legendary_ronald.asm
diff --git a/src/engine/deck_ai/decks/legendary_zapdos.asm b/src/engine/ai/decks/legendary_zapdos.asm
index cc99f0c..cc99f0c 100644
--- a/src/engine/deck_ai/decks/legendary_zapdos.asm
+++ b/src/engine/ai/decks/legendary_zapdos.asm
diff --git a/src/engine/deck_ai/decks/powerful_ronald.asm b/src/engine/ai/decks/powerful_ronald.asm
index 096fbea..096fbea 100644
--- a/src/engine/deck_ai/decks/powerful_ronald.asm
+++ b/src/engine/ai/decks/powerful_ronald.asm
diff --git a/src/engine/deck_ai/decks/rock_crusher.asm b/src/engine/ai/decks/rock_crusher.asm
index 41a50fa..41a50fa 100644
--- a/src/engine/deck_ai/decks/rock_crusher.asm
+++ b/src/engine/ai/decks/rock_crusher.asm
diff --git a/src/engine/deck_ai/decks/sams_practice.asm b/src/engine/ai/decks/sams_practice.asm
index dddce61..dddce61 100644
--- a/src/engine/deck_ai/decks/sams_practice.asm
+++ b/src/engine/ai/decks/sams_practice.asm
diff --git a/src/engine/deck_ai/decks/strange_psyshock.asm b/src/engine/ai/decks/strange_psyshock.asm
index ef378b0..ef378b0 100644
--- a/src/engine/deck_ai/decks/strange_psyshock.asm
+++ b/src/engine/ai/decks/strange_psyshock.asm
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/deck_ai/decks/wonders_of_science.asm b/src/engine/ai/decks/wonders_of_science.asm
index 706a7e6..706a7e6 100644
--- a/src/engine/deck_ai/decks/wonders_of_science.asm
+++ b/src/engine/ai/decks/wonders_of_science.asm
diff --git a/src/engine/deck_ai/decks/zapping_selfdestruct.asm b/src/engine/ai/decks/zapping_selfdestruct.asm
index da5e7c6..da5e7c6 100644
--- a/src/engine/deck_ai/decks/zapping_selfdestruct.asm
+++ b/src/engine/ai/decks/zapping_selfdestruct.asm
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/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/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/bank08.asm b/src/engine/ai/trainer_cards.asm
index 4b4444c..cd87a4b 100644
--- a/src/engine/bank08.asm
+++ b/src/engine/ai/trainer_cards.asm
@@ -1,50 +1,4 @@
-; 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
+INCLUDE "data/ai_trainer_card_logic.asm"
_AIProcessHandTrainerCards: ; 200e5 (8:40e5)
ld [wce18], a
@@ -64,7 +18,7 @@ _AIProcessHandTrainerCards: ; 200e5 (8:40e5)
push hl
ld a, [wce18]
ld d, a
- ld hl, Data_20000
+ ld hl, AITrainerCardLogic
.loop_data
xor a
ld [wCurrentAIFlags], a
@@ -220,7 +174,7 @@ AIPlay_Potion: ; 201b5 (8:41b5)
AIDecide_Potion1: ; 201d1 (8:41d1)
farcall AIDecideWhetherToRetreat
jr c, .no_carry
- call Func_22bad
+ call AICheckIfAttackIsHighRecoil
jr c, .no_carry
xor a ; active card
ldh [hTempPlayAreaLocation_ff9d], a
@@ -341,10 +295,10 @@ AIDecide_Potion2: ; 20204 (8:4204)
scf
ret
-; return carry for active card if not Hgh Recoil.
+; return carry for active card if not High Recoil.
.active_card
push de
- call Func_22bad
+ call AICheckIfAttackIsHighRecoil
pop de
jr c, .no_carry
ld a, e
@@ -410,7 +364,7 @@ AIPlay_SuperPotion: ; 202a8 (8:42a8)
AIDecide_SuperPotion1: ; 202cc (8:42cc)
farcall AIDecideWhetherToRetreat
jr c, .no_carry
- call Func_22bad
+ call AICheckIfAttackIsHighRecoil
jr c, .no_carry
xor a
ldh [hTempPlayAreaLocation_ff9d], a
@@ -551,7 +505,7 @@ AIDecide_SuperPotion2: ; 2030f (8:430f)
; return carry for active card if not Hgh Recoil.
.active_card
push de
- call Func_22bad
+ call AICheckIfAttackIsHighRecoil
pop de
jr c, .no_carry
ld a, e
@@ -6117,2203 +6071,3 @@ AIDecide_PokemonTrader_Flamethrower: ; 22133 (8:6133)
.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/bank05.asm b/src/engine/bank05.asm
deleted file mode 100644
index 75ca6b9..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/deck_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/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..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
- "Bank 8"
+ "AI Logic 2"
ROMX $0b
"Effect Functions"
ROMX $0c
diff --git a/src/main.asm b/src/main.asm
index b331dc8..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,8 +32,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 2", 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"