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.
This commit is contained in:
Martha Schilling 2024-01-05 13:59:27 +00:00
parent a486ec7657
commit e6763371e4
6 changed files with 157 additions and 127 deletions

View file

@ -260,7 +260,7 @@ and more!
Known Bugs 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 Evolution Methods for new Pokemon
==== ====

View file

@ -50,7 +50,7 @@ PredefPointers::
add_predef UpdateHPBar add_predef UpdateHPBar
add_predef HPBarLength add_predef HPBarLength
add_predef Diploma_TextBoxBorder add_predef Diploma_TextBoxBorder
; add_predef DoubleOrHalveSelectedStats add_predef DoubleOrHalveSelectedStats
add_predef ShowPokedexMenu add_predef ShowPokedexMenu
add_predef EvolutionAfterBattle add_predef EvolutionAfterBattle
add_predef SaveSAVtoSRAM0 add_predef SaveSAVtoSRAM0

View file

@ -9,7 +9,7 @@ ENDM
; move choice modification methods that are applied for each trainer class ; move choice modification methods that are applied for each trainer class
TrainerClassMoveChoiceModifications: TrainerClassMoveChoiceModifications:
list_start TrainerClassMoveChoiceModifications list_start TrainerClassMoveChoiceModifications
move_choices ; YOUNGSTER move_choices 1 ; YOUNGSTER
move_choices 1 ; BUG CATCHER move_choices 1 ; BUG CATCHER
move_choices 1 ; LASS move_choices 1 ; LASS
move_choices 1, 3 ; SAILOR move_choices 1, 3 ; SAILOR
@ -20,10 +20,10 @@ TrainerClassMoveChoiceModifications:
move_choices 1 ; HIKER move_choices 1 ; HIKER
move_choices 1 ; BIKER move_choices 1 ; BIKER
move_choices 1, 3 ; BURGLAR move_choices 1, 3 ; BURGLAR
move_choices 1 ; ENGINEER move_choices 1, 3 ; ENGINEER
move_choices 1, 3 ; FISHER move_choices 1, 3 ; FISHER
move_choices 1, 3 ; SWIMMER move_choices 1, 3 ; SWIMMER
move_choices ; CUE_BALL move_choices 1 ; CUE_BALL
move_choices 1 ; GAMBLER move_choices 1 ; GAMBLER
move_choices 1, 3 ; BEAUTY move_choices 1, 3 ; BEAUTY
move_choices 1, 2 ; PSYCHIC_TR move_choices 1, 2 ; PSYCHIC_TR
@ -33,32 +33,32 @@ TrainerClassMoveChoiceModifications:
move_choices 1 ; BIRD_KEEPER move_choices 1 ; BIRD_KEEPER
move_choices 1 ; BLACKBELT move_choices 1 ; BLACKBELT
move_choices 1 ; RIVAL1 move_choices 1 ; RIVAL1
move_choices 1, 3 ; PROF_OAK move_choices 1, 2, 3, 4 ; PROF_OAK
move_choices 1, 2 ; CHIEF move_choices 1, 2, 3, 4 ; CHIEF
move_choices 1, 2 ; SCIENTIST move_choices 1, 2, 4 ; SCIENTIST
move_choices 1, 3 ; GIOVANNI move_choices 1, 2, 3 ; GIOVANNI
move_choices 1 ; ROCKET move_choices 1 ; ROCKET
move_choices 1, 3 ; COOLTRAINER_M move_choices 1, 3, 4 ; COOLTRAINER_M
move_choices 1, 3 ; COOLTRAINER_F move_choices 1, 3, 4 ; COOLTRAINER_F
move_choices 1 ; BRUNO move_choices 1, 2, 3 ; BRUNO
move_choices 1 ; BROCK move_choices 1, 3 ; BROCK
move_choices 1, 3 ; MISTY move_choices 1, 3, 4 ; MISTY
move_choices 1, 3 ; LT_SURGE move_choices 1, 2, 3 ; LT_SURGE
move_choices 1, 3 ; ERIKA move_choices 1, 3, 4 ; ERIKA
move_choices 1, 3 ; KOGA move_choices 1, 3, 4 ; KOGA
move_choices 1, 3 ; BLAINE move_choices 1, 2, 3 ; BLAINE
move_choices 1, 3 ; SABRINA move_choices 1, 3, 4 ; SABRINA
move_choices 1, 2 ; GENTLEMAN move_choices 1, 2 ; GENTLEMAN
move_choices 1, 3 ; RIVAL2 move_choices 1, 3 ; RIVAL2
move_choices 1, 3 ; RIVAL3 move_choices 1, 2, 3, 4 ; RIVAL3
move_choices 1, 2, 3 ; LORELEI move_choices 1, 2, 3, 4 ; LORELEI
move_choices 1 ; CHANNELER move_choices 1 ; CHANNELER
move_choices 1 ; AGATHA move_choices 1, 2, 3, 4 ; AGATHA
move_choices 1, 3 ; LANCE move_choices 1, 2, 3, 4 ; LANCE
move_choices 1, 3, ; YUJIROU, was UNUSED_JUGGLER move_choices 1, 3, ; YUJIROU
move_choices 1, 3, ; STUDENT move_choices 1 ; STUDENT
move_choices 1, 3, ; FIREFIGHTER move_choices 1, 3, ; FIREFIGHTER
move_choices 1, 3, ; KOICHI move_choices 1, 2, 3 ; KOICHI
move_choices 1, 3, ; JACK move_choices 1, 2, 3, 4 ; JACK
move_choices 1, 3, ; JESSIE_JAMES move_choices 1, 3, ; JESSIE_JAMES
assert_list_length NUM_TRAINERS assert_list_length NUM_TRAINERS

View file

@ -7,6 +7,18 @@ AIEnemyTrainerChooseMoves:
ld [hli], a ; move 2 ld [hli], a ; move 2
ld [hli], a ; move 3 ld [hli], a ; move 3
ld [hl], a ; move 4 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) ld a, [wEnemyDisabledMove] ; forbid disabled move (if any)
swap a swap a
and $f and $f
@ -52,6 +64,11 @@ AIEnemyTrainerChooseMoves:
ld de, .nextMoveChoiceModification ; set return address ld de, .nextMoveChoiceModification ; set return address
push de push de
jp hl ; execute modification function 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 .loopFindMinimumEntries ; all entries will be decremented sequentially until one of them is zero
ld hl, wBuffer ; temp move selection array ld hl, wBuffer ; temp move selection array
ld de, wEnemyMonMoves ; enemy moves ld de, wEnemyMonMoves ; enemy moves
@ -466,10 +483,8 @@ AIMoveChoiceModification3:
jr c, .notEffectiveMove jr c, .notEffectiveMove
;ld a, [wEnemyMoveEffect] ;ld a, [wEnemyMoveEffect]
; check for reasons not to use a super effective move here ; check for reasons not to use a super effective move here
dec [hl] ; slightly encourage this super effective move dec [hl] ; slightly encourage this super effective move
.checkSpecificEffects ; we'll further encourage certain moves .checkSpecificEffects ; we'll further encourage certain moves
call EncouragePriorityIfSlow
call EncourageDrainingMoveIfLowHealth call EncourageDrainingMoveIfLowHealth
jr .nextMove jr .nextMove
.notEffectiveMove ; discourages non-effective moves if better moves are available .notEffectiveMove ; discourages non-effective moves if better moves are available
@ -479,7 +494,7 @@ AIMoveChoiceModification3:
ld a, [wEnemyMoveType] ld a, [wEnemyMoveType]
ld d, a ld d, a
ld hl, wEnemyMonMoves ; enemy moves ld hl, wEnemyMonMoves ; enemy moves
ld b, NUM_MOVES + 1 ld bc, NUM_MOVES + 1
ld c, $0 ld c, $0
.loopMoves .loopMoves
dec b dec b
@ -543,18 +558,6 @@ CompareSpeed:
ret 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 ; PureRGBnote: ADDED: if the opponent has less than 1/2 health they will prefer healing moves if they use AI subroutine 3
EncourageDrainingMoveIfLowHealth: EncourageDrainingMoveIfLowHealth:
ld a, [wEnemyMoveEffect] ld a, [wEnemyMoveEffect]
@ -576,11 +579,11 @@ AIMoveChoiceModification4:
ld b, NUM_MOVES + 1 ld b, NUM_MOVES + 1
.nextMove .nextMove
dec b dec b
jr z, .done ; processed all 4 moves ret z ; processed all 4 moves
inc hl inc hl
ld a, [de] ld a, [de]
and a and a
jr z, .done ; no more moves in move set ret z ; no more moves in move set
inc de inc de
call ReadMove call ReadMove
ld a, [wEnemyMoveEffect] 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 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] ld a, [wBattleMonStatus]
and SLP_MASK and SLP_MASK
jr nz, .preferMoveEvenMore ; heavier favor for dream eater if the opponent is asleep jr z, .nextMove
jr .nextMove
.preferMoveEvenMore .preferMoveEvenMore
dec [hl] dec [hl]
jr .preferMove jr .preferMove
@ -676,9 +678,6 @@ TrainerAI:
ld a, [wIsInBattle] ld a, [wIsInBattle]
dec a dec a
ret z ; if not a trainer, we're done here 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] ld a, [wLinkState]
cp LINK_STATE_BATTLING cp LINK_STATE_BATTLING
ret z ; if in a link battle, we're done as well ret z ; if in a link battle, we're done as well
@ -732,12 +731,9 @@ BlackbeltAI:
jp AIUseXAttack jp AIUseXAttack
GiovanniAI: GiovanniAI:
cp 20 percent + 1 cp 25 percent + 1
ret nc ret nc
ld a, [wEnemyBattleStatus2] 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.
bit GETTING_PUMPED, a
ret nz
jp AIUseDireHit
CooltrainerMAI: CooltrainerMAI:
cp 20 percent + 1 cp 20 percent + 1
@ -765,41 +761,46 @@ BrockAI:
jp AIUseFullHeal jp AIUseFullHeal
MistyAI: MistyAI:
cp 20 percent + 1 cp 25 percent + 1
ret nc 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: LtSurgeAI:
cp 10 percent + 1 cp 20 percent + 1
ret nc ret nc
jp AIUseXSpeed jp AIUseXSpecial
ErikaAI: ErikaAI:
cp 50 percent + 1 cp 50 percent + 1
ret nc ret nc
ld a, 5 ld a, 10
call AICheckIfHPBelowFraction call AICheckIfHPBelowFraction
ret nc ret nc
jp AIUseSuperPotion jp AIUseSuperPotion
KogaAI: KogaAI:
cp 10 percent + 1 cp 50 percent + 1
ret nc ret nc
jp AIUseXAttack ld a, 10
BlaineAI: ;blaine needs to check HP. this was an oversight
cp 40 percent + 1
jr nc, .blainereturn
ld a, 2
call AICheckIfHPBelowFraction call AICheckIfHPBelowFraction
jp c, AIUseSuperPotion ret nc
.blainereturn jp AIUseSuperPotion ; Koga is weird - I don't think anything fits. X Attack is certainly not the move though...
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: SabrinaAI:
cp 25 percent + 1 cp 25 percent + 1
ret nc ret nc
ld a, 5 ld a, 10
call AICheckIfHPBelowFraction call AICheckIfHPBelowFraction
ret nc ret nc
jp AIUseHyperPotion jp AIUseHyperPotion
@ -810,7 +811,7 @@ Rival2AI:
ld a, 5 ld a, 5
call AICheckIfHPBelowFraction call AICheckIfHPBelowFraction
ret nc ret nc
jp AIUseHyperPotion jp AIUseSuperPotion
Rival3AI: Rival3AI:
cp 40 percent - 1 cp 40 percent - 1
@ -821,43 +822,51 @@ Rival3AI:
jp AIUseFullRestore jp AIUseFullRestore
LoreleiAI: LoreleiAI:
cp 50 percent + 1 cp 15 percent + 1
ret nc
jp AIUseXSpecial
cp 40 percent + 1
ret nc ret nc
ld a, 5 ld a, 5
call AICheckIfHPBelowFraction call AICheckIfHPBelowFraction
ret nc ret nc
jp AIUseHyperPotion jp AIUseFullRestore
BrunoAI: BrunoAI:
;cp 10 percent + 1 cp 15 percent + 1
;ret nc ret nc
;jp AIUseXDefend jp AIUseXAttack
cp 30 percent + 1 cp 40 percent + 1
jr nc, .brunoreturn ret nc
ld a, 5 ld a, 5
call AICheckIfHPBelowFraction call AICheckIfHPBelowFraction
jp c, AIUseHyperPotion ret nc
.brunoreturn jp AIUseFullRestore
ret
AgathaAI: AgathaAI:
cp 8 percent cp 8 percent
jp c, AISwitchIfEnoughMons jp c, AISwitchIfEnoughMons
cp 50 percent + 1 cp 15 percent + 1
ret nc ret nc
ld a, 4 jp AIUseXAccuracy ; hahahahahahahaha
call AICheckIfHPBelowFraction cp 40 percent + 1
ret nc
jp AIUseHyperPotion
LanceAI:
cp 50 percent + 1
ret nc ret nc
ld a, 5 ld a, 5
call AICheckIfHPBelowFraction call AICheckIfHPBelowFraction
ret nc ret nc
jp AIUseFullRestore 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: GenericAI:
and a ; clear carry and a ; clear carry
ret ret
@ -1087,12 +1096,12 @@ AICureStatus: ;shinpokerednote: CHANGED: modified to be more robust and also und
res BADLY_POISONED, [hl] ;clear toxic bit res BADLY_POISONED, [hl] ;clear toxic bit
ret ret
;AIUseXAccuracy: ; unused AIUseXAccuracy:
; call AIPlayRestoringSFX call AIPlayRestoringSFX
; ld hl, wEnemyBattleStatus2 ld hl, wEnemyBattleStatus2
; set 0, [hl] set 0, [hl]
; ld a, X_ACCURACY ld a, X_ACCURACY
; jp AIPrintItemUse jp AIPrintItemUse
;AIUseGuardSpec: ; PureRGBnote: CHANGED: now unused ;AIUseGuardSpec: ; PureRGBnote: CHANGED: now unused
; call AIPlayRestoringSFX ; call AIPlayRestoringSFX

View file

@ -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. ;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 ;It's meant to be run right before healing paralysis or burn so as to
;undo the stat changes. ;undo the stat changes.
@ -31,35 +81,6 @@ UndoBurnParStats:
ld [de], a ;reset the stat change bits ld [de], a ;reset the stat change bits
ret 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) ; does nothing since no stats are ever selected (barring glitches)
;HalveSelectedStats: ;HalveSelectedStats:
; ldh a, [hWhoseTurn] ; ldh a, [hWhoseTurn]

View file

@ -186,6 +186,8 @@ SECTION "Battle Engine 7", ROMX
INCLUDE "data/moves/moves.asm" INCLUDE "data/moves/moves.asm"
INCLUDE "data/pokemon/cries.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/unused_stats_functions.asm"
INCLUDE "engine/battle/move_effects/heal.asm" INCLUDE "engine/battle/move_effects/heal.asm"
INCLUDE "engine/battle/move_effects/transform.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 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" INCLUDE "engine/battle/draw_hud_pokeball_gfx.asm"