ref: 4c6a58ee21326ff8249914704ba6043b8b9af239
dir: /engine/battle/trainer_ai.asm/
; 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 ret .useOriginalMoveSet ld hl, wEnemyMonMoves ; use original move set ret AIMoveChoiceModificationFunctionPointers: dw AIMoveChoiceModification1 dw AIMoveChoiceModification2 dw AIMoveChoiceModification3 dw AIMoveChoiceModification4 ; unused, does nothing ; discourages moves that cause no damage but only a status ailment if player's mon already has one AIMoveChoiceModification1: ld a, [wBattleMonStatus] and a ret z ; return if no status ailment on player's mon 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, [wEnemyMovePower] and a jr nz, .nextMove 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 ld a, [hl] add $5 ; heavily discourage move ld [hl], a jr .nextMove StatusAilmentMoveEffects: db EFFECT_01 ; unused sleep effect db SLEEP_EFFECT db POISON_EFFECT db PARALYZE_EFFECT db -1 ; end ; slightly encourage moves with specific effects. ; in particular, stat-modifying moves and other move effects ; that fall in-between AIMoveChoiceModification2: ld a, [wAILayer2Encouragement] cp $1 ret nz 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 ATTACK_UP1_EFFECT jr c, .nextMove cp BIDE_EFFECT jr c, .preferMove cp ATTACK_UP2_EFFECT jr c, .nextMove cp POISON_EFFECT jr c, .preferMove jr .nextMove .preferMove dec [hl] ; slightly encourage this move jr .nextMove ; encourages moves that are effective against the player's mon (even if non-damaging). ; 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 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 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, [wTypeEffectiveness] cp $10 jr z, .nextMove jr c, .notEffectiveMove dec [hl] ; slightly encourage this move 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 jr z, .nextMove inc [hl] ; slightly discourage this move jr .nextMove AIMoveChoiceModification4: ret ReadMove: push hl push de push bc dec a ld hl, Moves ld bc, MOVE_LENGTH call AddNTimes ld de, wEnemyMoveNum call 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 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" JugglerAI: cp 25 percent + 1 ret nc jp AISwitchIfEnoughMons BlackbeltAI: cp 13 percent - 1 ret nc jp AIUseXAttack GiovanniAI: cp 25 percent + 1 ret nc jp AIUseGuardSpec CooltrainerMAI: cp 25 percent + 1 ret nc jp AIUseXAttack 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 jp AIUseXDefend LtSurgeAI: cp 25 percent + 1 ret nc jp AIUseXSpeed ErikaAI: cp 50 percent + 1 ret nc ld a, 10 call AICheckIfHPBelowFraction ret nc jp AIUseSuperPotion KogaAI: cp 25 percent + 1 ret nc jp AIUseXAttack BlaineAI: cp 25 percent + 1 ret nc jp AIUseSuperPotion SabrinaAI: cp 25 percent + 1 ret nc ld a, 10 call AICheckIfHPBelowFraction ret nc jp AIUseHyperPotion Rival2AI: cp 13 percent - 1 ret nc ld a, 5 call AICheckIfHPBelowFraction ret nc jp AIUsePotion Rival3AI: cp 13 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 AIUseSuperPotion BrunoAI: cp 25 percent + 1 ret nc jp AIUseXDefend AgathaAI: cp 8 percent jp c, AISwitchIfEnoughMons cp 50 percent + 1 ret nc ld a, 4 call AICheckIfHPBelowFraction ret nc jp AIUseSuperPotion LanceAI: cp 50 percent + 1 ret nc ld a, 5 call AICheckIfHPBelowFraction ret nc jp AIUseHyperPotion 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 SwitchEnemyMon: ; 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 call CopyData ld hl, AIBattleWithdrawText call PrintText ; 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 scf ret AIBattleWithdrawText: text_far _AIBattleWithdrawText text_end AIUseFullHeal: call AIPlayRestoringSFX call AICureStatus ld a, FULL_HEAL jp AIPrintItemUse AICureStatus: ; 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 ld [wEnemyMonStatus], a ; clear status of active enemy ld hl, wEnemyBattleStatus3 res 0, [hl] ret AIUseXAccuracy: ; unused call AIPlayRestoringSFX ld hl, wEnemyBattleStatus2 set 0, [hl] ld a, X_ACCURACY jp AIPrintItemUse AIUseGuardSpec: call AIPlayRestoringSFX ld hl, wEnemyBattleStatus2 set 1, [hl] ld a, GUARD_SPEC jp AIPrintItemUse AIUseDireHit: ; unused call AIPlayRestoringSFX ld hl, wEnemyBattleStatus2 set 2, [hl] ld a, DIRE_HIT jp AIPrintItemUse 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, ANIM_AF 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