From e6763371e4fcb26da70709fe0566ab4a8d1d2083 Mon Sep 17 00:00:00 2001 From: Martha Schilling Date: Fri, 5 Jan 2024 13:59:27 +0000 Subject: [PATCH] AI Improvements This fixes some of the issues with the current AI system to make them better. In short: - Most Trainers should correctly recognise when a move is not very effective, and not use it. If they have a supereffective move and a regular-effective move, they will slightly prioritise the supereffective one, but not always use it like in regular RBY. This prevents the Lorelei softlock, for example. - Trainers won't attempt to set status effects on Pokemon that already have one, won't try recovering at full health, and won't attempt to set up multiple Reflects or Light Screen - Certain high-level trainers will recognise when a Pokemon does not have a status, and will try to inflict one if so. This makes Agatha's, Erika's and Koga's team types much more effective. - Youngsters and Cue Balls no longer pick moves randomly and will actually give it some thought - Brock and the Engineers now recognize type effectiveness, Students do not given how early they're encountered. - General improvements to move choices for all Gym Leaders, E4 and Shinjuku Jacky. We're almost done. --- README.md | 2 +- data/predef_pointers.asm | 2 +- data/trainers/move_choices.asm | 50 ++++---- engine/battle/trainer_ai.asm | 145 ++++++++++++----------- engine/battle/unused_stats_functions.asm | 81 ++++++++----- main.asm | 4 +- 6 files changed, 157 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 855d2203..c02def00 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,7 @@ and more! Known Bugs ==== -- The new AI (possibly bugged?) behaves quite oddly, such as using Recover at full HP, spamming status-inflicting moves like Lovely Kiss, neglecting supereffective moves, setting up multiple Reflects, etc. +- Currently testing... Evolution Methods for new Pokemon ==== diff --git a/data/predef_pointers.asm b/data/predef_pointers.asm index 559d990d..eba3a0e4 100644 --- a/data/predef_pointers.asm +++ b/data/predef_pointers.asm @@ -50,7 +50,7 @@ PredefPointers:: add_predef UpdateHPBar add_predef HPBarLength add_predef Diploma_TextBoxBorder -; add_predef DoubleOrHalveSelectedStats + add_predef DoubleOrHalveSelectedStats add_predef ShowPokedexMenu add_predef EvolutionAfterBattle add_predef SaveSAVtoSRAM0 diff --git a/data/trainers/move_choices.asm b/data/trainers/move_choices.asm index 306c7c7c..7facfddb 100644 --- a/data/trainers/move_choices.asm +++ b/data/trainers/move_choices.asm @@ -9,7 +9,7 @@ ENDM ; move choice modification methods that are applied for each trainer class TrainerClassMoveChoiceModifications: list_start TrainerClassMoveChoiceModifications - move_choices ; YOUNGSTER + move_choices 1 ; YOUNGSTER move_choices 1 ; BUG CATCHER move_choices 1 ; LASS move_choices 1, 3 ; SAILOR @@ -20,10 +20,10 @@ TrainerClassMoveChoiceModifications: move_choices 1 ; HIKER move_choices 1 ; BIKER move_choices 1, 3 ; BURGLAR - move_choices 1 ; ENGINEER + move_choices 1, 3 ; ENGINEER move_choices 1, 3 ; FISHER move_choices 1, 3 ; SWIMMER - move_choices ; CUE_BALL + move_choices 1 ; CUE_BALL move_choices 1 ; GAMBLER move_choices 1, 3 ; BEAUTY move_choices 1, 2 ; PSYCHIC_TR @@ -33,32 +33,32 @@ TrainerClassMoveChoiceModifications: move_choices 1 ; BIRD_KEEPER move_choices 1 ; BLACKBELT move_choices 1 ; RIVAL1 - move_choices 1, 3 ; PROF_OAK - move_choices 1, 2 ; CHIEF - move_choices 1, 2 ; SCIENTIST - move_choices 1, 3 ; GIOVANNI + move_choices 1, 2, 3, 4 ; PROF_OAK + move_choices 1, 2, 3, 4 ; CHIEF + move_choices 1, 2, 4 ; SCIENTIST + move_choices 1, 2, 3 ; GIOVANNI move_choices 1 ; ROCKET - move_choices 1, 3 ; COOLTRAINER_M - move_choices 1, 3 ; COOLTRAINER_F - move_choices 1 ; BRUNO - move_choices 1 ; BROCK - move_choices 1, 3 ; MISTY - move_choices 1, 3 ; LT_SURGE - move_choices 1, 3 ; ERIKA - move_choices 1, 3 ; KOGA - move_choices 1, 3 ; BLAINE - move_choices 1, 3 ; SABRINA + move_choices 1, 3, 4 ; COOLTRAINER_M + move_choices 1, 3, 4 ; COOLTRAINER_F + move_choices 1, 2, 3 ; BRUNO + move_choices 1, 3 ; BROCK + move_choices 1, 3, 4 ; MISTY + move_choices 1, 2, 3 ; LT_SURGE + move_choices 1, 3, 4 ; ERIKA + move_choices 1, 3, 4 ; KOGA + move_choices 1, 2, 3 ; BLAINE + move_choices 1, 3, 4 ; SABRINA move_choices 1, 2 ; GENTLEMAN move_choices 1, 3 ; RIVAL2 - move_choices 1, 3 ; RIVAL3 - move_choices 1, 2, 3 ; LORELEI + move_choices 1, 2, 3, 4 ; RIVAL3 + move_choices 1, 2, 3, 4 ; LORELEI move_choices 1 ; CHANNELER - move_choices 1 ; AGATHA - move_choices 1, 3 ; LANCE - move_choices 1, 3, ; YUJIROU, was UNUSED_JUGGLER - move_choices 1, 3, ; STUDENT + move_choices 1, 2, 3, 4 ; AGATHA + move_choices 1, 2, 3, 4 ; LANCE + move_choices 1, 3, ; YUJIROU + move_choices 1 ; STUDENT move_choices 1, 3, ; FIREFIGHTER - move_choices 1, 3, ; KOICHI - move_choices 1, 3, ; JACK + move_choices 1, 2, 3 ; KOICHI + move_choices 1, 2, 3, 4 ; JACK move_choices 1, 3, ; JESSIE_JAMES assert_list_length NUM_TRAINERS diff --git a/engine/battle/trainer_ai.asm b/engine/battle/trainer_ai.asm index dca83aa9..1223359c 100644 --- a/engine/battle/trainer_ai.asm +++ b/engine/battle/trainer_ai.asm @@ -7,6 +7,18 @@ AIEnemyTrainerChooseMoves: ld [hli], a ; move 2 ld [hli], a ; move 3 ld [hl], a ; move 4 + +;;;;;;;;;; shinpokerednote: ADDED: make a backup buffer + push hl + ld a, $ff + inc hl + ld [hli], a ;backup 1 + ld [hli], a ;backup 2 + ld [hli], a ;backup 3 + ld [hl], a ;backup 4 + pop hl +;;;;;;;;;; + ld a, [wEnemyDisabledMove] ; forbid disabled move (if any) swap a and $f @@ -52,6 +64,11 @@ AIEnemyTrainerChooseMoves: ld de, .nextMoveChoiceModification ; set return address push de jp hl ; execute modification function +.loopFindMinimumEntries_backupfirst ;shinpokerednote: ADDED: make a backup of the scores + ld hl, wBuffer ; temp move selection array + ld de, wBuffer + NUM_MOVES ;backup buffer + ld bc, NUM_MOVES + rst _CopyData .loopFindMinimumEntries ; all entries will be decremented sequentially until one of them is zero ld hl, wBuffer ; temp move selection array ld de, wEnemyMonMoves ; enemy moves @@ -466,10 +483,8 @@ AIMoveChoiceModification3: jr c, .notEffectiveMove ;ld a, [wEnemyMoveEffect] ; check for reasons not to use a super effective move here - dec [hl] ; slightly encourage this super effective move .checkSpecificEffects ; we'll further encourage certain moves - call EncouragePriorityIfSlow call EncourageDrainingMoveIfLowHealth jr .nextMove .notEffectiveMove ; discourages non-effective moves if better moves are available @@ -479,7 +494,7 @@ AIMoveChoiceModification3: ld a, [wEnemyMoveType] ld d, a ld hl, wEnemyMonMoves ; enemy moves - ld b, NUM_MOVES + 1 + ld bc, NUM_MOVES + 1 ld c, $0 .loopMoves dec b @@ -543,18 +558,6 @@ CompareSpeed: ret ;;;;;;;;;; -; PureRGBnote: ADDED: encourages priority moves if the enemy's pokemon is slower than the player's and the move is neutral or super effective. -; BUT this effect is only applied after you have the soulbadge to prevent priority moves from being spammed early game. -; Applies to trainers that use AI subroutine 3 -EncouragePriorityIfSlow: - ld a, [wObtainedBadges] - bit BIT_SOULBADGE, a - ret z - call CompareSpeed - ret nc - dec [hl] ; encourage the move if it's a priority move and the pokemon is slower - ret - ; PureRGBnote: ADDED: if the opponent has less than 1/2 health they will prefer healing moves if they use AI subroutine 3 EncourageDrainingMoveIfLowHealth: ld a, [wEnemyMoveEffect] @@ -576,11 +579,11 @@ AIMoveChoiceModification4: ld b, NUM_MOVES + 1 .nextMove dec b - jr z, .done ; processed all 4 moves + ret z ; processed all 4 moves inc hl ld a, [de] and a - jr z, .done ; no more moves in move set + ret z ; no more moves in move set inc de call ReadMove ld a, [wEnemyMoveEffect] @@ -626,8 +629,7 @@ AIMoveChoiceModification4: jr z, .nextMove ; if the AI thinks the player IS NOT asleep before they switch, we shouldn't encourage based on the new mon's status ld a, [wBattleMonStatus] and SLP_MASK - jr nz, .preferMoveEvenMore ; heavier favor for dream eater if the opponent is asleep - jr .nextMove + jr z, .nextMove .preferMoveEvenMore dec [hl] jr .preferMove @@ -676,9 +678,6 @@ TrainerAI: ld a, [wIsInBattle] dec a ret z ; if not a trainer, we're done here - ld a, [wCurMap] - cp BATTLE_TENT - ret z ; if we are in battle tent, we are done ld a, [wLinkState] cp LINK_STATE_BATTLING ret z ; if in a link battle, we're done as well @@ -732,12 +731,9 @@ BlackbeltAI: jp AIUseXAttack GiovanniAI: - cp 20 percent + 1 + cp 25 percent + 1 ret nc - ld a, [wEnemyBattleStatus2] - bit GETTING_PUMPED, a - ret nz - jp AIUseDireHit + jp AIUseXAttack ; Used to use a Guard Spec. This will make the item use have a proper impact - healing doesn't feel right for a trainer fixated on strength. CooltrainerMAI: cp 20 percent + 1 @@ -765,41 +761,46 @@ BrockAI: jp AIUseFullHeal MistyAI: - cp 20 percent + 1 + cp 25 percent + 1 ret nc - jp AIUseXDefend + ld a, 10 + call AICheckIfHPBelowFraction + ret nc + jp AIUseSuperPotion ; Replicates Starmie using Recover. Unlike other trainers that heal, Misty will do this 26% of the time instead of 51%. LtSurgeAI: - cp 10 percent + 1 + cp 20 percent + 1 ret nc - jp AIUseXSpeed + jp AIUseXSpecial ErikaAI: cp 50 percent + 1 ret nc - ld a, 5 + ld a, 10 call AICheckIfHPBelowFraction ret nc jp AIUseSuperPotion KogaAI: - cp 10 percent + 1 + cp 50 percent + 1 ret nc - jp AIUseXAttack + ld a, 10 + call AICheckIfHPBelowFraction + ret nc + jp AIUseSuperPotion ; Koga is weird - I don't think anything fits. X Attack is certainly not the move though... -BlaineAI: ;blaine needs to check HP. this was an oversight - cp 40 percent + 1 - jr nc, .blainereturn - ld a, 2 - call AICheckIfHPBelowFraction - jp c, AIUseSuperPotion -.blainereturn - ret +BlaineAI: + cp 25 percent + 1 + ret nc + ld a, 10 + call AICheckIfHPBelowFraction + ret nc ; this fixes the super potion thing - PvK + jp AIUseHyperPotion ; Instead of a Super Potion though, let's give him this. More impactful for the sixth gym while staying true to the meme that everyone knows Gen 1 Blaine for. SabrinaAI: cp 25 percent + 1 ret nc - ld a, 5 + ld a, 10 call AICheckIfHPBelowFraction ret nc jp AIUseHyperPotion @@ -810,7 +811,7 @@ Rival2AI: ld a, 5 call AICheckIfHPBelowFraction ret nc - jp AIUseHyperPotion + jp AIUseSuperPotion Rival3AI: cp 40 percent - 1 @@ -821,43 +822,51 @@ Rival3AI: jp AIUseFullRestore LoreleiAI: - cp 50 percent + 1 + cp 15 percent + 1 + ret nc + jp AIUseXSpecial + cp 40 percent + 1 ret nc ld a, 5 call AICheckIfHPBelowFraction ret nc - jp AIUseHyperPotion + jp AIUseFullRestore BrunoAI: - ;cp 10 percent + 1 - ;ret nc - ;jp AIUseXDefend - cp 30 percent + 1 - jr nc, .brunoreturn + cp 15 percent + 1 + ret nc + jp AIUseXAttack + cp 40 percent + 1 + ret nc ld a, 5 call AICheckIfHPBelowFraction - jp c, AIUseHyperPotion -.brunoreturn - ret + ret nc + jp AIUseFullRestore AgathaAI: cp 8 percent jp c, AISwitchIfEnoughMons - cp 50 percent + 1 + cp 15 percent + 1 ret nc - ld a, 4 - call AICheckIfHPBelowFraction - ret nc - jp AIUseHyperPotion - -LanceAI: - cp 50 percent + 1 + jp AIUseXAccuracy ; hahahahahahahaha + cp 40 percent + 1 ret nc ld a, 5 call AICheckIfHPBelowFraction ret nc jp AIUseFullRestore +LanceAI: + cp 15 percent + 1 + ret nc + jp AIUseXSpecial + cp 50 percent + 1 + ret nc + ld a, 10 + call AICheckIfHPBelowFraction + ret nc + jp AIUseFullRestore + GenericAI: and a ; clear carry ret @@ -1087,12 +1096,12 @@ AICureStatus: ;shinpokerednote: CHANGED: modified to be more robust and also und res BADLY_POISONED, [hl] ;clear toxic bit ret -;AIUseXAccuracy: ; unused -; call AIPlayRestoringSFX -; ld hl, wEnemyBattleStatus2 -; set 0, [hl] -; ld a, X_ACCURACY -; jp AIPrintItemUse +AIUseXAccuracy: + call AIPlayRestoringSFX + ld hl, wEnemyBattleStatus2 + set 0, [hl] + ld a, X_ACCURACY + jp AIPrintItemUse ;AIUseGuardSpec: ; PureRGBnote: CHANGED: now unused ; call AIPlayRestoringSFX diff --git a/engine/battle/unused_stats_functions.asm b/engine/battle/unused_stats_functions.asm index d9f637ac..fc07098d 100644 --- a/engine/battle/unused_stats_functions.asm +++ b/engine/battle/unused_stats_functions.asm @@ -1,4 +1,54 @@ -; Used by the pureRGB AI +;shinpokerednote: ADDED: doubles the given stat +DoubleSelectedStats: + ldh a, [hWhoseTurn] + and a + ld a, [wPlayerStatsToDouble] + ld hl, wBattleMonAttack + jr z, .notEnemyTurn + ld a, [wEnemyStatsToDouble] + ld hl, wEnemyMonAttack +.notEnemyTurn + ld c, 4 + ld b, a +.loop + srl b + call c, .doubleStat + inc hl + inc hl + dec c + ret z + jr .loop +.doubleStat + push bc + ld a, [hli] + ld b, a + ld c, [hl] ; bc holds value of stat to double +;double the stat + sla c + rl b +;cap stat at 999 + ;b register contains high byte & c register contains low byte + ld a, c ;let's work on low byte first. Note that decimal 999 is $03E7 in hex. + sub 999 % $100 ;a = a - ($03E7 % $100). Gives a = a - $E7. A byte % $100 always gives the lesser nibble. + ;Note that if a < $E7 then the carry bit 'c' in the flag register gets set due to overflowing with a negative result. + ld a, b ;now let's work on the high byte + sbc 999 / $100 ;a = a - ($03E7 / $100 + c_flag). Gives a = a - ($03 + c_flag). A byte / $100 always gives the greater nibble. + ;Note again that if a < $03 then the carry bit remains set. + ;If the bit is already set from the lesser nibble, then its addition here can still make it remain set if a is low enough. + jr c, .donecapping ;jump to next marker if the c_flag is set. This only remains set if BC < the cap of $03E7. + ;else let's continue and set the 999 cap + ld a, 999 / $100 ; else load $03 into a + ld b, a ;and store it as the high byte + ld a, 999 % $100 ; else load $E7 into a + ld c, a ;and store it as the low byte + ;now registers b & c together contain $03E7 for a capped stat value of 999 +.donecapping + ld a, c + ld [hld], a + ld [hl], b + pop bc + ret + ;shinpokerednote: ADDED: doubles attack if burned or quadruples speed if paralyzed. ;It's meant to be run right before healing paralysis or burn so as to ;undo the stat changes. @@ -31,35 +81,6 @@ UndoBurnParStats: ld [de], a ;reset the stat change bits ret -; Reused for pureRGB AI -DoubleSelectedStats: - ldh a, [hWhoseTurn] - and a - ld a, [wPlayerStatsToDouble] - ld hl, wBattleMonAttack + 1 - jr z, .notEnemyTurn - ld a, [wEnemyStatsToDouble] - ld hl, wEnemyMonAttack + 1 -.notEnemyTurn - ld c, 4 - ld b, a -.loop - srl b - call c, .doubleStat - inc hl - inc hl - dec c - ret z - jr .loop -.doubleStat - ld a, [hl] - add a - ld [hld], a - ld a, [hl] - rl a - ld [hli], a - ret - ; does nothing since no stats are ever selected (barring glitches) ;HalveSelectedStats: ; ldh a, [hWhoseTurn] diff --git a/main.asm b/main.asm index 84324740..06dd64bf 100644 --- a/main.asm +++ b/main.asm @@ -186,6 +186,8 @@ SECTION "Battle Engine 7", ROMX INCLUDE "data/moves/moves.asm" INCLUDE "data/pokemon/cries.asm" +INCLUDE "engine/battle/scroll_draw_trainer_pic.asm" +INCLUDE "engine/battle/trainer_ai.asm" INCLUDE "engine/battle/unused_stats_functions.asm" INCLUDE "engine/battle/move_effects/heal.asm" INCLUDE "engine/battle/move_effects/transform.asm" @@ -249,8 +251,6 @@ INCLUDE "engine/events/hidden_objects/indigo_plateau_hq.asm" SECTION "Battle Engine 9", ROMX -INCLUDE "engine/battle/scroll_draw_trainer_pic.asm" -INCLUDE "engine/battle/trainer_ai.asm" INCLUDE "engine/battle/draw_hud_pokeball_gfx.asm"