First Commit

Upload literally everything from the pokecrystal16 expand-move-ID branch
This commit is contained in:
Zeta_Null 2023-09-10 12:35:35 -04:00
commit 2f8a41f833
4618 changed files with 480386 additions and 0 deletions

845
engine/battle/ai/items.asm Normal file
View file

@ -0,0 +1,845 @@
AI_SwitchOrTryItem:
and a
ld a, [wBattleMode]
dec a
ret z
ld a, [wLinkMode]
and a
ret nz
farcall CheckEnemyLockedIn
ret nz
ld a, [wPlayerSubStatus5]
bit SUBSTATUS_CANT_RUN, a
jr nz, DontSwitch
ld a, [wEnemyWrapCount]
and a
jr nz, DontSwitch
; always load the first trainer class in wTrainerClass for Battle Tower trainers
ld hl, TrainerClassAttributes + TRNATTR_AI_ITEM_SWITCH
ld a, [wInBattleTowerBattle]
and a
jr nz, .ok
ld a, [wTrainerClass]
dec a
ld bc, NUM_TRAINER_ATTRIBUTES
call AddNTimes
.ok
bit SWITCH_OFTEN_F, [hl]
jp nz, SwitchOften
bit SWITCH_RARELY_F, [hl]
jp nz, SwitchRarely
bit SWITCH_SOMETIMES_F, [hl]
jp nz, SwitchSometimes
; fallthrough
DontSwitch:
call AI_TryItem
ret
SwitchOften:
callfar CheckAbleToSwitch
ld a, [wEnemySwitchMonParam]
and $f0
jp z, DontSwitch
cp $10
jr nz, .not_10
call Random
cp 50 percent + 1
jr c, .switch
jp DontSwitch
.not_10
cp $20
jr nz, .not_20
call Random
cp 79 percent - 1
jr c, .switch
jp DontSwitch
.not_20
; $30
call Random
cp 4 percent
jp c, DontSwitch
.switch
ld a, [wEnemySwitchMonParam]
and $f
inc a
; In register 'a' is the number (1-6) of the mon to switch to
ld [wEnemySwitchMonIndex], a
jp AI_TrySwitch
SwitchRarely:
callfar CheckAbleToSwitch
ld a, [wEnemySwitchMonParam]
and $f0
jp z, DontSwitch
cp $10
jr nz, .not_10
call Random
cp 8 percent
jr c, .switch
jp DontSwitch
.not_10
cp $20
jr nz, .not_20
call Random
cp 12 percent
jr c, .switch
jp DontSwitch
.not_20
; $30
call Random
cp 79 percent - 1
jp c, DontSwitch
.switch
ld a, [wEnemySwitchMonParam]
and $f
inc a
ld [wEnemySwitchMonIndex], a
jp AI_TrySwitch
SwitchSometimes:
callfar CheckAbleToSwitch
ld a, [wEnemySwitchMonParam]
and $f0
jp z, DontSwitch
cp $10
jr nz, .not_10
call Random
cp 20 percent - 1
jr c, .switch
jp DontSwitch
.not_10
cp $20
jr nz, .not_20
call Random
cp 50 percent + 1
jr c, .switch
jp DontSwitch
.not_20
; $30
call Random
cp 20 percent - 1
jp c, DontSwitch
.switch
ld a, [wEnemySwitchMonParam]
and $f
inc a
ld [wEnemySwitchMonIndex], a
jp AI_TrySwitch
CheckSubstatusCantRun: ; unreferenced
ld a, [wEnemySubStatus5]
bit SUBSTATUS_CANT_RUN, a
ret
AI_TryItem:
; items are not allowed in the Battle Tower
ld a, [wInBattleTowerBattle]
and a
ret nz
ld a, [wEnemyTrainerItem1]
ld b, a
ld a, [wEnemyTrainerItem2]
or b
ret z
call .IsHighestLevel
ret nc
ld a, [wTrainerClass]
dec a
ld hl, TrainerClassAttributes + TRNATTR_AI_ITEM_SWITCH
ld bc, NUM_TRAINER_ATTRIBUTES
call AddNTimes
ld b, h
ld c, l
ld hl, AI_Items
ld de, wEnemyTrainerItem1
.loop
ld a, [hl]
and a
inc a
ret z
ld a, [de]
cp [hl]
jr z, .has_item
inc de
ld a, [de]
cp [hl]
jr z, .has_item
dec de
inc hl
inc hl
inc hl
jr .loop
.has_item
inc hl
push hl
push de
ld de, .callback
push de
ld a, [hli]
ld h, [hl]
ld l, a
jp hl
.callback
pop de
pop hl
inc hl
inc hl
jr c, .loop
; used item
xor a
ld [de], a
inc a
ld [wEnemyGoesFirst], a
ld hl, wEnemySubStatus3
res SUBSTATUS_BIDE, [hl]
xor a
ld [wEnemyFuryCutterCount], a
ld [wEnemyProtectCount], a
ld [wEnemyRageCounter], a
ld hl, wEnemySubStatus4
res SUBSTATUS_RAGE, [hl]
xor a
ld [wLastEnemyCounterMove], a
scf
ret
.IsHighestLevel:
ld a, [wOTPartyCount]
ld d, a
ld e, 0
ld hl, wOTPartyMon1Level
ld bc, PARTYMON_STRUCT_LENGTH
.next
ld a, [hl]
cp e
jr c, .ok
ld e, a
.ok
add hl, bc
dec d
jr nz, .next
ld a, [wCurOTMon]
ld hl, wOTPartyMon1Level
call AddNTimes
ld a, [hl]
cp e
jr nc, .yes
.no ; unreferenced
and a
ret
.yes
scf
ret
AI_Items:
dbw FULL_RESTORE, .FullRestore
dbw MAX_POTION, .MaxPotion
dbw HYPER_POTION, .HyperPotion
dbw SUPER_POTION, .SuperPotion
dbw POTION, .Potion
dbw X_ACCURACY, .XAccuracy
dbw FULL_HEAL, .FullHeal
dbw GUARD_SPEC, .GuardSpec
dbw DIRE_HIT, .DireHit
dbw X_ATTACK, .XAttack
dbw X_DEFEND, .XDefend
dbw X_SPEED, .XSpeed
dbw X_SPECIAL, .XSpecial
db -1 ; end
.FullHeal:
call .Status
jp c, .DontUse
call EnemyUsedFullHeal
jp .Use
.Status:
ld a, [wEnemyMonStatus]
and a
jp z, .DontUse
ld a, [bc]
bit CONTEXT_USE_F, a
jr nz, .StatusCheckContext
ld a, [bc]
bit ALWAYS_USE_F, a
jp nz, .Use
call Random
cp 20 percent - 1
jp c, .Use
jp .DontUse
.StatusCheckContext:
ld a, [wEnemySubStatus5]
bit SUBSTATUS_TOXIC, a
jr z, .FailToxicCheck
ld a, [wEnemyToxicCount]
cp 4
jr c, .FailToxicCheck
call Random
cp 50 percent + 1
jp c, .Use
.FailToxicCheck:
ld a, [wEnemyMonStatus]
and 1 << FRZ | SLP_MASK
jp z, .DontUse
jp .Use
.FullRestore:
call .HealItem
jp nc, .UseFullRestore
ld a, [bc]
bit CONTEXT_USE_F, a
jp z, .DontUse
call .Status
jp c, .DontUse
.UseFullRestore:
call EnemyUsedFullRestore
jp .Use
.MaxPotion:
call .HealItem
jp c, .DontUse
call EnemyUsedMaxPotion
jp .Use
.HealItem:
ld a, [bc]
bit CONTEXT_USE_F, a
jr nz, .CheckHalfOrQuarterHP
callfar AICheckEnemyHalfHP
jp c, .DontUse
ld a, [bc]
bit UNKNOWN_USE_F, a
jp nz, .CheckQuarterHP
callfar AICheckEnemyQuarterHP
jp nc, .UseHealItem
call Random
cp 50 percent + 1
jp c, .UseHealItem
jp .DontUse
.CheckQuarterHP:
callfar AICheckEnemyQuarterHP
jp c, .DontUse
call Random
cp 20 percent - 1
jp c, .DontUse
jr .UseHealItem
.CheckHalfOrQuarterHP:
callfar AICheckEnemyHalfHP
jp c, .DontUse
callfar AICheckEnemyQuarterHP
jp nc, .UseHealItem
call Random
cp 20 percent - 1
jp nc, .DontUse
.UseHealItem:
jp .Use
.HyperPotion:
call .HealItem
jp c, .DontUse
ld b, 200
call EnemyUsedHyperPotion
jp .Use
.SuperPotion:
call .HealItem
jp c, .DontUse
ld b, 50
call EnemyUsedSuperPotion
jp .Use
.Potion:
call .HealItem
jp c, .DontUse
ld b, 20
call EnemyUsedPotion
jp .Use
; Everything up to "End unused" is unused
.UnusedHealItem: ; unreferenced
; This has similar conditions to .HealItem
callfar AICheckEnemyMaxHP
jr c, .dont_use
push bc
ld de, wEnemyMonMaxHP + 1
ld hl, wEnemyMonHP + 1
ld a, [de]
sub [hl]
jr z, .check_40_percent
dec hl
dec de
ld c, a
sbc [hl]
and a
jr nz, .check_40_percent
ld a, c
cp b
jp c, .check_50_percent
callfar AICheckEnemyQuarterHP
jr c, .check_40_percent
.check_50_percent
pop bc
ld a, [bc]
bit UNKNOWN_USE_F, a
jp z, .Use
call Random
cp 50 percent + 1
jp c, .Use
.dont_use
jp .DontUse
.check_40_percent
pop bc
ld a, [bc]
bit UNKNOWN_USE_F, a
jp z, .DontUse
call Random
cp 39 percent + 1
jp c, .Use
jp .DontUse
; End unused
.XAccuracy:
call .XItem
jp c, .DontUse
call EnemyUsedXAccuracy
jp .Use
.GuardSpec:
call .XItem
jp c, .DontUse
call EnemyUsedGuardSpec
jp .Use
.DireHit:
call .XItem
jp c, .DontUse
call EnemyUsedDireHit
jp .Use
.XAttack:
call .XItem
jp c, .DontUse
call EnemyUsedXAttack
jp .Use
.XDefend:
call .XItem
jp c, .DontUse
call EnemyUsedXDefend
jp .Use
.XSpeed:
call .XItem
jp c, .DontUse
call EnemyUsedXSpeed
jp .Use
.XSpecial:
call .XItem
jp c, .DontUse
call EnemyUsedXSpecial
jp .Use
.XItem:
ld a, [wEnemyTurnsTaken]
and a
jr nz, .notfirstturnout
ld a, [bc]
bit ALWAYS_USE_F, a
jp nz, .Use
call Random
cp 50 percent + 1
jp c, .DontUse
ld a, [bc]
bit CONTEXT_USE_F, a
jp nz, .Use
call Random
cp 50 percent + 1
jp c, .DontUse
jp .Use
.notfirstturnout
ld a, [bc]
bit ALWAYS_USE_F, a
jp z, .DontUse
call Random
cp 20 percent - 1
jp nc, .DontUse
jp .Use
.DontUse:
scf
ret
.Use:
and a
ret
AIUpdateHUD:
call UpdateEnemyMonInParty
farcall UpdateEnemyHUD
ld a, $1
ldh [hBGMapMode], a
ld hl, wEnemyItemState
dec [hl]
scf
ret
AIUsedItemSound:
push de
ld de, SFX_FULL_HEAL
call PlaySFX
pop de
ret
EnemyUsedFullHeal:
call AIUsedItemSound
call AI_HealStatus
ld a, FULL_HEAL
jp PrintText_UsedItemOn_AND_AIUpdateHUD
EnemyUsedMaxPotion:
ld a, MAX_POTION
ld [wCurEnemyItem], a
jr FullRestoreContinue
EnemyUsedFullRestore:
; BUG: AI use of Full Heal does not cure confusion status (see docs/bugs_and_glitches.md)
call AI_HealStatus
ld a, FULL_RESTORE
ld [wCurEnemyItem], a
ld hl, wEnemySubStatus3
res SUBSTATUS_CONFUSED, [hl]
xor a
ld [wEnemyConfuseCount], a
; fallthrough
FullRestoreContinue:
ld de, wCurHPAnimOldHP
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 [wCurHPAnimMaxHP], a
ld [wEnemyMonHP + 1], a
ld a, [hl]
ld [de], a
ld [wCurHPAnimMaxHP + 1], a
ld [wEnemyMonHP], a
jr EnemyPotionFinish
EnemyUsedPotion:
ld a, POTION
ld b, 20
jr EnemyPotionContinue
EnemyUsedSuperPotion:
ld a, SUPER_POTION
ld b, 50
jr EnemyPotionContinue
EnemyUsedHyperPotion:
ld a, HYPER_POTION
ld b, 200
EnemyPotionContinue:
ld [wCurEnemyItem], a
ld hl, wEnemyMonHP + 1
ld a, [hl]
ld [wCurHPAnimOldHP], a
add b
ld [hld], a
ld [wCurHPAnimNewHP], a
ld a, [hl]
ld [wCurHPAnimOldHP + 1], a
ld [wCurHPAnimNewHP + 1], a
jr nc, .ok
inc a
ld [hl], a
ld [wCurHPAnimNewHP + 1], a
.ok
inc hl
ld a, [hld]
ld b, a
ld de, wEnemyMonMaxHP + 1
ld a, [de]
dec de
ld [wCurHPAnimMaxHP], a
sub b
ld a, [hli]
ld b, a
ld a, [de]
ld [wCurHPAnimMaxHP + 1], a
sbc b
jr nc, EnemyPotionFinish
inc de
ld a, [de]
dec de
ld [hld], a
ld [wCurHPAnimNewHP], a
ld a, [de]
ld [hl], a
ld [wCurHPAnimNewHP + 1], a
EnemyPotionFinish:
call PrintText_UsedItemOn
hlcoord 2, 2
xor a
ld [wWhichHPBar], a
call AIUsedItemSound
predef AnimateHPBar
jp AIUpdateHUD
AI_TrySwitch:
; Determine whether the AI can switch based on how many Pokemon are still alive.
; If it can switch, it will.
ld a, [wOTPartyCount]
ld c, a
ld hl, wOTPartyMon1HP
ld d, 0
.SwitchLoop:
ld a, [hli]
ld b, a
ld a, [hld]
or b
jr z, .fainted
inc d
.fainted
push bc
ld bc, PARTYMON_STRUCT_LENGTH
add hl, bc
pop bc
dec c
jr nz, .SwitchLoop
ld a, d
cp 2
jp nc, AI_Switch
and a
ret
AI_Switch:
ld a, $1
ld [wEnemyIsSwitching], a
ld [wEnemyGoesFirst], a
ld hl, wEnemySubStatus4
res SUBSTATUS_RAGE, [hl]
xor a
ldh [hBattleTurn], a
callfar PursuitSwitch
push af
ld a, [wCurOTMon]
ld hl, wOTPartyMon1Status
ld bc, PARTYMON_STRUCT_LENGTH
call AddNTimes
ld d, h
ld e, l
ld hl, wEnemyMonStatus
ld bc, MON_MAXHP - MON_STATUS
call CopyBytes
pop af
jr c, .skiptext
ld hl, EnemyWithdrewText
call PrintText
.skiptext
ld a, 1
ld [wBattleHasJustStarted], a
callfar NewEnemyMonStatus
callfar ResetEnemyStatLevels
ld hl, wPlayerSubStatus1
res SUBSTATUS_IN_LOVE, [hl]
farcall EnemySwitch
farcall ResetBattleParticipants
xor a
ld [wBattleHasJustStarted], a
ld a, [wLinkMode]
and a
ret nz
scf
ret
EnemyWithdrewText:
text_far _EnemyWithdrewText
text_end
EnemyUsedFullHealRed: ; unreferenced
call AIUsedItemSound
call AI_HealStatus
ld a, FULL_HEAL_RED ; X_SPEED
jp PrintText_UsedItemOn_AND_AIUpdateHUD
AI_HealStatus:
; BUG: AI use of Full Heal or Full Restore does not cure Nightmare status (see docs/bugs_and_glitches.md)
ld a, [wCurOTMon]
ld hl, wOTPartyMon1Status
ld bc, PARTYMON_STRUCT_LENGTH
call AddNTimes
xor a
ld [hl], a
ld [wEnemyMonStatus], a
ld hl, wEnemySubStatus5
res SUBSTATUS_TOXIC, [hl]
ret
EnemyUsedXAccuracy:
call AIUsedItemSound
ld hl, wEnemySubStatus4
set SUBSTATUS_X_ACCURACY, [hl]
ld a, X_ACCURACY
jp PrintText_UsedItemOn_AND_AIUpdateHUD
EnemyUsedGuardSpec:
call AIUsedItemSound
ld hl, wEnemySubStatus4
set SUBSTATUS_MIST, [hl]
ld a, GUARD_SPEC
jp PrintText_UsedItemOn_AND_AIUpdateHUD
EnemyUsedDireHit:
call AIUsedItemSound
ld hl, wEnemySubStatus4
set SUBSTATUS_FOCUS_ENERGY, [hl]
ld a, DIRE_HIT
jp PrintText_UsedItemOn_AND_AIUpdateHUD
AICheckEnemyFractionMaxHP: ; unreferenced
; Input: a = divisor
; Work: bc = [wEnemyMonMaxHP] / a
; Work: de = [wEnemyMonHP]
; Output:
; - c, nz if [wEnemyMonHP] > [wEnemyMonMaxHP] / a
; - nc, z if [wEnemyMonHP] = [wEnemyMonMaxHP] / a
; - nc, nz if [wEnemyMonHP] < [wEnemyMonMaxHP] / a
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
EnemyUsedXAttack:
ld b, ATTACK
ld a, X_ATTACK
jr EnemyUsedXItem
EnemyUsedXDefend:
ld b, DEFENSE
ld a, X_DEFEND
jr EnemyUsedXItem
EnemyUsedXSpeed:
ld b, SPEED
ld a, X_SPEED
jr EnemyUsedXItem
EnemyUsedXSpecial:
ld b, SP_ATTACK
ld a, X_SPECIAL
; Parameter
; a = ITEM_CONSTANT
; b = BATTLE_CONSTANT (ATTACK, DEFENSE, SPEED, SP_ATTACK, SP_DEFENSE, ACCURACY, EVASION)
EnemyUsedXItem:
ld [wCurEnemyItem], a
push bc
call PrintText_UsedItemOn
pop bc
farcall RaiseStat
jp AIUpdateHUD
; Parameter
; a = ITEM_CONSTANT
PrintText_UsedItemOn_AND_AIUpdateHUD:
ld [wCurEnemyItem], a
call PrintText_UsedItemOn
jp AIUpdateHUD
PrintText_UsedItemOn:
ld a, [wCurEnemyItem]
ld [wNamedObjectIndex], a
call GetItemName
ld hl, wStringBuffer1
ld de, wMonOrItemNameBuffer
ld bc, ITEM_NAME_LENGTH
call CopyBytes
ld hl, EnemyUsedOnText
jp PrintText
EnemyUsedOnText:
text_far _EnemyUsedOnText
text_end

217
engine/battle/ai/move.asm Normal file
View file

@ -0,0 +1,217 @@
AIChooseMove:
; Score each move of wEnemyMonMoves in wEnemyAIMoveScores. Lower is better.
; Pick the move with the lowest score.
; Wildmons attack at random.
ld a, [wBattleMode]
dec a
ret z
ld a, [wLinkMode]
and a
ret nz
; No use picking a move if there's no choice.
farcall CheckEnemyLockedIn
ret nz
; The default score is 20. Unusable moves are given a score of 80.
ld a, 20
ld hl, wEnemyAIMoveScores
ld [hli], a
ld [hli], a
ld [hli], a
ld [hl], a
; Don't pick disabled moves.
ld a, [wEnemyDisabledMove]
and a
jr z, .CheckPP
ld hl, wEnemyMonMoves
ld c, 0
.CheckDisabledMove:
cp [hl]
jr z, .ScoreDisabledMove
inc c
inc hl
jr .CheckDisabledMove
.ScoreDisabledMove:
ld hl, wEnemyAIMoveScores
ld b, 0
add hl, bc
ld [hl], 80
; Don't pick moves with 0 PP.
.CheckPP:
ld hl, wEnemyAIMoveScores - 1
ld de, wEnemyMonPP
ld b, 0
.CheckMovePP:
inc b
ld a, b
cp NUM_MOVES + 1
jr z, .ApplyLayers
inc hl
ld a, [de]
inc de
and PP_MASK
jr nz, .CheckMovePP
ld [hl], 80
jr .CheckMovePP
; Apply AI scoring layers depending on the trainer class.
.ApplyLayers:
ld hl, TrainerClassAttributes + TRNATTR_AI_MOVE_WEIGHTS
; If we have a battle in BattleTower just load the Attributes of the first trainer class in wTrainerClass (Falkner)
; so we have always the same AI, regardless of the loaded class of trainer
ld a, [wInBattleTowerBattle]
bit 0, a
jr nz, .battle_tower_skip
ld a, [wTrainerClass]
dec a
ld bc, 7 ; Trainer2AI - Trainer1AI
call AddNTimes
.battle_tower_skip
lb bc, CHECK_FLAG, 0
push bc
push hl
.CheckLayer:
pop hl
pop bc
ld a, c
cp 16 ; up to 16 scoring layers
jr z, .DecrementScores
push bc
ld d, BANK(TrainerClassAttributes)
predef SmallFarFlagAction
ld d, c
pop bc
inc c
push bc
push hl
ld a, d
and a
jr z, .CheckLayer
ld hl, AIScoringPointers
dec c
ld b, 0
add hl, bc
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
ld a, BANK(AIScoring)
call FarCall_hl
jr .CheckLayer
; Decrement the scores of all moves one by one until one reaches 0.
.DecrementScores:
ld hl, wEnemyAIMoveScores
ld de, wEnemyMonMoves
ld c, NUM_MOVES
.DecrementNextScore:
; If the enemy has no moves, this will infinite.
ld a, [de]
inc de
and a
jr z, .DecrementScores
; We are done whenever a score reaches 0
dec [hl]
jr z, .PickLowestScoreMoves
; If we just decremented the fourth move's score, go back to the first move
inc hl
dec c
jr z, .DecrementScores
jr .DecrementNextScore
; In order to avoid bias towards the moves located first in memory, increment the scores
; that were decremented one more time than the rest (in case there was a tie).
; This means that the minimum score will be 1.
.PickLowestScoreMoves:
ld a, c
.move_loop
inc [hl]
dec hl
inc a
cp NUM_MOVES + 1
jr nz, .move_loop
ld hl, wEnemyAIMoveScores
ld de, wEnemyMonMoves
ld c, NUM_MOVES
; Give a score of 0 to a blank move
.loop2
ld a, [de]
and a
jr nz, .skip_load
ld [hl], a
; Disregard the move if its score is not 1
.skip_load
ld a, [hl]
dec a
jr z, .keep
xor a
ld [hli], a
jr .after_toss
.keep
ld a, [de]
ld [hli], a
.after_toss
inc de
dec c
jr nz, .loop2
; Randomly choose one of the moves with a score of 1
.ChooseMove:
ld hl, wEnemyAIMoveScores
call Random
maskbits NUM_MOVES
ld c, a
ld b, 0
add hl, bc
ld a, [hl]
and a
jr z, .ChooseMove
ld [wCurEnemyMove], a
ld a, c
ld [wCurEnemyMoveNum], a
ret
AIScoringPointers:
; entries correspond to AI_* constants
dw AI_Basic
dw AI_Setup
dw AI_Types
dw AI_Offensive
dw AI_Smart
dw AI_Opportunist
dw AI_Aggressive
dw AI_Cautious
dw AI_Status
dw AI_Risky
dw AI_None
dw AI_None
dw AI_None
dw AI_None
dw AI_None
dw AI_None

View file

@ -0,0 +1,199 @@
AI_Redundant:
; Check if move effect c will fail because it's already been used.
; Return z if the move is a good choice.
; Return nz if the move is a bad choice.
ld a, c
ld de, 3
ld hl, .Moves
call IsInArray
jp nc, .NotRedundant
inc hl
ld a, [hli]
ld h, [hl]
ld l, a
jp hl
.Moves:
dbw EFFECT_DREAM_EATER, .DreamEater
dbw EFFECT_HEAL, .Heal
dbw EFFECT_LIGHT_SCREEN, .LightScreen
dbw EFFECT_MIST, .Mist
dbw EFFECT_FOCUS_ENERGY, .FocusEnergy
dbw EFFECT_CONFUSE, .Confuse
dbw EFFECT_TRANSFORM, .Transform
dbw EFFECT_REFLECT, .Reflect
dbw EFFECT_SUBSTITUTE, .Substitute
dbw EFFECT_LEECH_SEED, .LeechSeed
dbw EFFECT_DISABLE, .Disable
dbw EFFECT_ENCORE, .Encore
dbw EFFECT_SNORE, .Snore
dbw EFFECT_SLEEP_TALK, .SleepTalk
dbw EFFECT_MEAN_LOOK, .MeanLook
dbw EFFECT_NIGHTMARE, .Nightmare
dbw EFFECT_SPIKES, .Spikes
dbw EFFECT_FORESIGHT, .Foresight
dbw EFFECT_PERISH_SONG, .PerishSong
dbw EFFECT_SANDSTORM, .Sandstorm
dbw EFFECT_ATTRACT, .Attract
dbw EFFECT_SAFEGUARD, .Safeguard
dbw EFFECT_RAIN_DANCE, .RainDance
dbw EFFECT_SUNNY_DAY, .SunnyDay
dbw EFFECT_TELEPORT, .Teleport
dbw EFFECT_MORNING_SUN, .MorningSun
dbw EFFECT_SYNTHESIS, .Synthesis
dbw EFFECT_MOONLIGHT, .Moonlight
dbw EFFECT_SWAGGER, .Swagger
dbw EFFECT_FUTURE_SIGHT, .FutureSight
db -1
.LightScreen:
ld a, [wEnemyScreens]
bit SCREENS_LIGHT_SCREEN, a
ret
.Mist:
ld a, [wEnemySubStatus4]
bit SUBSTATUS_MIST, a
ret
.FocusEnergy:
ld a, [wEnemySubStatus4]
bit SUBSTATUS_FOCUS_ENERGY, a
ret
.Confuse:
ld a, [wPlayerSubStatus3]
bit SUBSTATUS_CONFUSED, a
ret nz
ld a, [wPlayerScreens]
bit SCREENS_SAFEGUARD, a
ret
.Transform:
ld a, [wEnemySubStatus5]
bit SUBSTATUS_TRANSFORMED, a
ret
.Reflect:
ld a, [wEnemyScreens]
bit SCREENS_REFLECT, a
ret
.Substitute:
ld a, [wEnemySubStatus4]
bit SUBSTATUS_SUBSTITUTE, a
ret
.LeechSeed:
ld a, [wPlayerSubStatus4]
bit SUBSTATUS_LEECH_SEED, a
ret
.Disable:
ld a, [wPlayerDisableCount]
and a
ret
.Encore:
ld a, [wPlayerSubStatus5]
bit SUBSTATUS_ENCORED, a
ret
.Snore:
.SleepTalk:
ld a, [wEnemyMonStatus]
and SLP_MASK
jr z, .Redundant
jr .NotRedundant
.MeanLook:
ld a, [wEnemySubStatus5]
bit SUBSTATUS_CANT_RUN, a
ret
.Nightmare:
ld a, [wBattleMonStatus]
and a
jr z, .Redundant
ld a, [wPlayerSubStatus1]
bit SUBSTATUS_NIGHTMARE, a
ret
.Spikes:
ld a, [wPlayerScreens]
bit SCREENS_SPIKES, a
ret
.Foresight:
ld a, [wPlayerSubStatus1]
bit SUBSTATUS_IDENTIFIED, a
ret
.PerishSong:
ld a, [wPlayerSubStatus1]
bit SUBSTATUS_PERISH, a
ret
.Sandstorm:
ld a, [wBattleWeather]
cp WEATHER_SANDSTORM
jr z, .Redundant
jr .NotRedundant
.Attract:
farcall CheckOppositeGender
jr c, .Redundant
ld a, [wPlayerSubStatus1]
bit SUBSTATUS_IN_LOVE, a
ret
.Safeguard:
ld a, [wEnemyScreens]
bit SCREENS_SAFEGUARD, a
ret
.RainDance:
ld a, [wBattleWeather]
cp WEATHER_RAIN
jr z, .Redundant
jr .NotRedundant
.SunnyDay:
ld a, [wBattleWeather]
cp WEATHER_SUN
jr z, .Redundant
jr .NotRedundant
.DreamEater:
ld a, [wBattleMonStatus]
and SLP_MASK
jr z, .Redundant
jr .NotRedundant
.Swagger:
ld a, [wPlayerSubStatus3]
bit SUBSTATUS_CONFUSED, a
ret
.FutureSight:
; BUG: AI does not discourage Future Sight when it's already been used (see docs/bugs_and_glitches.md)
ld a, [wEnemyScreens]
bit 5, a
ret
.Heal:
.MorningSun:
.Synthesis:
.Moonlight:
farcall AICheckEnemyMaxHP
jr nc, .NotRedundant
.Teleport:
.Redundant:
ld a, 1
and a
ret
.NotRedundant:
xor a
ret

3293
engine/battle/ai/scoring.asm Normal file

File diff suppressed because it is too large Load diff

650
engine/battle/ai/switch.asm Normal file
View file

@ -0,0 +1,650 @@
CheckPlayerMoveTypeMatchups:
; Check how well the moves you've already used
; fare against the enemy's Pokemon. Used to
; score a potential switch.
push hl
push de
push bc
ld a, BASE_AI_SWITCH_SCORE
ld [wEnemyAISwitchScore], a
ld hl, wPlayerUsedMoves
ld a, [hl]
and a
jr z, .unknown_moves
ld d, NUM_MOVES
ld e, 0
.loop
ld a, [hli]
and a
jr z, .exit
push hl
call GetMoveTypeIfDamaging
jr z, .next
ld hl, wEnemyMonType
call CheckTypeMatchup
ld a, [wTypeMatchup]
cp EFFECTIVE + 1 ; 1.0 + 0.1
jr nc, .super_effective
and a
jr z, .next
cp EFFECTIVE ; 1.0
jr nc, .neutral
; not very effective
ld a, e
cp 1 ; 0.1
jr nc, .next
ld e, 1
jr .next
.neutral
ld e, 2
jr .next
.super_effective
call .DecreaseScore
pop hl
jr .done
.next
pop hl
dec d
jr nz, .loop
.exit
ld a, e
cp 2
jr z, .done
call .IncreaseScore
ld a, e
and a
jr nz, .done
call .IncreaseScore
jr .done
.unknown_moves
ld a, [wBattleMonType1]
ld b, a
ld hl, wEnemyMonType1
call CheckTypeMatchup
ld a, [wTypeMatchup]
cp EFFECTIVE + 1 ; 1.0 + 0.1
jr c, .ok
call .DecreaseScore
.ok
ld a, [wBattleMonType2]
cp b
jr z, .ok2
call CheckTypeMatchup
ld a, [wTypeMatchup]
cp EFFECTIVE + 1 ; 1.0 + 0.1
jr c, .ok2
call .DecreaseScore
.ok2
.done
call .CheckEnemyMoveMatchups
pop bc
pop de
pop hl
ret
.CheckEnemyMoveMatchups:
ld de, wEnemyMonMoves
ld b, NUM_MOVES + 1
ld c, 0
ld a, [wTypeMatchup]
push af
.loop2
dec b
jr z, .exit2
ld a, [de]
and a
jr z, .exit2
inc de
call GetMoveTypeIfDamaging
jr z, .loop2
ld hl, wBattleMonType1
call CheckTypeMatchup
ld a, [wTypeMatchup]
; immune
and a
jr z, .loop2
; not very effective
inc c
cp EFFECTIVE
jr c, .loop2
; neutral
inc c
inc c
inc c
inc c
inc c
cp EFFECTIVE
jr z, .loop2
; super effective
ld c, 100
jr .loop2
.exit2
pop af
ld [wTypeMatchup], a
ld a, c
and a
jr z, .doubledown ; double down
cp 5
jr c, .DecreaseScore ; down
cp 100
ret c
jr .IncreaseScore ; up
.doubledown
call .DecreaseScore
.DecreaseScore:
ld a, [wEnemyAISwitchScore]
dec a
ld [wEnemyAISwitchScore], a
ret
.IncreaseScore:
ld a, [wEnemyAISwitchScore]
inc a
ld [wEnemyAISwitchScore], a
ret
CheckAbleToSwitch:
xor a
ld [wEnemySwitchMonParam], a
call FindAliveEnemyMons
ret c
ld a, [wEnemySubStatus1]
bit SUBSTATUS_PERISH, a
jr z, .no_perish
ld a, [wEnemyPerishCount]
cp 1
jr nz, .no_perish
; Perish count is 1
call FindAliveEnemyMons
call FindEnemyMonsWithAtLeastQuarterMaxHP
call FindEnemyMonsThatResistPlayer
call FindAliveEnemyMonsWithASuperEffectiveMove
ld a, e
cp 2
jr nz, .not_2
ld a, [wEnemyAISwitchScore]
add $30 ; maximum chance
ld [wEnemySwitchMonParam], a
ret
.not_2
call FindAliveEnemyMons
sla c
sla c
ld b, $ff
.loop1
inc b
sla c
jr nc, .loop1
ld a, b
add $30 ; maximum chance
ld [wEnemySwitchMonParam], a
ret
.no_perish
call CheckPlayerMoveTypeMatchups
ld a, [wEnemyAISwitchScore]
cp 11
ret nc
ld a, [wLastPlayerCounterMove]
and a
jr z, .no_last_counter_move
call FindEnemyMonsImmuneToLastCounterMove
ld a, [wEnemyAISwitchScore]
and a
jr z, .no_last_counter_move
ld c, a
call FindEnemyMonsWithASuperEffectiveMove
ld a, [wEnemyAISwitchScore]
cp $ff
ret z
ld b, a
ld a, e
cp 2
jr z, .not_2_again
call CheckPlayerMoveTypeMatchups
ld a, [wEnemyAISwitchScore]
cp 10
ret nc
ld a, b
add $10
ld [wEnemySwitchMonParam], a
ret
.not_2_again
ld c, $10
call CheckPlayerMoveTypeMatchups
ld a, [wEnemyAISwitchScore]
cp 10
jr nc, .okay
ld c, $20
.okay
ld a, b
add c
ld [wEnemySwitchMonParam], a
ret
.no_last_counter_move
call CheckPlayerMoveTypeMatchups
ld a, [wEnemyAISwitchScore]
cp 10
ret nc
call FindAliveEnemyMons
call FindEnemyMonsWithAtLeastQuarterMaxHP
call FindEnemyMonsThatResistPlayer
call FindAliveEnemyMonsWithASuperEffectiveMove
ld a, e
cp $2
ret nz
ld a, [wEnemyAISwitchScore]
add $10
ld [wEnemySwitchMonParam], a
ret
FindAliveEnemyMons:
ld a, [wOTPartyCount]
cp 2
jr c, .only_one
ld d, a
ld e, 0
ld b, 1 << (PARTY_LENGTH - 1)
ld c, 0
ld hl, wOTPartyMon1HP
.loop
ld a, [wCurOTMon]
cp e
jr z, .next
push bc
ld b, [hl]
inc hl
ld a, [hld]
or b
pop bc
jr z, .next
ld a, c
or b
ld c, a
.next
srl b
push bc
ld bc, PARTYMON_STRUCT_LENGTH
add hl, bc
pop bc
inc e
dec d
jr nz, .loop
ld a, c
and a
jr nz, .more_than_one
.only_one
scf
ret
.more_than_one
and a
ret
FindEnemyMonsImmuneToLastCounterMove:
ld hl, wOTPartyMon1
ld a, [wOTPartyCount]
ld b, a
ld c, 1 << (PARTY_LENGTH - 1)
ld d, 0
xor a
ld [wEnemyAISwitchScore], a
.loop
ld a, [wCurOTMon]
cp d
push hl
jr z, .next
push hl
push bc
; If the Pokemon has at least 1 HP...
ld bc, MON_HP
add hl, bc
pop bc
ld a, [hli]
or [hl]
pop hl
jr z, .next
ld a, [hl]
ld [wCurSpecies], a
call GetBaseData
; the player's last move is damaging...
ld a, [wLastPlayerCounterMove]
call GetMoveTypeIfDamaging
jr z, .next
; and the Pokemon is immune to it...
ld hl, wBaseType
call CheckTypeMatchup
ld a, [wTypeMatchup]
and a
jr nz, .next
; ... encourage that Pokemon.
ld a, [wEnemyAISwitchScore]
or c
ld [wEnemyAISwitchScore], a
.next
pop hl
dec b
ret z
push bc
ld bc, PARTYMON_STRUCT_LENGTH
add hl, bc
pop bc
inc d
srl c
jr .loop
FindAliveEnemyMonsWithASuperEffectiveMove:
push bc
ld a, [wOTPartyCount]
ld e, a
ld hl, wOTPartyMon1HP
ld b, 1 << (PARTY_LENGTH - 1)
ld c, 0
.loop
ld a, [hli]
or [hl]
jr z, .next
ld a, b
or c
ld c, a
.next
srl b
push bc
ld bc, wPartyMon2HP - (wPartyMon1HP + 1)
add hl, bc
pop bc
dec e
jr nz, .loop
ld a, c
pop bc
and c
ld c, a
; fallthrough
FindEnemyMonsWithASuperEffectiveMove:
ld a, -1
ld [wEnemyAISwitchScore], a
ld hl, wOTPartyMon1Moves
ld b, 1 << (PARTY_LENGTH - 1)
ld d, 0
ld e, 0
.loop
ld a, b
and c
jr z, .next
push hl
push bc
; for move on mon:
ld b, NUM_MOVES
ld c, 0
.loop3
; if move is None: break
ld a, [hli]
and a
push hl
jr z, .break3
; if move has no power: continue
call GetMoveTypeIfDamaging
jr z, .nope
; check type matchups
ld hl, wBattleMonType1
call CheckTypeMatchup
; if immune or not very effective: continue
ld a, [wTypeMatchup]
cp 10
jr c, .nope
; if neutral: load 1 and continue
ld e, 1
cp EFFECTIVE + 1
jr c, .nope
; if super-effective: load 2 and break
ld e, 2
jr .break3
.nope
pop hl
dec b
jr nz, .loop3
jr .done
.break3
pop hl
.done
ld a, e
pop bc
pop hl
cp 2
jr z, .done2 ; at least one move is super-effective
cp 1
jr nz, .next ; no move does more than half damage
; encourage this pokemon
ld a, d
or b
ld d, a
jr .next ; such a long jump
.next
; next pokemon?
push bc
ld bc, PARTYMON_STRUCT_LENGTH
add hl, bc
pop bc
srl b
jr nc, .loop
; if no pokemon has a super-effective move: return
ld a, d
ld b, a
and a
ret z
.done2
; convert the bit flag to an int and return
push bc
sla b
sla b
ld c, $ff
.loop2
inc c
sla b
jr nc, .loop2
ld a, c
ld [wEnemyAISwitchScore], a
pop bc
ret
FindEnemyMonsThatResistPlayer:
push bc
ld hl, wOTPartySpecies
ld b, 1 << (PARTY_LENGTH - 1)
ld c, 0
.loop
ld a, [hli]
cp $ff
jr z, .done
push hl
ld [wCurSpecies], a
call GetBaseData
ld a, [wLastPlayerCounterMove]
and a
jr z, .skip_move
call GetMoveTypeIfDamaging
jr nz, .check_type
.skip_move
ld a, [wBattleMonType1]
ld hl, wBaseType
call CheckTypeMatchup
ld a, [wTypeMatchup]
cp 10 + 1
jr nc, .dont_choose_mon
ld a, [wBattleMonType2]
.check_type
ld hl, wBaseType
call CheckTypeMatchup
ld a, [wTypeMatchup]
cp EFFECTIVE + 1
jr nc, .dont_choose_mon
ld a, b
or c
ld c, a
.dont_choose_mon
srl b
pop hl
jr .loop
.done
ld a, c
pop bc
and c
ld c, a
ret
FindEnemyMonsWithAtLeastQuarterMaxHP:
push bc
ld de, wOTPartySpecies
ld b, 1 << (PARTY_LENGTH - 1)
ld c, 0
ld hl, wOTPartyMon1HP
.loop
ld a, [de]
inc de
cp $ff
jr z, .done
push hl
push bc
ld b, [hl]
inc hl
ld c, [hl]
inc hl
inc hl
; hl = MaxHP + 1
; bc = [CurHP] * 4
srl c
rl b
srl c
rl b
; if bc >= [hl], encourage
ld a, [hld]
cp c
ld a, [hl]
sbc b
pop bc
jr nc, .next
ld a, b
or c
ld c, a
.next
srl b
pop hl
push bc
ld bc, PARTYMON_STRUCT_LENGTH
add hl, bc
pop bc
jr .loop
.done
ld a, c
pop bc
and c
ld c, a
ret
GetMoveTypeIfDamaging:
; returns the type of move a in a, and sets the zero flag depending on whether the move causes damage
; clobbers hl
push bc
call GetMoveAddress
ld b, a
rept MOVE_POWER - 1
inc hl
endr
call GetFarByte
ld c, a
ld a, b
inc hl
call GetFarByte
inc c
dec c
pop bc
ret