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
====
- 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
====

View file

@ -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

View file

@ -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

View file

@ -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

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.
;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]

View file

@ -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"