kep-hack/engine/battle/trainer_ai.asm
Llinos Evans 0af5bd126b Port PureRGB AI
Short tests showed positive results. Revert if it fucks up anything.

PureRGB enhances Gen 1 AI in various ways, fixing notorious glitches and making it not do completely stupid things. I would use shin pokered's, but it may be too difficult for unfamiliar players, and has a bunch of outdated markers I'd have to spruce up.

Relevant changes:
- Burn effect calls have been replaced with Fire Blast's effect, replicating the burn spread use-case of Fire Blast
- Teleport references removed because pureRGB uses a unique version
- Any straggler references to effects not used in pureRGB
- Mist properly referred to as we only have one move that provides stat drop immunity.

Updated the sprite gallery too!!
2023-09-07 02:14:38 +01:00

1233 lines
28 KiB
NASM

; creates a set of moves that may be used and returns its address in hl
; unused slots are filled with 0, all used slots may be chosen with equal probability
AIEnemyTrainerChooseMoves:
ld a, $a
ld hl, wBuffer ; init temporary move selection array. Only the moves with the lowest numbers are chosen in the end
ld [hli], a ; move 1
ld [hli], a ; move 2
ld [hli], a ; move 3
ld [hl], a ; move 4
ld a, [wEnemyDisabledMove] ; forbid disabled move (if any)
swap a
and $f
jr z, .noMoveDisabled
ld hl, wBuffer
dec a
ld c, a
ld b, $0
add hl, bc ; advance pointer to forbidden move
ld [hl], $50 ; forbid (highly discourage) disabled move
.noMoveDisabled
ld hl, TrainerClassMoveChoiceModifications
ld a, [wTrainerClass]
ld b, a
.loopTrainerClasses
dec b
jr z, .readTrainerClassData
.loopTrainerClassData
ld a, [hli]
and a
jr nz, .loopTrainerClassData
jr .loopTrainerClasses
.readTrainerClassData
ld a, [hl]
and a
jp z, .useOriginalMoveSet
push hl
.nextMoveChoiceModification
pop hl
ld a, [hli]
and a
jr z, .loopFindMinimumEntries
push hl
ld hl, AIMoveChoiceModificationFunctionPointers
dec a
add a
ld c, a
ld b, 0
add hl, bc ; skip to pointer
ld a, [hli] ; read pointer into hl
ld h, [hl]
ld l, a
ld de, .nextMoveChoiceModification ; set return address
push de
jp hl ; execute modification function
.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
ld c, NUM_MOVES
.loopDecrementEntries
ld a, [de]
inc de
and a
jr z, .loopFindMinimumEntries
dec [hl]
jr z, .minimumEntriesFound
inc hl
dec c
jr z, .loopFindMinimumEntries
jr .loopDecrementEntries
.minimumEntriesFound
ld a, c
.loopUndoPartialIteration ; undo last (partial) loop iteration
inc [hl]
dec hl
inc a
cp NUM_MOVES + 1
jr nz, .loopUndoPartialIteration
ld hl, wBuffer ; temp move selection array
ld de, wEnemyMonMoves ; enemy moves
ld c, NUM_MOVES
.filterMinimalEntries ; all minimal entries now have value 1. All other slots will be disabled (move set to 0)
ld a, [de]
and a
jr nz, .moveExisting
ld [hl], a
.moveExisting
ld a, [hl]
dec a
jr z, .slotWithMinimalValue
xor a
ld [hli], a ; disable move slot
jr .next
.slotWithMinimalValue
ld a, [de]
ld [hli], a ; enable move slot
.next
inc de
dec c
jr nz, .filterMinimalEntries
ld hl, wBuffer ; use created temporary array as move set
jr .done
.useOriginalMoveSet
ld hl, wEnemyMonMoves ; use original move set
.done
;;;;;;;;;; PureRGBnote: clear these values at the end of an AI cycle, they only apply when the player has switched or healed in a turn
xor a
ld [wAIMoveSpamAvoider], a
ld [wAITargetMonStatus], a
;;;;;;;;;;
ret
AIMoveChoiceModificationFunctionPointers:
dw AIMoveChoiceModification1
dw AIMoveChoiceModification2
dw AIMoveChoiceModification3
dw AIMoveChoiceModification4
; PureRGBnote: CHANGED: AKA the "Dont do stupid things no player would ever do" AI subroutine, many new default AI restrictions added
; discourages moves that cause no damage but only a status ailment if player's mon already has one, or if they're immune to it
; discourages moves that after being used once won't do anything when used again (mist, leech seed, etc.)
; discourages moves that will fail due to the current enemy pokemon's state (recover at full health, one hit ko moves on faster pkmn)
AIMoveChoiceModification1:
ld hl, wBuffer - 1 ; temp move selection array (-1 byte offset)
ld de, wEnemyMonMoves ; enemy moves
ld b, NUM_MOVES + 1
.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
ld a, [wEnemyMoveEffect]
cp DREAM_EATER_EFFECT
jp z, .checkAsleep
cp OHKO_EFFECT
jr z, .ohko
ld a, [wEnemyMovePower]
and a
jr nz, .nextMove
ld a, [wEnemyMoveEffect]
cp DISABLE_EFFECT
jr z, .checkDisabled
cp LEECH_SEED_EFFECT
jp z, .checkSeeded
cp FOCUS_ENERGY_EFFECT
jr z, .checkPumpedUp
cp LIGHT_SCREEN_EFFECT
jp z, .checkLightScreenUp
cp REFLECT_EFFECT
jp z, .checkReflectUp
cp MIST_EFFECT
jp z, .checkMistUp
cp CONFUSION_EFFECT
jp z, .checkConfused
cp HEAL_EFFECT
jp z, .checkFullHealth
cp MIRROR_MOVE_EFFECT
jp z, .checkNoMirrorMoveOnFirstTurn
ld a, [wEnemyMoveEffect]
push hl
push de
push bc
ld hl, StatusAilmentMoveEffects
ld de, 1
call IsInArray
pop bc
pop de
pop hl
jr nc, .nextMove
.checkStatusImmunity
call CheckStatusImmunity
jr c, .discourage
.notImmune
ld a, [wAITargetMonStatus] ; set to the pokemon's current status before it gets healed or before it switches out
and a
jr nz, .discourage ; if the AI thinks the player has a status, they should avoid using status moves
; even if the player heals the status or switches out that turn
ld a, [wAIMoveSpamAvoider] ; set if we switched or healed this turn
cp 2 ; set to 2 if we switched
jr z, .nextMove ; if the AI thinks the player DOESNT have a status before they switch, we should avoid discouraging status moves
ld a, [wBattleMonStatus]
and a
jr z, .nextMove ; no need to discourage status moves if the player doesn't have a status
.discourage
ld a, [hl]
add $5 ; heavily discourage move
ld [hl], a
jr .nextMove
.ohko
call WillOHKOMoveAlwaysFail
jp nc, .nextMove
jr .discourage
.checkDisabled
ld a, [wPlayerDisabledMove] ; non-zero if the player has a disabled move
and a
jp z, .nextMove ; if it's zero don't do anything
jr .discourage ; otherwise discourage using disable while opponent is disabled already
.checkPumpedUp
ld a, [wEnemyBattleStatus2]
bit GETTING_PUMPED, a
jr nz, .discourage ; if the enemy has used focus energy don't use again
jp .nextMove
.checkAsleep
ld a, [wAITargetMonStatus]
and SLP_MASK
jp nz, .nextMove ; if we just healed sleep or switched out a sleeping pokemon,
; the AI shouldn't predict this perfectly when deciding whether to use dream eater
ld a, [wBattleMonStatus]
and SLP_MASK
jr z, .discourage ; heavily discourage, if the player isn't asleep avoid using dream eater
jp .nextMove
.checkLightScreenUp
ld a, [wEnemyBattleStatus3]
bit HAS_LIGHT_SCREEN_UP, a
jr nz, .discourage ; if the enemy has a light screen up dont use the move again
jp .nextMove
.checkReflectUp
ld a, [wEnemyBattleStatus3]
bit HAS_REFLECT_UP, a
jr nz, .discourage ; if the enemy has a reflect up dont use the move again
jp .nextMove
.checkMistUp
ld a, [wEnemyBattleStatus2]
bit PROTECTED_BY_MIST, a
jr nz, .discourage ; if the enemy has used mist, don't use it again
jp .nextMove
.checkConfused
ld a, [wPlayerBattleStatus1]
bit CONFUSED, a
jr nz, .discourage ; if the player is confused, don't use confusion-inflicting moves
jp .nextMove
.checkSeeded
call CheckSeeded
jp nc, .nextMove
jr .discourage
.checkFullHealth ; avoid using moves like recover at full health.
push hl
push de
ld hl, wEnemyMonMaxHP
ld de, wEnemyMonHP
ld a, [de]
cp [hl]
jr nz, .notFullHealth
inc hl
inc de
ld a, [de]
cp [hl]
jr nz, .notFullHealth
pop de
pop hl
jp .discourage
.notFullHealth
pop de
pop hl
jp .nextMove
.checkNoMirrorMoveOnFirstTurn
ld a, [wPlayerLastSelectedMove]
and a
jp z, .discourage ; don't use mirror move if the player has never selected a move yet
jp .nextMove
StatusAilmentMoveEffects:
db SLEEP_EFFECT
db POISON_EFFECT
db PARALYZE_EFFECT
db BURN_SIDE_EFFECT2 ; Fire Blast is often used as a burn spreading tool in comp RBY!
db -1 ; end
;;;;;;;;;; PureRGBnote: ADDED: function for checking if the player can have leech seed applied and whether they already have it applied
CheckSeeded:
push hl
ld a, [wPlayerBattleStatus2]
bit SEEDED, a
jr nz, .discourage ; if the enemy has used leech seed don't use again
ld a, [wAIMoveSpamAvoider]
cp 2 ; set to 2 if we switched out this turn
ld hl, wBattleMonType1
jr nz, .noSwitchOut
ld hl, wAITargetMonType1 ; stores what the AI thinks the player's type is when a switchout happens
.noSwitchOut
ld a, [hl]
cp GRASS
jr z, .discourage ; leech seed does not affect grass types
inc hl
ld a, [hl]
cp GRASS
jr z, .discourage ; leech seed does not affect grass types
pop hl
and a
ret
.discourage
pop hl
scf
ret
;;;;;;;;;;
;;;;;;;;;; PureRGBnote: ADDED: function for checking if the player's pokemon is unaffected by specific status moves.
CheckStatusImmunity:
push bc
push hl
ld a, [wEnemyMoveEffect]
cp POISON_EFFECT
ld b, POISON
jr z, .getMonTypes
cp PARALYZE_EFFECT
jr z, .checkParalyze
cp BURN_SIDE_EFFECT2
ld b, FIRE
jr z, .getMonTypes
jr .done
.checkParalyze
ld a, [wEnemyMoveType]
cp ELECTRIC
ld b, GROUND
jr nz, .done
.getMonTypes
ld a, [wAIMoveSpamAvoider] ; set if we healed status or switched out this turn
cp 2 ; it's 2 if we switched out
jr nz, .noSwitchOut
ld hl, wAITargetMonType1
jr .checkTypes
.noSwitchOut
ld hl, wBattleMonType1
.checkTypes
ld a, [hl]
cp b
jr z, .discourage
inc hl
ld a, [hl]
cp b
jr z, .discourage
.done
pop hl
pop bc
and a
ret
.discourage
pop hl
pop bc
scf
ret
;;;;;;;;;;
;;;;;;;;;; PureRGBnote: ADDED: function that allows AI to avoid OHKO moves if they will never do anything to the player's pokemon due to speed differences
WillOHKOMoveAlwaysFail:
call CompareSpeed
jr c, .userIsSlower
and a
ret
.userIsSlower
scf
ret
;;;;;;;;;;
; PureRGBnote: CHANGED: AKA the "Boost stats on the first turn" subroutine
; slightly encourage moves with specific effects on the first turn. (PureRGBnote: FIXED: used to be the second turn, made it first turn)
; this mostly means trainers will buff their pokemon a bit on the first turn
AIMoveChoiceModification2:
ld a, [wAILayer2Encouragement]
and a
ret nz ; choose this modifier only on the first turn
ld hl, wBuffer - 1 ; temp move selection array (-1 byte offset)
ld de, wEnemyMonMoves ; enemy moves
ld b, NUM_MOVES + 1
.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
ld a, [wEnemyMoveEffect]
push hl
push de
push bc
ld hl, Modifier2PreferredMoves
ld de, 1
call IsInArray
pop bc
pop de
pop hl
jr nc, .nextMove
.preferMove
dec [hl] ; slightly encourage this move
jr .nextMove
Modifier2PreferredMoves:
db LEECH_SEED_EFFECT
db FOCUS_ENERGY_EFFECT
db REFLECT_EFFECT
db LIGHT_SCREEN_EFFECT
db ATTACK_UP1_EFFECT
db DEFENSE_UP1_EFFECT
db SPEED_UP1_EFFECT
db SPECIAL_UP1_EFFECT
db ACCURACY_UP1_EFFECT
db EVASION_UP1_EFFECT
db ATTACK_DOWN1_EFFECT
db DEFENSE_DOWN1_EFFECT
db SPEED_DOWN1_EFFECT
db SPECIAL_DOWN1_EFFECT
db ACCURACY_DOWN1_EFFECT
db EVASION_DOWN1_EFFECT
db ATTACK_UP2_EFFECT
db DEFENSE_UP2_EFFECT
db SPEED_UP2_EFFECT
db SPECIAL_UP2_EFFECT
db ACCURACY_UP2_EFFECT
db EVASION_UP2_EFFECT
db ATTACK_DOWN2_EFFECT
db DEFENSE_DOWN2_EFFECT
db SPEED_DOWN2_EFFECT
db SPECIAL_DOWN2_EFFECT
db ACCURACY_DOWN2_EFFECT
db EVASION_DOWN2_EFFECT
db SUBSTITUTE_EFFECT
db -1 ; end
; PureRGBnote: CHANGED: AKA the "Use Effective damaging moves offensively" subroutine
; encourages moves that are effective against the player's mon if they do damage.
; discourage damaging moves that are ineffective or not very effective against the player's mon,
; unless there's no damaging move that deals at least neutral damage
; encourage effective or super effective priority moves if the pokemon is slower than the player's pokemon (but only after obtaining 5 badges)
; encourage effective or super effective draining moves to be used at low health
; PureRGBnote: FIXED: this subroutine won't cause the AI to prefer status moves
; just because their type is super effective against the opponent. Like spamming agility on a poison pokemon.
AIMoveChoiceModification3:
ld hl, wBuffer - 1 ; temp move selection array (-1 byte offset)
ld de, wEnemyMonMoves ; enemy moves
ld b, NUM_MOVES + 1
.nextMove
dec b
jp z, .clearPreviousTypes ; processed all 4 moves
inc hl
ld a, [de]
and a
jp z, .clearPreviousTypes ; no more moves in move set
inc de
call ReadMove
ld a, [wEnemyMovePower]
and a
jr z, .nextMove ; ignores moves that do no damage (status moves), as we're only concerned with damaging moves for this modifier
ld a, [wAIMoveSpamAvoider] ; if we switched this turn or healed status, this is set
cp 2 ; it's 2 if we switched pokemon this turn
call nz, StoreBattleMonTypes ; in the case where we didnt switch
; we need to populate wAITargetMonType1 and wAITargetMonType2 with the current pokemon's type data
push hl
push bc
push de
callfar AIGetTypeEffectiveness
pop de
pop bc
pop hl
ld a, [wTypeEffectiveness]
cp EFFECTIVE
jr z, .checkSpecificEffects
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
push hl
push de
push bc
ld a, [wEnemyMoveType]
ld d, 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, [wEnemyMoveEffect]
cp SUPER_FANG_EFFECT
jr z, .betterMoveFound ; Super Fang is considered to be a better move
cp SPECIAL_DAMAGE_EFFECT
jr z, .betterMoveFound ; any special damage moves are considered to be better moves
cp FLY_EFFECT
jr z, .betterMoveFound ; Fly is considered to be a better move
ld a, [wEnemyMoveType]
cp d
jr z, .loopMoves
ld a, [wEnemyMovePower]
and a
jr nz, .betterMoveFound ; damaging moves of a different type are considered to be better moves
jr .loopMoves
.betterMoveFound
ld c, a
.done
ld a, c
pop bc
pop de
pop hl
and a
jp z, .nextMove
inc [hl] ; slightly discourage this move
jp .nextMove
.clearPreviousTypes
xor a
ld [wAITargetMonType1], a
ld [wAITargetMonType2], a
ret
;;;;;;;;;; PureRGBnote: ADDED: function that allows AI to be aware if they are slower than the opponent. Allows them to prefer priority moves.
CompareSpeed:
push hl
push de
push bc
ld hl, wEnemyMonSpeed + 1
ld de, wBattleMonSpeed + 1
.compareSpeed
; check if current speed is higher than the target's
ld a, [de]
dec de
ld b, a
ld a, [hld]
sub b
ld a, [de]
ld b, a
ld a, [hl]
sbc b
pop bc
pop de
pop hl
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]
cp DRAIN_HP_EFFECT
ret nz
ld a, 2 ; 1/2 maximum hp gone
call AICheckIfHPBelowFractionWrapped
ret nc
dec [hl] ; encourage the draining move if enemy has more than half health gone
ret
; PureRGBnote: ADDED: AKA the "Apply Status and Heal when needed" subroutine
; slightly encourage moves with specific effects.
; This one will make the opponent want to use status applying moves when you don't have one.
; It also makes them want to use dream eater if you're asleep, and want to use a recovery move at low health.
AIMoveChoiceModification4:
ld hl, wBuffer - 1 ; temp move selection array (-1 byte offset)
ld de, wEnemyMonMoves ; enemy moves
ld b, NUM_MOVES + 1
.nextMove
dec b
jr z, .done ; processed all 4 moves
inc hl
ld a, [de]
and a
jr z, .done ; no more moves in move set
inc de
call ReadMove
ld a, [wEnemyMoveEffect]
cp DREAM_EATER_EFFECT
jr z, .checkOpponentAsleep
ld a, [wEnemyMovePower]
and a
jr nz, .nextMove
ld a, [wEnemyMoveEffect]
cp HEAL_EFFECT
jr z, .checkWorthHealing
push hl
push de
push bc
ld hl, Modifier4PreferredMoves
ld de, 1
call IsInArray
pop bc
pop de
pop hl
jr nc, .nextMove
ld a, [wAITargetMonStatus] ; set to nonzero if player healed battle mon's status or switched one with a status out this turn
and a
jr z, .preferMove
ld a, [hl]
add $5
ld [hl], a ; heavily discourage using a status move right after the player switched or healed
jr .nextMove
.preferMove
dec [hl] ; slightly encourage this move
jr .nextMove
.checkWorthHealing
ld a, 2 ; 1/2 maximum HP
call AICheckIfHPBelowFractionWrapped
jr c, .preferMove ; if HP is below 50% encourage using a healing move
jr .nextMove ; otherwise don't encourage it
.checkOpponentAsleep
ld a, [wAITargetMonStatus] ; set to nonzero if player healed battle mon's status or switched one with a status out this turn
and SLP_MASK
jr nz, .preferMoveEvenMore
ld a, [wAIMoveSpamAvoider] ; set if we switched or healed this turn
cp 2 ; set to 2 if we switched
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
.preferMoveEvenMore
dec [hl]
jr .preferMove
.done
ret
Modifier4PreferredMoves:
db SLEEP_EFFECT
db POISON_EFFECT
db PARALYZE_EFFECT
db BURN_SIDE_EFFECT2
db CONFUSION_EFFECT
db -1 ; end
ReadMove:
push hl
push de
push bc
dec a
ld hl, Moves
ld bc, MOVE_LENGTH
call AddNTimes
ld de, wEnemyMoveNum
rst _CopyData
pop bc
pop de
pop hl
ret
INCLUDE "data/trainers/move_choices.asm"
INCLUDE "data/trainers/pic_pointers_money.asm"
INCLUDE "data/trainers/names.asm"
INCLUDE "engine/battle/misc.asm"
INCLUDE "engine/battle/read_trainer_party.asm"
INCLUDE "data/trainers/special_moves.asm"
INCLUDE "data/trainers/parties.asm"
TrainerAI:
and a
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;shinpokerednote: FIXED: AI should not use actions (items / switching) if in a move that prevents such a thing
and a ; clear carry flag in case we return due to the next two checks, we dont want carry returned in those cases as it marks an action as being taken by the opponent.
ld a, [wEnemyBattleStatus2]
bit NEEDS_TO_RECHARGE, a
ret nz
ld a, [wEnemyBattleStatus1]
and %01110010
ret nz
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ld a, [wTrainerClass] ; what trainer class is this?
dec a
ld c, a
ld b, 0
ld hl, TrainerAIPointers
add hl, bc
add hl, bc
add hl, bc
ld a, [wAICount]
and a
ret z ; if no AI uses left, we're done here
inc hl
inc a
jr nz, .getpointer
dec hl
ld a, [hli]
ld [wAICount], a
.getpointer
ld a, [hli]
ld h, [hl]
ld l, a
call Random
jp hl
INCLUDE "data/trainers/ai_pointers.asm"
; PureRGBnote: CHANGED: some of these trainer-specific AI were tweaked to happen more or less often or had their item modified from vanilla.
JugglerAI:
cp 25 percent + 1
ret nc
jp AISwitchIfEnoughMons
BlackbeltAI:
cp 13 percent - 1
ret nc
jp AIUseXAttack
GiovanniAI:
cp 20 percent + 1
ret nc
ld a, [wEnemyBattleStatus2]
bit GETTING_PUMPED, a
ret nz
jp AIUseDireHit
CooltrainerMAI:
cp 20 percent + 1
ret nc
jp AIUseXSpecial
CooltrainerFAI:
; The intended 25% chance to consider switching will not apply.
; Uncomment the line below to fix this.
cp 25 percent + 1
ret nc
ld a, 10
call AICheckIfHPBelowFraction
jp c, AIUseHyperPotion
ld a, 5
call AICheckIfHPBelowFraction
ret nc
jp AISwitchIfEnoughMons
BrockAI:
; if his active monster has a status condition, use a full heal
ld a, [wEnemyMonStatus]
and a
ret z
jp AIUseFullHeal
MistyAI:
cp 20 percent + 1
ret nc
jp AIUseXDefend
LtSurgeAI:
cp 10 percent + 1
ret nc
jp AIUseXSpeed
ErikaAI:
cp 50 percent + 1
ret nc
ld a, 5
call AICheckIfHPBelowFraction
ret nc
jp AIUseSuperPotion
KogaAI:
cp 10 percent + 1
ret nc
jp AIUseXAttack
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
SabrinaAI:
cp 25 percent + 1
ret nc
ld a, 5
call AICheckIfHPBelowFraction
ret nc
jp AIUseHyperPotion
Rival2AI:
cp 13 percent - 1
ret nc
ld a, 5
call AICheckIfHPBelowFraction
ret nc
jp AIUseHyperPotion
Rival3AI:
cp 40 percent - 1
ret nc
ld a, 5
call AICheckIfHPBelowFraction
ret nc
jp AIUseFullRestore
LoreleiAI:
cp 50 percent + 1
ret nc
ld a, 5
call AICheckIfHPBelowFraction
ret nc
jp AIUseHyperPotion
BrunoAI:
;cp 10 percent + 1
;ret nc
;jp AIUseXDefend
cp 30 percent + 1
jr nc, .brunoreturn
ld a, 5
call AICheckIfHPBelowFraction
jp c, AIUseHyperPotion
.brunoreturn
ret
AgathaAI:
cp 8 percent
jp c, AISwitchIfEnoughMons
cp 50 percent + 1
ret nc
ld a, 4
call AICheckIfHPBelowFraction
ret nc
jp AIUseHyperPotion
LanceAI:
cp 50 percent + 1
ret nc
ld a, 5
call AICheckIfHPBelowFraction
ret nc
jp AIUseFullRestore
GenericAI:
and a ; clear carry
ret
; end of individual trainer AI routines
DecrementAICount:
ld hl, wAICount
dec [hl]
scf
ret
AIPlayRestoringSFX:
ld a, SFX_HEAL_AILMENT
jp PlaySoundWaitForCurrent
AIUseFullRestore:
call AICureStatus
ld a, FULL_RESTORE
ld [wAIItem], a
ld de, wHPBarOldHP
ld hl, wEnemyMonHP + 1
ld a, [hld]
ld [de], a
inc de
ld a, [hl]
ld [de], a
inc de
ld hl, wEnemyMonMaxHP + 1
ld a, [hld]
ld [de], a
inc de
ld [wHPBarMaxHP], a
ld [wEnemyMonHP + 1], a
ld a, [hl]
ld [de], a
ld [wHPBarMaxHP+1], a
ld [wEnemyMonHP], a
jr AIPrintItemUseAndUpdateHPBar
AIUsePotion:
; enemy trainer heals his monster with a potion
ld a, POTION
ld b, 20
jr AIRecoverHP
AIUseSuperPotion:
; enemy trainer heals his monster with a super potion
ld a, SUPER_POTION
ld b, 50
jr AIRecoverHP
AIUseHyperPotion:
; enemy trainer heals his monster with a hyper potion
ld a, HYPER_POTION
ld b, 200
; fallthrough
AIRecoverHP:
; heal b HP and print "trainer used $(a) on pokemon!"
ld [wAIItem], a
ld hl, wEnemyMonHP + 1
ld a, [hl]
ld [wHPBarOldHP], a
add b
ld [hld], a
ld [wHPBarNewHP], a
ld a, [hl]
ld [wHPBarOldHP+1], a
ld [wHPBarNewHP+1], a
jr nc, .next
inc a
ld [hl], a
ld [wHPBarNewHP+1], a
.next
inc hl
ld a, [hld]
ld b, a
ld de, wEnemyMonMaxHP + 1
ld a, [de]
dec de
ld [wHPBarMaxHP], a
sub b
ld a, [hli]
ld b, a
ld a, [de]
ld [wHPBarMaxHP+1], a
sbc b
jr nc, AIPrintItemUseAndUpdateHPBar
inc de
ld a, [de]
dec de
ld [hld], a
ld [wHPBarNewHP], a
ld a, [de]
ld [hl], a
ld [wHPBarNewHP+1], a
; fallthrough
AIPrintItemUseAndUpdateHPBar:
call AIPrintItemUse_
hlcoord 2, 2
xor a
ld [wHPBarType], a
predef UpdateHPBar2
jp DecrementAICount
AISwitchIfEnoughMons:
; enemy trainer switches if there are 2 or more unfainted mons in party
ld a, [wEnemyPartyCount]
ld c, a
ld hl, wEnemyMon1HP
ld d, 0 ; keep count of unfainted monsters
; count how many monsters haven't fainted yet
.loop
ld a, [hli]
ld b, a
ld a, [hld]
or b
jr z, .Fainted ; has monster fainted?
inc d
.Fainted
push bc
ld bc, wEnemyMon2 - wEnemyMon1
add hl, bc
pop bc
dec c
jr nz, .loop
ld a, d ; how many available monsters are there?
cp 2 ; don't bother if only 1
jp nc, SwitchEnemyMon
and a
ret
SwitchEnemyMonNoText:
call SwitchEnemyMonCommon
jp SwitchEnemyMonCommon2
SwitchEnemyMon:
call SwitchEnemyMonCommon
ld hl, AIBattleWithdrawText
rst _PrintText
jp SwitchEnemyMonCommon2
SwitchEnemyMonCommon:
;;;;; shinpokerednote: CHANGED: if player using trapping move, then end their move
ld a, [wPlayerBattleStatus1]
bit USING_TRAPPING_MOVE, a
jr z, .preparewithdraw
ld hl, wPlayerBattleStatus1
res USING_TRAPPING_MOVE, [hl]
xor a
ld [wPlayerNumAttacksLeft], a
ld a, $FF
ld [wPlayerSelectedMove], a
.preparewithdraw
;;;;;
; prepare to withdraw the active monster: copy hp, number, and status to roster
ld a, [wEnemyMonPartyPos]
ld hl, wEnemyMon1HP
ld bc, wEnemyMon2 - wEnemyMon1
call AddNTimes
ld d, h
ld e, l
ld hl, wEnemyMonHP
ld bc, 4
rst _CopyData
ret
SwitchEnemyMonCommon2:
;;;;;;;;;; PureRGBnote: ADDED: clear the previous selected move here to reset disable functionality on opponent switching pokemon.
xor a
ld [wEnemyLastSelectedMoveDisable], a
;;;;;;;;;;
; This wFirstMonsNotOutYet variable is abused to prevent the player from
; switching in a new mon in response to this switch.
ld a, 1
ld [wFirstMonsNotOutYet], a
callfar EnemySendOut
xor a
ld [wFirstMonsNotOutYet], a
ld a, [wLinkState]
cp LINK_STATE_BATTLING
ret z
;shinpokerednote: FIXED: the act of switching clears hWhoseTurn, so it needs to be set back to 1
ld a, 1
ldh [hWhoseTurn], a
scf
ret
AIBattleWithdrawText:
text_far _AIBattleWithdrawText
text_end
AIUseFullHeal:
call AIPlayRestoringSFX
call AICureStatus
ld a, FULL_HEAL
jp AIPrintItemUse
AICureStatus: ;shinpokerednote: CHANGED: modified to be more robust and also undo stat changes of brn/par
; cures the status of enemy's active pokemon
ld a, [wEnemyMonPartyPos]
ld hl, wEnemyMon1Status
ld bc, wEnemyMon2 - wEnemyMon1
call AddNTimes
xor a
ld [hl], a ; clear status in enemy team roster
ldh a, [hWhoseTurn]
push af
ld a, $01 ;forcibly set it to the AI's turn
ldh [hWhoseTurn], a
farcall UndoBurnParStats ;undo brn/par stat changes
pop af
ldh [hWhoseTurn], a
xor a
ld [wEnemyMonStatus], a ; clear status in active enemy data
ld hl, wEnemyBattleStatus3
res BADLY_POISONED, [hl] ;clear toxic bit
ret
;AIUseXAccuracy: ; unused
; call AIPlayRestoringSFX
; ld hl, wEnemyBattleStatus2
; set 0, [hl]
; ld a, X_ACCURACY
; jp AIPrintItemUse
;AIUseGuardSpec: ; PureRGBnote: CHANGED: now unused
; call AIPlayRestoringSFX
; ld hl, wEnemyBattleStatus2
; set 1, [hl]
; ld a, GUARD_SPEC
; jp AIPrintItemUse
AIUseDireHit:
call AIPlayRestoringSFX
ld hl, wEnemyBattleStatus2
set 2, [hl]
ld a, DIRE_HIT
jp AIPrintItemUse
; PureRGBnote: ADDED: if enemy HP is below a 1/[wUnusedC000], store 1 in wUnusedC000.
; used for checking whether the hyper ball item should guarantee success on use
AICheckIfHPBelowFractionStore::
ld a, [wUnusedC000]
call AICheckIfHPBelowFraction
jr c, .below
xor a
jr .done
.below
ld a, 1
.done
ld [wUnusedC000], a
ret
AICheckIfHPBelowFractionWrapped:
push hl
push bc
push de
call AICheckIfHPBelowFraction
pop de
pop bc
pop hl
ret
AICheckIfHPBelowFraction:
; return carry if enemy trainer's current HP is below 1 / a of the maximum
ldh [hDivisor], a
ld hl, wEnemyMonMaxHP
ld a, [hli]
ldh [hDividend], a
ld a, [hl]
ldh [hDividend + 1], a
ld b, 2
call Divide
ldh a, [hQuotient + 3]
ld c, a
ldh a, [hQuotient + 2]
ld b, a
ld hl, wEnemyMonHP + 1
ld a, [hld]
ld e, a
ld a, [hl]
ld d, a
ld a, d
sub b
ret nz
ld a, e
sub c
ret
AIUseXAttack:
ld b, $A
ld a, X_ATTACK
jr AIIncreaseStat
AIUseXDefend:
ld b, $B
ld a, X_DEFEND
jr AIIncreaseStat
AIUseXSpeed:
ld b, $C
ld a, X_SPEED
jr AIIncreaseStat
AIUseXSpecial:
ld b, $D
ld a, X_SPECIAL
; fallthrough
AIIncreaseStat:
ld [wAIItem], a
push bc
call AIPrintItemUse_
pop bc
ld hl, wEnemyMoveEffect
ld a, [hld]
push af
ld a, [hl]
push af
push hl
ld a, XSTATITEM_ANIM
ld [hli], a
ld [hl], b
callfar StatModifierUpEffect
pop hl
pop af
ld [hli], a
pop af
ld [hl], a
jp DecrementAICount
AIPrintItemUse:
ld [wAIItem], a
call AIPrintItemUse_
jp DecrementAICount
AIPrintItemUse_:
; print "x used [wAIItem] on z!"
ld a, [wAIItem]
ld [wd11e], a
call GetItemName
ld hl, AIBattleUseItemText
jp _PrintText
AIBattleUseItemText:
text_far _AIBattleUseItemText
text_end
;;;;;;;;;; PureRGBnote: ADDED: these wram properties are used to make sure the
;;;;;;;;;; AI doesn't instantly read the player's current pokemon type after a player switches.
;;;;;;;;;; makes sure the AI doesn't appear to predict all your switch-outs of pokemon.
StoreBattleMonTypes:
push hl
ld hl, wBattleMonType
ld a, [hl] ; b = type 1 of player's pokemon
ld [wAITargetMonType1], a
inc hl
ld a, [hl] ; c = type 2 of player's pokemon
ld [wAITargetMonType2], a
pop hl
ret