Fix Trianer AI glitch of Sabrina's Alakazam only using Recover against other Psychic Pokemon - pret/pokered GitHub Wiki

Trainer AI mechanism

In Pokémon Red, trainer AI mostly chooses moves at random, but up to three move-choice modification routes can alter the final pick, depending on the trainer.

You can see the modification choices of different trainers in data/trainers/move_choices.asm

	move_choices 1, 3    ; SABRINA
	move_choices 1, 2    ; GENTLEMAN
	move_choices 1, 3    ; RIVAL2
	move_choices 1, 3    ; RIVAL3
	move_choices 1, 2, 3 ; LORELEI

The example above shows that Sabrina uses modification routes 1 and 3.

Those routes live in engine/battle/trainer_ai.asm. For a full breakdown of how they work, see the Pokémon Red/Blue/Yellow Trainer AI wiki.

How to fix Sabrina AI glitch

The bug is in AIMoveChoiceModification3 in engine/battle/trainer_ai.asm. That route prioritizes moves based on type effectiveness. For each of the AI Pokémon's moves, it applies the following checks:

  1. If the move's type is effective against the player's Pokémon (whether or not it deals damage), encourage that move.
  2. If the move is ineffective, scan the other available moves for better alternatives (a damaging move of a different type, or a special move such as one with SPECIAL_DAMAGE_EFFECT). If a better move exists, discourage the current move.
  3. Only moves with the highest priority remain in contention during the final random choice.

The problem is in step 2: the AI also treats the move currently being evaluated as an alternative. If that move is one of the special cases (for example, a move with SPECIAL_DAMAGE_EFFECT), it can count against itself.

Take Sabrina's Alakazam as an example. Its moveset is:

Psybeam: Psychic Type
Recover: Normal Type
Psywave: Psychic Type
Reflect: Psychic Type

If the player brings a Psychic-type Pokémon, no move is super effective, so step 1 does not apply. Psybeam, Psywave, and Reflect all take the ineffective branch. Psywave is then treated as a "better" move because it has SPECIAL_DAMAGE_EFFECT. Without skipping self-comparison, Psywave discourages itself along with the other Psychic moves, leaving only Recover with a high enough priority—so Alakazam spams Recover.

The fix is straightforward: when running step 2, skip the move currently under evaluation. Store its move number before the loop and continue past it when it comes up again:

.notEffectiveMove ; discourages non-effective moves if better moves are available
	push hl
	push de
	push bc
	ld a, [wEnemyMoveType]
	ld d, a
+ld a, [wEnemyMoveNum]
+ld e, a
	ld hl, wEnemyMonMoves  ; enemy moves
	ld b, NUM_MOVES + 1
	ld c, $0
.loopMoves
	dec b
	jr z, .done
	ld a, [hli]
	and a
	jr z, .done
	call ReadMove
+ld a, [wEnemyMoveNum]
+cp e
+jr z, .loopMoves
	ld a, [wEnemyMoveEffect]

Bonus: Lorelei's Dewgong glitch

In Pokémon Red, if you bring a Poison- or Fighting-type Pokémon against Lorelei's Dewgong, Dewgong will endlessly use Rest. This happens because of step 1: Rest is a psychic type move, which is super effective against Poison and Fighting, so Rest gets top priority and becomes the only viable option.

Pokémon Yellow adds a special-case workaround for Lorelei's Dewgong that ignores type effectiveness entirely. A simpler, more general fix is to skip step 1 for moves that deal no damage:

.nextMove
	dec b
	ret z ; processed all 4 moves
	inc hl
	ld a, [de]
	and a
	ret z ; no more moves in move set
	inc de
	call ReadMove
	push hl
	push bc
	push de
	callfar AIGetTypeEffectiveness
	pop de
	pop bc
	pop hl
+ld a, [wEnemyMovePower]
+and a
+jr z, .nextMove

After this fix, Rest is no longer preferred by modifier 3 because it has power = 0, even though its type is Psychic.