summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorElectroDeoxys <ElectroDeoxys@gmail.com>2020-04-30 16:04:07 +0100
committerElectroDeoxys <ElectroDeoxys@gmail.com>2020-04-30 16:04:07 +0100
commitcf9a3c313b63c78ebcb4077e827eeff9a5940ba0 (patch)
treeebdf7b58783fe0ae943b486a76727eb40499e433 /src
parent47736d7c415ea5b38f5d9df83e7fb1e9b12a9e9e (diff)
Unpack Legendary Moltres AI
Diffstat (limited to 'src')
-rw-r--r--src/constants/duel_constants.asm2
-rw-r--r--src/engine/bank05.asm513
-rw-r--r--src/wram.asm9
3 files changed, 507 insertions, 17 deletions
diff --git a/src/constants/duel_constants.asm b/src/constants/duel_constants.asm
index b667c50..9cdc73b 100644
--- a/src/constants/duel_constants.asm
+++ b/src/constants/duel_constants.asm
@@ -3,6 +3,8 @@ MAX_PLAY_AREA_POKEMON EQU 6 ; arena + bench
MAX_HP EQU 120
HP_BAR_LENGTH EQU MAX_HP / 10
+STARTING_HAND_SIZE EQU 7
+
; hWhoseTurn constants
PLAYER_TURN EQUS "HIGH(wPlayerDuelVariables)"
OPPONENT_TURN EQUS "HIGH(wOpponentDuelVariables)"
diff --git a/src/engine/bank05.asm b/src/engine/bank05.asm
index bb77536..92fc0aa 100644
--- a/src/engine/bank05.asm
+++ b/src/engine/bank05.asm
@@ -11,7 +11,7 @@ PointerTable_14000: ; 14000 (05:4000)
dw PointerTable_14668 ; LIGHTNING_AND_FIRE_DECK
dw PointerTable_14668 ; WATER_AND_FIGHTING_DECK
dw PointerTable_14668 ; GRASS_AND_PSYCHIC_DECK
- dw $49e8 ; LEGENDARY_MOLTRES_DECK
+ dw PointerTable_149e8 ; LEGENDARY_MOLTRES_DECK
dw $4b0f ; LEGENDARY_ZAPDOS_DECK
dw $4c0b ; LEGENDARY_ARTICUNO_DECK
dw $4d60 ; LEGENDARY_DRAGONITE_DECK
@@ -1658,7 +1658,245 @@ AIPerformSciptedTurn: ; 1483a (5:483a)
; 0x148dc
Func_148dc: ; 148dc (5:48dc)
- INCROM $148dc, $14c91
+ INCROM $148dc, $149e8
+
+PointerTable_149e8: ; 149e8 (05:49e8)
+ dw Func_149f4
+ dw Func_149f4
+ dw Func_149f8
+ dw Func_14a09
+ dw Func_14a0d
+ dw Func_14a11
+
+Func_149f4: ; 149f4 (5:49f4)
+ call Func_14a81
+ ret
+; 0x149f8
+
+Func_149f8: ; 149f8 (5:49f8)
+ call InitAIDuelVars
+ call Func_14a4a
+ call SetUpBossStartingHandAndDeck
+ call TrySetUpBossStartingPlayArea
+ ret nc ; Play Area set up was successful
+ call AIPlayInitialBasicCards
+ ret
+; 0x14a09
+
+Func_14a09: ; 14a09 (5:4a09)
+ call AIDecideBenchPokemonToSwitchTo
+ ret
+; 0x14a0d
+
+Func_14a0d: ; 14a0d (5:4a0d)
+ call AIDecideBenchPokemonToSwitchTo
+ ret
+; 0x14a11
+
+Func_14a11: ; 14a11 (5:4a11)
+ call _AIPickPrizeCards
+ ret
+; 0x14a15
+
+Data_14a15: ; 14a15 (5:4a15)
+ db MAGMAR2
+ db GROWLITHE
+ db VULPIX
+ db MAGMAR1
+ db MOLTRES1
+ db MOLTRES2
+ db $00
+
+Data_14a1c: ; 14a1c (5:4a1c)
+ db MOLTRES1
+ db VULPIX
+ db GROWLITHE
+ db MAGMAR2
+ db MAGMAR1
+ db $00
+
+Data_14a22: ; 14a22 (5:4a22)
+ db MOLTRES2
+ db MOLTRES1
+ db VULPIX
+ db GROWLITHE
+ db MAGMAR2
+ db MAGMAR1
+ db $00
+
+Data_14a29: ; 14a29 (5:4a29)
+ db GROWLITHE
+ db $80 - 5
+ db VULPIX
+ db $80 - 5
+ db $00
+
+Data_14a2e: ; 14a2e (5:4a2e)
+ db VULPIX
+ db 3
+ db $80 + 0
+
+ db NINETAILS2
+ db 3
+ db $80 + 1
+
+ db GROWLITHE
+ db 3
+ db $80 + 1
+
+ db ARCANINE2
+ db 4
+ db $80 + 1
+
+ db MAGMAR1
+ db 4
+ db $80 - 1
+
+ db MAGMAR2
+ db 1
+ db $80 - 1
+
+ db MOLTRES2
+ db 3
+ db $80 + 2
+
+ db MOLTRES1
+ db 4
+ db $80 + 2
+
+ db $00
+
+Data_14a47: ; 14a47 (5:4a47)
+ db ENERGY_REMOVAL
+ db MOLTRES2
+ db $00
+
+Func_14a4a: ; 14a4a (5:4a4a)
+ ld hl, wcda8
+ ld de, Data_14a47
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ ld hl, wcdaa
+ ld de, Data_14a15
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ ld hl, wcdac
+ ld de, Data_14a1c
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ ld hl, wcdae
+ ld de, Data_14a22
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ ld hl, wcdb0
+ ld de, Data_14a29
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ ld hl, wcdb2
+ ld de, Data_14a2e
+ ld [hl], e
+ inc hl
+ ld [hl], d
+
+ ret
+; 0x14a81
+
+Func_14a81: ; 14a81 (5:4a81)
+ call InitAITurnVars
+ farcall Func_227d3
+ jp nc, .try_attack
+
+ ld a, AI_TRAINER_CARD_PHASE_02
+ call AIProcessHandTrainerCards
+ ld a, AI_TRAINER_CARD_PHASE_04
+ call AIProcessHandTrainerCards
+
+; check if AI can play Moltres2 from hand
+; if so, play it.
+ ld a, DUELVARS_NUMBER_OF_POKEMON_IN_PLAY_AREA
+ call GetTurnDuelistVariable
+ cp MAX_PLAY_AREA_POKEMON
+ jr nc, .skip_moltres ; skip if bench is full
+ ld a, DUELVARS_NUMBER_OF_CARDS_NOT_IN_DECK
+ call GetTurnDuelistVariable
+ cp DECK_SIZE - 9
+ jr nc, .skip_moltres ; skip if cards in deck <= 9
+ ld a, MUK
+ call CountPokemonIDInBothPlayAreas
+ jr c, .skip_moltres ; skip if Muk in play
+ ld a, MOLTRES2
+ call LookForCardIDInHandList_Bank5
+ jr nc, .skip_moltres ; skip if no Moltres2 in hand
+ ldh [hTemp_ffa0], a
+ ld a, OPPACTION_PLAY_BASIC_PKMN
+ bank1call AIMakeDecision
+
+.skip_moltres
+ call AIDecidePlayPokemonCard
+ ret c
+ ld a, AI_TRAINER_CARD_PHASE_05
+ call AIProcessHandTrainerCards
+ call Func_14786
+ ld a, AI_TRAINER_CARD_PHASE_10
+ call AIProcessHandTrainerCards
+ ld a, AI_TRAINER_CARD_PHASE_11
+ call AIProcessHandTrainerCards
+
+; handle attaching energy from hand
+ ld a, [wAlreadyPlayedEnergy]
+ or a
+ jr nz, .skip_attach_energy
+
+; if Magmar2 is the Arena card and has no energy attached,
+; try attaching an energy card to it from the hand.
+; otherwise, run normal AI energy attach routine.
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call GetCardIDFromDeckIndex
+ ld a, MAGMAR2
+ cp e
+ jr nz, .attach_normally
+ ; Magmar2 is the Arena card
+ call CreateEnergyCardListFromHand
+ jr c, .skip_attach_energy
+ ld e, PLAY_AREA_ARENA
+ call CountNumberOfEnergyCardsAttached
+ or a
+ jr nz, .attach_normally
+ xor a ; PLAY_AREA_ARENA
+ ldh [hTempPlayAreaLocation_ff9d], a
+ call AITryToPlayEnergyCard
+ jr c, .skip_attach_energy
+
+.attach_normally
+ call AIProcessAndTryToPlayEnergy
+
+.skip_attach_energy
+; try playing Pokemon cards from hand again
+ call AIDecidePlayPokemonCard
+ ld a, AI_TRAINER_CARD_PHASE_13
+ call AIProcessHandTrainerCards
+
+.try_attack
+ call AIProcessAndTryToUseAttack
+ ret c
+ ld a, OPPACTION_FINISH_NO_ATTACK
+ bank1call AIMakeDecision
+ ret
+; 0x14b0f
+
+Func_14b0f: ; 14b0f (5:4b0f)
+ INCROM $14b0f, $14c91
; this routine handles how Legendary Articuno
; prioritises playing energy cards to each Pokémon.
@@ -2279,8 +2517,82 @@ RemoveCardIDInList: ; 157f3 (5:57f3)
ret
; 0x1581b
-Func_1581b: ; 1581b (5:581b)
- INCROM $1581b, $158b2
+; 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, wcdaa
+ ld a, d
+ or a
+ jr z, .set_carry ; return if null
+
+; pick Arena card
+ call CreateHandCardList
+ ld hl, wDuelTempList
+ ld de, wcdaa
+ call .PlayPokemonCardInOrder
+ ret c
+
+; play Pokemon cards to Bench until there are
+; a maximum of 3 cards in Play Area.
+.loop
+ ld de, wcdac
+ call .PlayPokemonCardInOrder
+ jr c, .done
+ cp 3
+ jr c, .loop
+
+.done
+ or a
+ ret
+.set_carry
+ scf
+ ret
+; 0x1583f
+
+; 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
+; 0x1585b
+
+Func_1585b: ; 1585b (5:585b)
+ INCROM $1585b, $158b2
; determine AI score for retreating
; return carry if AI decides to retreat
@@ -3009,26 +3321,26 @@ AIDecideBenchPokemonToSwitchTo: ; 15b72 (5:5b72)
ld a, [wcdb0]
ld l, a
-.loop
+.loop_ids
ld a, [hli]
or a
- jr z, .store_score
+ jr z, .store_score ; list is over
cp b
- jr nz, .asm_15d32
+ jr nz, .next_id
ld a, [hl]
cp $80
jr c, .asm_15d2b
sub $80
call AddToAIScore
- jr .asm_15d32
+ jr .next_id
.asm_15d2b
ld c, a
ld a, $80
sub c
call SubFromAIScore
-.asm_15d32
+.next_id
inc hl
- jr .loop
+ jr .loop_ids
.store_score
ldh a, [hTempPlayAreaLocation_ff9d]
@@ -4126,19 +4438,19 @@ LookForEnergyNeededForMoveInHand: ; 16311 (5:6311)
SortTempHandByIDList: ; 1633f (5:633f)
ld a, [wcdae+1]
or a
- ret z
+ ret z ; return if list is empty
; start going down the ID list
ld d, a
ld a, [wcdae]
ld e, a
ld c, 0
-.next_list_id
+.loop_list_id
; get this item's ID
; if $00, list has ended
ld a, [de]
or a
- ret z
+ ret z ; return when list is over
inc de
ld hl, wDuelTempList
ld b, 0
@@ -4150,7 +4462,7 @@ SortTempHandByIDList: ; 1633f (5:633f)
ld a, [hl]
ldh [hTempCardIndex_ff98], a
cp -1
- jr z, .next_list_id
+ jr z, .loop_list_id
push bc
push de
call GetCardIDFromDeckIndex
@@ -4703,6 +5015,7 @@ AIProcessEnergyCards: ; 164fc (5:64fc)
pop de
cp d
jr c, .check_id_score
+ ; already reached target number of energy cards
ld a, 10
call SubFromAIScore
jr .store_score
@@ -7159,8 +7472,176 @@ CheckCardEvolutionInHandOrDeck: ; 17274 (5:7274)
ret
; 0x172af
-Func_172af ; 172af (5:72af)
- INCROM $172af, $17383
+; sets up the inital 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
+; and avoids deck-specific list of cards.
+SetUpBossStartingHandAndDeck: ; 172af (5:72af)
+; shuffle all hand cards in deck
+ ld a, DUELVARS_HAND
+ call GetTurnDuelistVariable
+ ld b, STARTING_HAND_SIZE
+.asm_172b6
+ ld a, [hl]
+ call RemoveCardFromHand
+ call ReturnCardToDeck
+ dec b
+ jr nz, .asm_172b6
+ 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.
+; re-shuffle deck if any of these cards is listed in wcda8.
+ 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
+; 0x17366
+
+; expectation: return carry if card ID corresponding
+; to the input deck index is listed in wcda8;
+; reality: always returns no carry because when checking terminating
+; byte in wcda8 ($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, [wcda8 + 1]
+ or a
+ ret z ; null
+ push hl
+ ld h, a
+ ld a, [wcda8]
+ 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
+; 0x17383
; returns carry if Pokemon at PLAY_AREA* in a
; can damage defending Pokémon with any of its moves
diff --git a/src/wram.asm b/src/wram.asm
index 534297e..cae7f88 100644
--- a/src/wram.asm
+++ b/src/wram.asm
@@ -1195,7 +1195,14 @@ wAIPokedexCounter:: ; cda6
wcda7:: ; cda7
ds $1
- ds $6
+wcda8:: ; cda8
+ ds $2
+
+wcdaa:: ; cdaa
+ ds $2
+
+wcdac:: ; cdac
+ ds $2
; pointer to a list of card IDs for sorting AI hand
wcdae:: ; cdae