mirror of
https://github.com/thornAvery/kep-hack.git
synced 2025-09-16 18:30:50 +12:00

- Fixed a bug where the Elite 4 wouldn't use Full Restores. Apparently the game only allows one item per trainer. - Gave Oak and Chief custom AI found from proto assets (then changed to be less ass) - Small amount of balancing - Minor text fixes - Figured out roughly how the current high-level AI works (Stat boosting moves are only used sometimes on the first turn, Recover is only used sometimes when below half health, otherwise just attack with a move that isn't ineffective. More testing needed for stat lowering moves and things like Barrier)
1246 lines
28 KiB
NASM
1246 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
|
|
|
|
;;;;;;;;;; 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
|
|
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_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
|
|
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 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 bc, 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: 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
|
|
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
|
|
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 z, .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, [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: ; Fun fact! Jacky uses this same AI routine in the proto assets, but only 6% of the time.
|
|
cp 25 percent + 1
|
|
ret nc
|
|
jp AIUseXAttack
|
|
|
|
GiovanniAI:
|
|
cp 25 percent + 1
|
|
ret nc
|
|
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
|
|
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 25 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUsePotion ; Replicates Starmie using Recover. Unlike other trainers that heal, Misty will do this 26% of the time instead of 51%.
|
|
|
|
LtSurgeAI:
|
|
cp 20 percent + 1
|
|
ret nc
|
|
jp AIUseXSpecial
|
|
|
|
ErikaAI:
|
|
cp 25 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseSuperPotion
|
|
|
|
KogaAI:
|
|
cp 25 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseSuperPotion ; Koga is weird - I don't think anything fits. X Attack is certainly not the move though...
|
|
|
|
BlaineAI:
|
|
cp 40 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
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
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseHyperPotion
|
|
|
|
Rival2AI:
|
|
cp 20 percent - 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseSuperPotion
|
|
|
|
Rival3AI:
|
|
cp 40 percent - 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseFullRestore
|
|
|
|
LoreleiAI:
|
|
cp 40 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseFullRestore
|
|
|
|
BrunoAI:
|
|
cp 40 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseFullRestore
|
|
|
|
AgathaAI:
|
|
cp 10 percent
|
|
jp c, AISwitchIfEnoughMons
|
|
cp 40 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseFullRestore
|
|
|
|
LanceAI:
|
|
cp 50 percent + 1
|
|
ret nc
|
|
ld a, 4
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseFullRestore
|
|
|
|
OakAI:
|
|
cp 25 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseFullRestore
|
|
|
|
ChiefAI:
|
|
cp 25 percent + 1
|
|
ret nc
|
|
ld a, 5
|
|
call AICheckIfHPBelowFraction
|
|
ret nc
|
|
jp AIUseFullRestore ; this was a Dire Hit in the proto assets but we all know how useful that item is in Gen 1
|
|
|
|
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:
|
|
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
|