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

View file

@ -0,0 +1,417 @@
_AnimateHPBar:
call .IsMaximumMoreThan48Pixels
jr c, .MoreThan48Pixels
call .ComputePixels
.ShortAnimLoop:
push bc
push hl
call ShortAnim_UpdateVariables
pop hl
pop bc
push af
push bc
push hl
call ShortHPBarAnim_UpdateTiles
call HPBarAnim_BGMapUpdate
pop hl
pop bc
pop af
jr nc, .ShortAnimLoop
ret
.MoreThan48Pixels:
call .ComputePixels
.LongAnimLoop:
push bc
push hl
call LongAnim_UpdateVariables
pop hl
pop bc
ret c
push af
push bc
push hl
call LongHPBarAnim_UpdateTiles
call HPBarAnim_BGMapUpdate
pop hl
pop bc
pop af
jr nc, .LongAnimLoop
ret
.IsMaximumMoreThan48Pixels:
ld a, [wCurHPAnimMaxHP + 1]
and a
jr nz, .player
ld a, [wCurHPAnimMaxHP]
cp HP_BAR_LENGTH_PX
jr nc, .player
and a
ret
.player
scf
ret
.ComputePixels:
push hl
ld hl, wCurHPAnimMaxHP
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
ld a, [hli]
ld c, a
ld a, [hli]
ld b, a
pop hl
call ComputeHPBarPixels
ld a, e
ld [wCurHPBarPixels], a
ld a, [wCurHPAnimNewHP]
ld c, a
ld a, [wCurHPAnimNewHP + 1]
ld b, a
ld a, [wCurHPAnimMaxHP]
ld e, a
ld a, [wCurHPAnimMaxHP + 1]
ld d, a
call ComputeHPBarPixels
ld a, e
ld [wNewHPBarPixels], a
push hl
ld hl, wCurHPAnimOldHP
ld a, [hli]
ld c, a
ld a, [hli]
ld b, a
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
pop hl
ld a, e
sub c
ld e, a
ld a, d
sbc b
ld d, a
jr c, .negative
ld a, [wCurHPAnimOldHP]
ld [wCurHPAnimLowHP], a
ld a, [wCurHPAnimNewHP]
ld [wCurHPAnimHighHP], a
ld bc, 1
jr .got_direction
.negative
ld a, [wCurHPAnimOldHP]
ld [wCurHPAnimHighHP], a
ld a, [wCurHPAnimNewHP]
ld [wCurHPAnimLowHP], a
ld a, e
xor $ff
inc a
ld e, a
ld a, d
xor $ff
ld d, a
ld bc, -1
.got_direction
ld a, d
ld [wCurHPAnimDeltaHP], a
ld a, e
ld [wCurHPAnimDeltaHP + 1], a
ret
ShortAnim_UpdateVariables:
ld hl, wCurHPBarPixels
ld a, [wNewHPBarPixels]
cp [hl]
jr nz, .not_finished
scf
ret
.not_finished
ld a, c
add [hl]
ld [hl], a
call ShortHPBar_CalcPixelFrame
and a
ret
LongAnim_UpdateVariables:
.loop
ld hl, wCurHPAnimOldHP
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
ld a, e
cp [hl]
jr nz, .next
inc hl
ld a, d
cp [hl]
jr nz, .next
scf
ret
.next
ld l, e
ld h, d
add hl, bc
ld a, l
ld [wCurHPAnimOldHP], a
ld a, h
ld [wCurHPAnimOldHP + 1], a
push hl
push de
push bc
ld hl, wCurHPAnimMaxHP
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
ld a, [hli]
ld c, a
ld a, [hli]
ld b, a
; BUG: HP bar animation is slow for high HP (see docs/bugs_and_glitches.md)
call ComputeHPBarPixels
pop bc
pop de
pop hl
ld a, e
ld hl, wCurHPBarPixels
cp [hl]
jr z, .loop
ld [hl], a
and a
ret
ShortHPBarAnim_UpdateTiles:
call HPBarAnim_UpdateHPRemaining
ld d, HP_BAR_LENGTH
ld a, [wWhichHPBar]
and $1
ld b, a
ld a, [wCurHPBarPixels]
ld e, a
ld c, a
push de
call HPBarAnim_RedrawHPBar
pop de
call HPBarAnim_PaletteUpdate
ret
LongHPBarAnim_UpdateTiles:
call HPBarAnim_UpdateHPRemaining
ld a, [wCurHPAnimOldHP]
ld c, a
ld a, [wCurHPAnimOldHP + 1]
ld b, a
ld a, [wCurHPAnimMaxHP]
ld e, a
ld a, [wCurHPAnimMaxHP + 1]
ld d, a
call ComputeHPBarPixels
ld c, e
ld d, HP_BAR_LENGTH
ld a, [wWhichHPBar]
and $1
ld b, a
push de
call HPBarAnim_RedrawHPBar
pop de
call HPBarAnim_PaletteUpdate
ret
HPBarAnim_RedrawHPBar:
ld a, [wWhichHPBar]
cp $2
jr nz, .skip
ld a, 2 * SCREEN_WIDTH
add l
ld l, a
ld a, 0
adc h
ld h, a
.skip
call DrawBattleHPBar
ret
HPBarAnim_UpdateHPRemaining:
ld a, [wWhichHPBar]
and a
ret z
cp $1
jr z, .load_15
ld de, SCREEN_WIDTH + 2
jr .loaded_de
.load_15
ld de, SCREEN_WIDTH + 1
.loaded_de
push hl
add hl, de
ld a, " "
ld [hli], a
ld [hli], a
ld [hld], a
dec hl
ld a, [wCurHPAnimOldHP]
ld [wStringBuffer2 + 1], a
ld a, [wCurHPAnimOldHP + 1]
ld [wStringBuffer2], a
ld de, wStringBuffer2
lb bc, 2, 3
call PrintNum
pop hl
ret
HPBarAnim_PaletteUpdate:
ldh a, [hCGB]
and a
ret z
ld hl, wCurHPAnimPal
call SetHPPal
ld a, [wCurHPAnimPal]
ld c, a
farcall ApplyHPBarPals
ret
HPBarAnim_BGMapUpdate:
ldh a, [hCGB]
and a
jr nz, .cgb
call DelayFrame
call DelayFrame
ret
.cgb
ld a, [wWhichHPBar]
and a
jr z, .load_0
cp $1
jr z, .load_1
ld a, [wCurPartyMon]
cp $3
jr nc, .bottom_half_of_screen
ld c, $0
jr .got_third
.bottom_half_of_screen
ld c, $1
.got_third
push af
cp $2
jr z, .skip_delay
cp $5
jr z, .skip_delay
ld a, $2
ldh [hBGMapMode], a
ld a, c
ldh [hBGMapThird], a
call DelayFrame
.skip_delay
ld a, $1
ldh [hBGMapMode], a
ld a, c
ldh [hBGMapThird], a
call DelayFrame
pop af
cp $2
jr z, .two_frames
cp $5
jr z, .two_frames
ret
.two_frames
inc c
ld a, $2
ldh [hBGMapMode], a
ld a, c
ldh [hBGMapThird], a
call DelayFrame
ld a, $1
ldh [hBGMapMode], a
ld a, c
ldh [hBGMapThird], a
call DelayFrame
ret
.load_0
ld c, $0
jr .finish
.load_1
ld c, $1
.finish
call DelayFrame
ld a, c
ldh [hBGMapThird], a
call DelayFrame
ret
ShortHPBar_CalcPixelFrame:
ld a, [wCurHPAnimMaxHP]
ld c, a
ld b, 0
ld hl, 0
ld a, [wCurHPBarPixels]
cp HP_BAR_LENGTH_PX
jr nc, .return_max
and a
jr z, .return_zero
call AddNTimes
ld b, 0
.loop
; BUG: HP bar animation off-by-one error for low HP (see docs/bugs_and_glitches.md)
ld a, l
sub HP_BAR_LENGTH_PX
ld l, a
ld a, h
sbc $0
ld h, a
jr c, .done
inc b
jr .loop
.done
push bc
ld bc, $80
add hl, bc
pop bc
ld a, l
sub HP_BAR_LENGTH_PX
ld l, a
ld a, h
sbc $0
ld h, a
jr c, .no_carry
inc b
.no_carry
ld a, [wCurHPAnimLowHP]
cp b
jr nc, .finish
ld a, [wCurHPAnimHighHP]
cp b
jr c, .finish
ld a, b
.finish
ld [wCurHPAnimOldHP], a
ret
.return_zero
xor a
ld [wCurHPAnimOldHP], a
ret
.return_max
ld a, [wCurHPAnimMaxHP]
ld [wCurHPAnimOldHP], a
ret

View file

@ -0,0 +1,840 @@
; BattleTransitionJumptable.Jumptable indexes
DEF BATTLETRANSITION_CAVE EQU $01
DEF BATTLETRANSITION_CAVE_STRONGER EQU $09
DEF BATTLETRANSITION_NO_CAVE EQU $10
DEF BATTLETRANSITION_NO_CAVE_STRONGER EQU $18
DEF BATTLETRANSITION_FINISH EQU $20
DEF BATTLETRANSITION_END EQU $80
DEF BATTLETRANSITION_SQUARE EQU "8" ; $fe
DEF BATTLETRANSITION_BLACK EQU "9" ; $ff
DoBattleTransition:
call .InitGFX
ldh a, [rBGP]
ld [wBGP], a
ldh a, [rOBP0]
ld [wOBP0], a
ldh a, [rOBP1]
ld [wOBP1], a
call DelayFrame
ld hl, hVBlank
ld a, [hl]
push af
vc_hook Reduce_battle_transition_flashing
ld [hl], $1
.loop
ld a, [wJumptableIndex]
bit 7, a ; BATTLETRANSITION_END?
jr nz, .done
call BattleTransitionJumptable
call DelayFrame
jr .loop
.done
ldh a, [rSVBK]
push af
ld a, BANK(wBGPals1)
ldh [rSVBK], a
ld hl, wBGPals1
ld bc, 8 palettes
xor a
call ByteFill
pop af
ldh [rSVBK], a
ld a, %11111111
ld [wBGP], a
call DmgToCgbBGPals
call DelayFrame
xor a
ldh [hLCDCPointer], a
ldh [hLYOverrideStart], a
ldh [hLYOverrideEnd], a
ldh [hSCY], a
ld a, $1 ; unnecessary bankswitch?
ldh [rSVBK], a
pop af
vc_hook Stop_reducing_battle_transition_flashing
ldh [hVBlank], a
call DelayFrame
ret
.InitGFX:
ld a, [wLinkMode]
cp LINK_MOBILE
jr z, .mobile
farcall ReanchorBGMap_NoOAMUpdate
call UpdateSprites
call DelayFrame
call .NonMobile_LoadPokeballTiles
call BattleStart_CopyTilemapAtOnce
jr .resume
.mobile
call LoadTrainerBattlePokeballTiles
.resume
ld a, SCREEN_HEIGHT_PX
ldh [hWY], a
call DelayFrame
xor a
ldh [hBGMapMode], a
ld hl, wJumptableIndex
xor a
ld [hli], a
ld [hli], a
ld [hl], a
call WipeLYOverrides
ret
.NonMobile_LoadPokeballTiles:
call LoadTrainerBattlePokeballTiles
hlbgcoord 0, 0
call ConvertTrainerBattlePokeballTilesTo2bpp
ret
LoadTrainerBattlePokeballTiles:
; Load the tiles used in the Pokeball Graphic that fills the screen
; at the start of every Trainer battle.
ld de, TrainerBattlePokeballTiles
ld hl, vTiles0 tile BATTLETRANSITION_SQUARE
ld b, BANK(TrainerBattlePokeballTiles)
ld c, 2
call Request2bpp
ldh a, [rVBK]
push af
ld a, $1
ldh [rVBK], a
ld de, TrainerBattlePokeballTiles
ld hl, vTiles3 tile BATTLETRANSITION_SQUARE
ld b, BANK(TrainerBattlePokeballTiles)
ld c, 2
call Request2bpp
pop af
ldh [rVBK], a
ret
ConvertTrainerBattlePokeballTilesTo2bpp:
ldh a, [rSVBK]
push af
ld a, BANK(wDecompressScratch)
ldh [rSVBK], a
push hl
ld hl, wDecompressScratch
ld bc, $28 tiles
.loop
ld [hl], -1
inc hl
dec bc
ld a, c
or b
jr nz, .loop
pop hl
ld de, wDecompressScratch
ld b, BANK(@)
ld c, $28
call Request2bpp
pop af
ldh [rSVBK], a
ret
TrainerBattlePokeballTiles:
INCBIN "gfx/overworld/trainer_battle_pokeball_tiles.2bpp"
BattleTransitionJumptable:
jumptable .Jumptable, wJumptableIndex
.Jumptable:
dw StartTrainerBattle_DetermineWhichAnimation ; 00
; BATTLETRANSITION_CAVE
dw StartTrainerBattle_LoadPokeBallGraphics ; 01
dw StartTrainerBattle_SetUpBGMap ; 02
dw StartTrainerBattle_Flash ; 03
dw StartTrainerBattle_Flash ; 04
dw StartTrainerBattle_Flash ; 05
dw StartTrainerBattle_NextScene ; 06
dw StartTrainerBattle_SetUpForWavyOutro ; 07
dw StartTrainerBattle_SineWave ; 08
; BATTLETRANSITION_CAVE_STRONGER
dw StartTrainerBattle_LoadPokeBallGraphics ; 09
dw StartTrainerBattle_SetUpBGMap ; 0a
dw StartTrainerBattle_Flash ; 0b
dw StartTrainerBattle_Flash ; 0c
dw StartTrainerBattle_Flash ; 0d
dw StartTrainerBattle_NextScene ; 0e
; There is no setup for this one
dw StartTrainerBattle_ZoomToBlack ; 0f
; BATTLETRANSITION_NO_CAVE
dw StartTrainerBattle_LoadPokeBallGraphics ; 10
dw StartTrainerBattle_SetUpBGMap ; 11
dw StartTrainerBattle_Flash ; 12
dw StartTrainerBattle_Flash ; 13
dw StartTrainerBattle_Flash ; 14
dw StartTrainerBattle_NextScene ; 15
dw StartTrainerBattle_SetUpForSpinOutro ; 16
dw StartTrainerBattle_SpinToBlack ; 17
; BATTLETRANSITION_NO_CAVE_STRONGER
dw StartTrainerBattle_LoadPokeBallGraphics ; 18
dw StartTrainerBattle_SetUpBGMap ; 19
dw StartTrainerBattle_Flash ; 1a
dw StartTrainerBattle_Flash ; 1b
dw StartTrainerBattle_Flash ; 1c
dw StartTrainerBattle_NextScene ; 1d
dw StartTrainerBattle_SetUpForRandomScatterOutro ; 1e
dw StartTrainerBattle_SpeckleToBlack ; 1f
; BATTLETRANSITION_FINISH
dw StartTrainerBattle_Finish ; 20
; transition animations
const_def
const TRANS_CAVE
const TRANS_CAVE_STRONGER
const TRANS_NO_CAVE
const TRANS_NO_CAVE_STRONGER
; transition animation bits
DEF TRANS_STRONGER_F EQU 0 ; bit set in TRANS_CAVE_STRONGER and TRANS_NO_CAVE_STRONGER
DEF TRANS_NO_CAVE_F EQU 1 ; bit set in TRANS_NO_CAVE and TRANS_NO_CAVE_STRONGER
StartTrainerBattle_DetermineWhichAnimation:
; The screen flashes a different number of times depending on the level of
; your lead Pokemon relative to the opponent's.
; BUG: Battle transitions fail to account for enemy's level (see docs/bugs_and_glitches.md)
ld de, 0
ld a, [wBattleMonLevel]
add 3
ld hl, wEnemyMonLevel
cp [hl]
jr nc, .not_stronger
set TRANS_STRONGER_F, e
.not_stronger
ld a, [wEnvironment]
cp CAVE
jr z, .cave
cp ENVIRONMENT_5
jr z, .cave
cp DUNGEON
jr z, .cave
set TRANS_NO_CAVE_F, e
.cave
ld hl, .StartingPoints
add hl, de
ld a, [hl]
ld [wJumptableIndex], a
ret
.StartingPoints:
; entries correspond to TRANS_* constants
db BATTLETRANSITION_CAVE
db BATTLETRANSITION_CAVE_STRONGER
db BATTLETRANSITION_NO_CAVE
db BATTLETRANSITION_NO_CAVE_STRONGER
StartTrainerBattle_Finish:
call ClearSprites
ld a, BATTLETRANSITION_END
ld [wJumptableIndex], a
ret
StartTrainerBattle_NextScene:
ld hl, wJumptableIndex
inc [hl]
ret
StartTrainerBattle_SetUpBGMap:
call StartTrainerBattle_NextScene
xor a
ld [wBattleTransitionCounter], a
ldh [hBGMapMode], a
ret
StartTrainerBattle_Flash:
call .DoFlashAnimation
ret nc
call StartTrainerBattle_NextScene
ret
.DoFlashAnimation:
ld a, [wTimeOfDayPalset]
cp DARKNESS_PALSET
jr z, .done
ld hl, wBattleTransitionCounter
ld a, [hl]
inc [hl]
srl a
ld e, a
ld d, 0
ld hl, .pals
add hl, de
ld a, [hl]
cp %00000001
jr z, .done
ld [wBGP], a
call DmgToCgbBGPals
and a
ret
.done
xor a
ld [wBattleTransitionCounter], a
scf
ret
.pals:
dc 3, 3, 2, 1
dc 3, 3, 3, 2
dc 3, 3, 3, 3
dc 3, 3, 3, 2
dc 3, 3, 2, 1
dc 3, 2, 1, 0
dc 2, 1, 0, 0
dc 1, 0, 0, 0
dc 0, 0, 0, 0
dc 1, 0, 0, 0
dc 2, 1, 0, 0
dc 3, 2, 1, 0
dc 0, 0, 0, 1
StartTrainerBattle_SetUpForWavyOutro:
vc_hook Stop_reducing_battle_transition_flashing_WavyOutro
farcall RespawnPlayerAndOpponent
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
call StartTrainerBattle_NextScene
ld a, LOW(rSCX)
ldh [hLCDCPointer], a
xor a
ldh [hLYOverrideStart], a
ld a, $90
ldh [hLYOverrideEnd], a
xor a
ld [wBattleTransitionCounter], a
ld [wBattleTransitionSineWaveOffset], a
ret
StartTrainerBattle_SineWave:
ld a, [wBattleTransitionCounter]
cp $60
jr nc, .end
call .DoSineWave
ret
.end
ld a, BATTLETRANSITION_FINISH
ld [wJumptableIndex], a
ret
.DoSineWave:
ld hl, wBattleTransitionSineWaveOffset
ld a, [hl]
inc [hl]
ld hl, wBattleTransitionCounter
ld d, [hl]
add [hl]
ld [hl], a
ld a, wLYOverridesEnd - wLYOverrides
ld bc, wLYOverrides
ld e, 0
.loop
push af
push de
ld a, e
call StartTrainerBattle_DrawSineWave
ld [bc], a
inc bc
pop de
ld a, e
add 2
ld e, a
pop af
dec a
jr nz, .loop
ret
StartTrainerBattle_SetUpForSpinOutro:
vc_hook Stop_reducing_battle_transition_flashing_SpinOutro
farcall RespawnPlayerAndOpponent
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
call StartTrainerBattle_NextScene
xor a
ld [wBattleTransitionCounter], a
ret
StartTrainerBattle_SpinToBlack:
xor a
ldh [hBGMapMode], a
ld a, [wBattleTransitionCounter]
ld e, a
ld d, 0
ld hl, .spin_quadrants
rept 5
add hl, de
endr
ld a, [hli]
cp -1
jr z, .end
ld [wBattleTransitionSineWaveOffset], a
call .load
ld a, 1
ldh [hBGMapMode], a
call DelayFrame
call DelayFrame
ld hl, wBattleTransitionCounter
inc [hl]
ret
.end
ld a, 1
ldh [hBGMapMode], a
call DelayFrame
call DelayFrame
call DelayFrame
xor a
ldh [hBGMapMode], a
ld a, BATTLETRANSITION_FINISH
ld [wJumptableIndex], a
ret
; quadrants
const_def
const UPPER_LEFT
const UPPER_RIGHT
const LOWER_LEFT
const LOWER_RIGHT
; quadrant bits
DEF RIGHT_QUADRANT_F EQU 0 ; bit set in UPPER_RIGHT and LOWER_RIGHT
DEF LOWER_QUADRANT_F EQU 1 ; bit set in LOWER_LEFT and LOWER_RIGHT
.spin_quadrants:
MACRO spin_quadrant
db \1
dw \2
dwcoord \3, \4
ENDM
spin_quadrant UPPER_LEFT, .wedge1, 1, 6
spin_quadrant UPPER_LEFT, .wedge2, 0, 3
spin_quadrant UPPER_LEFT, .wedge3, 1, 0
spin_quadrant UPPER_LEFT, .wedge4, 5, 0
spin_quadrant UPPER_LEFT, .wedge5, 9, 0
spin_quadrant UPPER_RIGHT, .wedge5, 10, 0
spin_quadrant UPPER_RIGHT, .wedge4, 14, 0
spin_quadrant UPPER_RIGHT, .wedge3, 18, 0
spin_quadrant UPPER_RIGHT, .wedge2, 19, 3
spin_quadrant UPPER_RIGHT, .wedge1, 18, 6
spin_quadrant LOWER_RIGHT, .wedge1, 18, 11
spin_quadrant LOWER_RIGHT, .wedge2, 19, 14
spin_quadrant LOWER_RIGHT, .wedge3, 18, 17
spin_quadrant LOWER_RIGHT, .wedge4, 14, 17
spin_quadrant LOWER_RIGHT, .wedge5, 10, 17
spin_quadrant LOWER_LEFT, .wedge5, 9, 17
spin_quadrant LOWER_LEFT, .wedge4, 5, 17
spin_quadrant LOWER_LEFT, .wedge3, 1, 17
spin_quadrant LOWER_LEFT, .wedge2, 0, 14
spin_quadrant LOWER_LEFT, .wedge1, 1, 11
db -1
.load:
ld a, [hli]
ld e, a
ld a, [hli]
ld d, a
ld a, [hli]
ld h, [hl]
ld l, a
.loop
push hl
ld a, [de]
ld c, a
inc de
.loop1
ld [hl], BATTLETRANSITION_BLACK
ld a, [wBattleTransitionSineWaveOffset]
bit RIGHT_QUADRANT_F, a
jr z, .leftside
inc hl
jr .okay1
.leftside
dec hl
.okay1
dec c
jr nz, .loop1
pop hl
ld a, [wBattleTransitionSineWaveOffset]
bit LOWER_QUADRANT_F, a
ld bc, SCREEN_WIDTH
jr z, .upper
ld bc, -SCREEN_WIDTH
.upper
add hl, bc
ld a, [de]
inc de
cp -1
ret z
and a
jr z, .loop
ld c, a
.loop2
ld a, [wBattleTransitionSineWaveOffset]
bit RIGHT_QUADRANT_F, a
jr z, .leftside2
dec hl
jr .okay2
.leftside2
inc hl
.okay2
dec c
jr nz, .loop2
jr .loop
.wedge1: db 2, 3, 5, 4, 9, -1
.wedge2: db 1, 1, 2, 2, 4, 2, 4, 2, 3, -1
.wedge3: db 2, 1, 3, 1, 4, 1, 4, 1, 4, 1, 3, 1, 2, 1, 1, 1, 1, -1
.wedge4: db 4, 1, 4, 0, 3, 1, 3, 0, 2, 1, 2, 0, 1, -1
.wedge5: db 4, 0, 3, 0, 3, 0, 2, 0, 2, 0, 1, 0, 1, 0, 1, -1
StartTrainerBattle_SetUpForRandomScatterOutro:
vc_hook Stop_reducing_battle_transition_flashing_ScatterOutro
farcall RespawnPlayerAndOpponent
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
call StartTrainerBattle_NextScene
ld a, $10
ld [wBattleTransitionCounter], a
ld a, 1
ldh [hBGMapMode], a
ret
StartTrainerBattle_SpeckleToBlack:
ld hl, wBattleTransitionCounter
ld a, [hl]
and a
jr z, .done
dec [hl]
ld c, $c
.loop
push bc
call .BlackOutRandomTile
pop bc
dec c
jr nz, .loop
ret
.done
ld a, $1
ldh [hBGMapMode], a
call DelayFrame
call DelayFrame
call DelayFrame
xor a
ldh [hBGMapMode], a
ld a, BATTLETRANSITION_FINISH
ld [wJumptableIndex], a
ret
.BlackOutRandomTile:
.y_loop
call Random
cp SCREEN_HEIGHT
jr nc, .y_loop
ld b, a
.x_loop
call Random
cp SCREEN_WIDTH
jr nc, .x_loop
ld c, a
hlcoord 0, -1
ld de, SCREEN_WIDTH
inc b
.row_loop
add hl, de
dec b
jr nz, .row_loop
add hl, bc
; If the tile has already been blacked out,
; sample a new tile
ld a, [hl]
cp BATTLETRANSITION_BLACK
jr z, .y_loop
ld [hl], BATTLETRANSITION_BLACK
ret
StartTrainerBattle_LoadPokeBallGraphics:
ld a, [wOtherTrainerClass]
and a
jp z, .nextscene ; don't need to be here if wild
xor a
ldh [hBGMapMode], a
hlcoord 0, 0, wAttrmap
ld bc, SCREEN_HEIGHT * SCREEN_WIDTH
inc b
inc c
jr .enter_loop_midway
.pal_loop
; set all pals to 7
ld a, [hl]
or PAL_BG_TEXT
ld [hli], a
.enter_loop_midway
dec c
jr nz, .pal_loop
dec b
jr nz, .pal_loop
call .loadpokeballgfx
hlcoord 2, 1
ld b, SCREEN_WIDTH - 4
.tile_loop
push hl
ld c, 2
.row_loop
push hl
ld a, [de]
inc de
.col_loop
; Loading is done bit by bit
and a
jr z, .done
sla a
jr nc, .no_load
ld [hl], BATTLETRANSITION_SQUARE
.no_load
inc hl
jr .col_loop
.done
pop hl
push bc
ld bc, (SCREEN_WIDTH - 4) / 2
add hl, bc
pop bc
dec c
jr nz, .row_loop
pop hl
push bc
ld bc, SCREEN_WIDTH
add hl, bc
pop bc
dec b
jr nz, .tile_loop
ldh a, [hCGB]
and a
jr nz, .cgb
ld a, 1
ldh [hBGMapMode], a
call DelayFrame
call DelayFrame
jr .nextscene
.cgb
ld hl, .pals
ld a, [wTimeOfDayPal]
maskbits NUM_DAYTIMES
cp DARKNESS_F
jr nz, .not_dark
ld hl, .darkpals
.not_dark
ldh a, [rSVBK]
push af
ld a, BANK(wBGPals1)
ldh [rSVBK], a
call .copypals
push hl
ld de, wBGPals1 palette PAL_BG_TEXT
ld bc, 1 palettes
call CopyBytes
pop hl
ld de, wBGPals2 palette PAL_BG_TEXT
ld bc, 1 palettes
call CopyBytes
pop af
ldh [rSVBK], a
ld a, TRUE
ldh [hCGBPalUpdate], a
call DelayFrame
call BattleStart_CopyTilemapAtOnce
.nextscene
call StartTrainerBattle_NextScene
ret
.copypals
ld de, wBGPals1 palette PAL_BG_TEXT
call .copy
ld de, wBGPals2 palette PAL_BG_TEXT
call .copy
ld de, wOBPals1 palette PAL_OW_TREE
call .copy
ld de, wOBPals2 palette PAL_OW_TREE
call .copy
ld de, wOBPals1 palette PAL_OW_ROCK
call .copy
ld de, wOBPals2 palette PAL_OW_ROCK
.copy
push hl
ld bc, 1 palettes
call CopyBytes
pop hl
ret
.pals:
INCLUDE "gfx/overworld/trainer_battle.pal"
.darkpals:
INCLUDE "gfx/overworld/trainer_battle_dark.pal"
.loadpokeballgfx:
ld a, [wOtherTrainerClass]
ld de, PokeBallTransition
ret
PokeBallTransition:
; 16x16 overlay of a Poke Ball
pusho
opt b.X ; . = 0, X = 1
bigdw %......XXXX......
bigdw %....XXXXXXXX....
bigdw %..XXXX....XXXX..
bigdw %..XX........XX..
bigdw %.XX..........XX.
bigdw %.XX...XXXX...XX.
bigdw %XX...XX..XX...XX
bigdw %XXXXXX....XXXXXX
bigdw %XXXXXX....XXXXXX
bigdw %XX...XX..XX...XX
bigdw %.XX...XXXX...XX.
bigdw %.XX..........XX.
bigdw %..XX........XX..
bigdw %..XXXX....XXXX..
bigdw %....XXXXXXXX....
bigdw %......XXXX......
popo
WipeLYOverrides:
ldh a, [rSVBK]
push af
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
ld hl, wLYOverrides
call .wipe
ld hl, wLYOverridesBackup
call .wipe
pop af
ldh [rSVBK], a
ret
.wipe
xor a
ld c, SCREEN_HEIGHT_PX
.loop
ld [hli], a
dec c
jr nz, .loop
ret
StartTrainerBattle_DrawSineWave:
calc_sine_wave
StartTrainerBattle_ZoomToBlack:
vc_hook Stop_reducing_battle_transition_flashing_ZoomToBlack
farcall RespawnPlayerAndOpponent
ld de, .boxes
.loop
ld a, [de]
cp -1
jr z, .done
inc de
ld c, a
ld a, [de]
inc de
ld b, a
ld a, [de]
inc de
ld l, a
ld a, [de]
inc de
ld h, a
xor a
ldh [hBGMapMode], a
call .Copy
call WaitBGMap
jr .loop
.done
ld a, BATTLETRANSITION_FINISH
ld [wJumptableIndex], a
ret
.boxes
MACRO zoombox
; width, height, start y, start x
db \1, \2
dwcoord \3, \4
ENDM
zoombox 4, 2, 8, 8
zoombox 6, 4, 7, 7
zoombox 8, 6, 6, 6
zoombox 10, 8, 5, 5
zoombox 12, 10, 4, 4
zoombox 14, 12, 3, 3
zoombox 16, 14, 2, 2
zoombox 18, 16, 1, 1
zoombox 20, 18, 0, 0
db -1
.Copy:
ld a, BATTLETRANSITION_BLACK
.row
push bc
push hl
.col
ld [hli], a
dec c
jr nz, .col
pop hl
ld bc, SCREEN_WIDTH
add hl, bc
pop bc
dec b
jr nz, .row
ret
UnusedWaitBGMapOnce: ; unreferenced
ld a, 1
ldh [hBGMapMode], a ; redundant
call WaitBGMap
xor a
ldh [hBGMapMode], a
ret

View file

@ -0,0 +1,3 @@
BattleStart_CopyTilemapAtOnce:
call CGBOnly_CopyTilemapAtOnce
ret

View file

@ -0,0 +1,47 @@
CheckBattleScene:
; Return carry if battle scene is turned off.
ld a, BANK(wLinkMode)
ld hl, wLinkMode
call GetFarWRAMByte
cp LINK_MOBILE
jr z, .mobile
ld a, [wOptions]
bit BATTLE_SCENE, a
jr nz, .off
and a
ret
.mobile
ld a, [wcd2f]
and a
jr nz, .from_wram
ld a, BANK(s4_a60c) ; MBC30 bank used by JP Crystal; inaccessible by MBC3
call OpenSRAM
ld a, [s4_a60c]
ld c, a
call CloseSRAM
ld a, c
bit 0, c
jr z, .off
and a
ret
.from_wram
ld a, BANK(w5_dc00)
ld hl, w5_dc00
call GetFarWRAMByte
bit 0, a
jr z, .off
and a
ret
.off
scf
ret

View file

@ -0,0 +1,57 @@
ConsumeHeldItem:
push hl
push de
push bc
ldh a, [hBattleTurn]
and a
ld hl, wOTPartyMon1Item
ld de, wEnemyMonItem
ld a, [wCurOTMon]
jr z, .theirturn
ld hl, wPartyMon1Item
ld de, wBattleMonItem
ld a, [wCurBattleMon]
.theirturn
push hl
push af
ld a, [de]
ld b, a
farcall GetItemHeldEffect
ld hl, ConsumableEffects
.loop
ld a, [hli]
cp b
jr z, .ok
inc a
jr nz, .loop
pop af
pop hl
pop bc
pop de
pop hl
ret
.ok
xor a
ld [de], a
pop af
pop hl
call GetPartyLocation
ldh a, [hBattleTurn]
and a
jr nz, .ourturn
ld a, [wBattleMode]
dec a
jr z, .done
.ourturn
ld [hl], NO_ITEM
.done
pop bc
pop de
pop hl
ret
INCLUDE "data/battle/held_consumables.asm"

9236
engine/battle/core.asm Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,108 @@
HiddenPowerDamage:
; Override Hidden Power's type and power based on the user's DVs.
ld hl, wBattleMonDVs
ldh a, [hBattleTurn]
and a
jr z, .got_dvs
ld hl, wEnemyMonDVs
.got_dvs
; Power:
; Take the top bit from each stat
; Attack
ld a, [hl]
swap a
and %1000
; Defense
ld b, a
ld a, [hli]
and %1000
srl a
or b
; Speed
ld b, a
ld a, [hl]
swap a
and %1000
srl a
srl a
or b
; Special
ld b, a
ld a, [hl]
and %1000
srl a
srl a
srl a
or b
; Multiply by 5
ld b, a
add a
add a
add b
; Add Special & 3
ld b, a
ld a, [hld]
and %0011
add b
; Divide by 2 and add 30 + 1
srl a
add 30
inc a
ld d, a
; Type:
; Def & 3
ld a, [hl]
and %0011
ld b, a
; + (Atk & 3) << 2
ld a, [hl]
and %0011 << 4
swap a
add a
add a
or b
; Skip Normal
inc a
; Skip Bird
cp BIRD
jr c, .done
inc a
; Skip unused types
cp UNUSED_TYPES
jr c, .done
add UNUSED_TYPES_END - UNUSED_TYPES
.done
; Overwrite the current move type.
push af
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVarAddr
pop af
ld [hl], a
; Get the rest of the damage formula variables
; based on the new type, but keep base power.
ld a, d
push af
farcall BattleCommand_DamageStats ; damagestats
pop af
ld d, a
ret

View file

@ -0,0 +1,162 @@
DetermineLinkBattleResult:
farcall UpdateEnemyMonInParty
ld hl, wPartyMon1HP
call .CountMonsRemaining
push bc
ld hl, wOTPartyMon1HP
call .CountMonsRemaining
ld a, c
pop bc
cp c
jr z, .even_number_of_mons_remaining
jr c, .defeat
jr .victory
.even_number_of_mons_remaining
call .BothSides_CheckNumberMonsAtFullHealth
jr z, .drawn
ld a, e
cp $1
jr z, .victory
cp $2
jr z, .defeat
ld hl, wPartyMon1HP
call .CalcPercentHPRemaining
push de
ld hl, wOTPartyMon1HP
call .CalcPercentHPRemaining
pop hl
ld a, d
cp h
jr c, .victory
jr z, .compare_lo
jr .defeat
.compare_lo
ld a, e
cp l
jr z, .drawn
jr nc, .defeat
.victory
ld a, [wBattleResult]
and $f0
ld [wBattleResult], a ; WIN
ret
.defeat
ld a, [wBattleResult]
and $f0
add LOSE
ld [wBattleResult], a
ret
.drawn
ld a, [wBattleResult]
and $f0
add DRAW
ld [wBattleResult], a
ret
.CountMonsRemaining:
ld c, 0
ld b, 3
ld de, PARTYMON_STRUCT_LENGTH - 1
.loop
ld a, [hli]
or [hl]
jr nz, .not_fainted
inc c
.not_fainted
add hl, de
dec b
jr nz, .loop
ret
.CalcPercentHPRemaining:
ld de, 0
ld c, $3
.loop2
ld a, [hli]
or [hl]
jr z, .next
dec hl
xor a
ldh [hDividend + 0], a
ld a, [hli]
ldh [hDividend + 1], a
ld a, [hli]
ldh [hDividend + 2], a
xor a
ldh [hDividend + 3], a
ld a, [hli]
ld b, a
ld a, [hld]
srl b
rr a
srl b
rr a
ldh [hDivisor], a
ld b, $4
call Divide
ldh a, [hQuotient + 3]
add e
ld e, a
ldh a, [hQuotient + 2]
adc d
ld d, a
dec hl
.next
push de
ld de, $2f
add hl, de
pop de
dec c
jr nz, .loop2
ret
.BothSides_CheckNumberMonsAtFullHealth:
ld hl, wPartyMon1HP
call .CheckFaintedOrFullHealth
jr nz, .finish ; we have a pokemon that's neither fainted nor at full health
ld hl, wOTPartyMon1HP
call .CheckFaintedOrFullHealth
ld e, $1 ; victory
ret
.finish
ld hl, wOTPartyMon1HP
call .CheckFaintedOrFullHealth
ld e, $0 ; drawn
ret nz ; we both have pokemon that are neither fainted nor at full health
ld e, $2 ; defeat
ld a, $1 ; not drawn
and a
ret
.CheckFaintedOrFullHealth:
ld d, 3
.loop3
ld a, [hli]
ld b, a
ld a, [hli]
ld c, a
or b
jr z, .fainted_or_full_health
ld a, [hli]
cp b
ret nz
ld a, [hld]
cp c
ret nz
.fainted_or_full_health
push de
ld de, PARTYMON_STRUCT_LENGTH - 2
add hl, de
pop de
dec d
jr nz, .loop3
ret

100
engine/battle/menu.asm Normal file
View file

@ -0,0 +1,100 @@
LoadBattleMenu:
ld hl, BattleMenuHeader
call LoadMenuHeader
ld a, [wBattleMenuCursorPosition]
ld [wMenuCursorPosition], a
call InterpretBattleMenu
ld a, [wMenuCursorPosition]
ld [wBattleMenuCursorPosition], a
call ExitMenu
ret
SafariBattleMenu: ; unreferenced
ld hl, SafariBattleMenuHeader
call LoadMenuHeader
jr CommonBattleMenu
ContestBattleMenu:
ld hl, ContestBattleMenuHeader
call LoadMenuHeader
; fallthrough
CommonBattleMenu:
ld a, [wBattleMenuCursorPosition]
ld [wMenuCursorPosition], a
call _2DMenu
ld a, [wMenuCursorPosition]
ld [wBattleMenuCursorPosition], a
call ExitMenu
ret
BattleMenuHeader:
db MENU_BACKUP_TILES ; flags
menu_coords 8, 12, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1
dw .MenuData
db 1 ; default option
.MenuData:
db STATICMENU_CURSOR | STATICMENU_DISABLE_B ; flags
dn 2, 2 ; rows, columns
db 6 ; spacing
dba .Text
dbw BANK(@), NULL
.Text:
db "FIGHT@"
db "<PKMN>@"
db "PACK@"
db "RUN@"
SafariBattleMenuHeader:
db MENU_BACKUP_TILES ; flags
menu_coords 0, 12, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1
dw .MenuData
db 1 ; default option
.MenuData:
db STATICMENU_CURSOR | STATICMENU_DISABLE_B ; flags
dn 2, 2 ; rows, columns
db 11 ; spacing
dba .Text
dba .PrintSafariBallsRemaining
.Text:
db "サファりボール×  @" ; "SAFARI BALL× @"
db "エサをなげる@" ; "THROW BAIT"
db "いしをなげる@" ; "THROW ROCK"
db "にげる@" ; "RUN"
.PrintSafariBallsRemaining:
hlcoord 17, 13
ld de, wSafariBallsRemaining
lb bc, PRINTNUM_LEADINGZEROS | 1, 2
call PrintNum
ret
ContestBattleMenuHeader:
db MENU_BACKUP_TILES ; flags
menu_coords 2, 12, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1
dw .MenuData
db 1 ; default option
.MenuData:
db STATICMENU_CURSOR | STATICMENU_DISABLE_B ; flags
dn 2, 2 ; rows, columns
db 12 ; spacing
dba .Text
dba .PrintParkBallsRemaining
.Text:
db "FIGHT@"
db "<PKMN>@"
db "PARKBALL× @"
db "RUN@"
.PrintParkBallsRemaining:
hlcoord 13, 16
ld de, wParkBallsRemaining
lb bc, PRINTNUM_LEADINGZEROS | 1, 2
call PrintNum
ret

224
engine/battle/misc.asm Normal file
View file

@ -0,0 +1,224 @@
_DisappearUser:
xor a
ldh [hBGMapMode], a
ldh a, [hBattleTurn]
and a
jr z, .player
call GetEnemyFrontpicCoords
jr .okay
.player
call GetPlayerBackpicCoords
.okay
call ClearBox
jr FinishAppearDisappearUser
_AppearUserRaiseSub:
farcall BattleCommand_RaiseSubNoAnim
jr AppearUser
_AppearUserLowerSub:
farcall BattleCommand_LowerSubNoAnim
AppearUser:
xor a
ldh [hBGMapMode], a
ldh a, [hBattleTurn]
and a
jr z, .player
call GetEnemyFrontpicCoords
xor a
jr .okay
.player
call GetPlayerBackpicCoords
ld a, $31
.okay
ldh [hGraphicStartTile], a
predef PlaceGraphic
FinishAppearDisappearUser:
ld a, $1
ldh [hBGMapMode], a
ret
GetEnemyFrontpicCoords:
hlcoord 12, 0
lb bc, 7, 7
ret
GetPlayerBackpicCoords:
hlcoord 2, 6
lb bc, 6, 6
ret
DoWeatherModifiers:
ld de, WeatherTypeModifiers
ld a, [wBattleWeather]
ld b, a
ld a, [wCurType]
ld c, a
.CheckWeatherType:
ld a, [de]
inc de
cp -1
jr z, .done_weather_types
cp b
jr nz, .NextWeatherType
ld a, [de]
cp c
jr z, .ApplyModifier
.NextWeatherType:
inc de
inc de
jr .CheckWeatherType
.done_weather_types
ld de, WeatherMoveModifiers
ld a, BATTLE_VARS_MOVE_EFFECT
call GetBattleVar
ld c, a
.CheckWeatherMove:
ld a, [de]
inc de
cp -1
jr z, .done
cp b
jr nz, .NextWeatherMove
ld a, [de]
cp c
jr z, .ApplyModifier
.NextWeatherMove:
inc de
inc de
jr .CheckWeatherMove
.ApplyModifier:
xor a
ldh [hMultiplicand + 0], a
ld hl, wCurDamage
ld a, [hli]
ldh [hMultiplicand + 1], a
ld a, [hl]
ldh [hMultiplicand + 2], a
inc de
ld a, [de]
ldh [hMultiplier], a
call Multiply
ld a, 10
ldh [hDivisor], a
ld b, 4
call Divide
ldh a, [hQuotient + 1]
and a
ld bc, -1
jr nz, .Update
ldh a, [hQuotient + 2]
ld b, a
ldh a, [hQuotient + 3]
ld c, a
or b
jr nz, .Update
ld bc, 1
.Update:
ld a, b
ld [wCurDamage], a
ld a, c
ld [wCurDamage + 1], a
.done
ret
INCLUDE "data/battle/weather_modifiers.asm"
DoBadgeTypeBoosts:
ld a, [wLinkMode]
and a
ret nz
ld a, [wInBattleTowerBattle]
and a
ret nz
ldh a, [hBattleTurn]
and a
ret nz
push de
push bc
ld hl, BadgeTypeBoosts
ld a, [wKantoBadges]
ld b, a
ld a, [wJohtoBadges]
ld c, a
.CheckBadge:
ld a, [hl]
cp -1
jr z, .done
srl b
rr c
jr nc, .NextBadge
ld a, [wCurType]
cp [hl]
jr z, .ApplyBoost
.NextBadge:
inc hl
jr .CheckBadge
.ApplyBoost:
ld a, [wCurDamage]
ld h, a
ld d, a
ld a, [wCurDamage + 1]
ld l, a
ld e, a
srl d
rr e
srl d
rr e
srl d
rr e
ld a, e
or d
jr nz, .done_min
ld e, 1
.done_min
add hl, de
jr nc, .Update
ld hl, $ffff
.Update:
ld a, h
ld [wCurDamage], a
ld a, l
ld [wCurDamage + 1], a
.done
pop bc
pop de
ret
INCLUDE "data/types/badge_type_boosts.asm"

View file

@ -0,0 +1,75 @@
BattleCommand_Attract:
ld a, [wAttackMissed]
and a
jr nz, .failed
call CheckOppositeGender
jr c, .failed
call CheckHiddenOpponent
jr nz, .failed
ld a, BATTLE_VARS_SUBSTATUS1_OPP
call GetBattleVarAddr
bit SUBSTATUS_IN_LOVE, [hl]
jr nz, .failed
set SUBSTATUS_IN_LOVE, [hl]
call AnimateCurrentMove
; 'fell in love!'
ld hl, FellInLoveText
jp StdBattleTextbox
.failed
jp FailMove
CheckOppositeGender:
ld a, MON_SPECIES
call BattlePartyAttr
ld a, [hl]
ld [wCurPartySpecies], a
ld a, [wCurBattleMon]
ld [wCurPartyMon], a
xor a
ld [wMonType], a
farcall GetGender
jr c, .genderless_samegender
ld b, 1
jr nz, .got_gender
dec b
.got_gender
push bc
ld a, [wTempEnemyMonSpecies]
ld [wCurPartySpecies], a
ld hl, wEnemyMonDVs
ld a, [wEnemySubStatus5]
bit SUBSTATUS_TRANSFORMED, a
jr z, .not_transformed
ld hl, wEnemyBackupDVs
.not_transformed
ld a, [hli]
ld [wTempMonDVs], a
ld a, [hl]
ld [wTempMonDVs + 1], a
ld a, TEMPMON
ld [wMonType], a
farcall GetGender
pop bc
jr c, .genderless_samegender
ld a, 1
jr nz, .got_enemy_gender
dec a
.got_enemy_gender
xor b
jr z, .genderless_samegender
and a
ret
.genderless_samegender
scf
ret

View file

@ -0,0 +1,215 @@
BattleCommand_BatonPass:
ldh a, [hBattleTurn]
and a
jp nz, .Enemy
; Need something to switch to
call CheckAnyOtherAlivePartyMons
jp z, FailedBatonPass
call UpdateBattleMonInParty
call AnimateCurrentMove
ld c, 50
call DelayFrames
; Transition into switchmon menu
call LoadStandardMenuHeader
farcall SetUpBattlePartyMenu
farcall ForcePickSwitchMonInBattle
; Return to battle scene
call ClearPalettes
farcall _LoadBattleFontsHPBar
call CloseWindow
call ClearSprites
hlcoord 1, 0
lb bc, 4, 10
call ClearBox
ld b, SCGB_BATTLE_COLORS
call GetSGBLayout
call SetPalettes
call BatonPass_LinkPlayerSwitch
; Mobile link battles handle entrances differently
farcall CheckMobileBattleError
jp c, EndMoveEffect
ld hl, PassedBattleMonEntrance
call CallBattleCore
call ResetBatonPassStatus
ret
.Enemy:
; Wildmons don't have anything to switch to
ld a, [wBattleMode]
dec a ; WILDMON
jp z, FailedBatonPass
call CheckAnyOtherAliveEnemyMons
jp z, FailedBatonPass
call UpdateEnemyMonInParty
call AnimateCurrentMove
call BatonPass_LinkEnemySwitch
; Mobile link battles handle entrances differently
farcall CheckMobileBattleError
jp c, EndMoveEffect
; Passed enemy PartyMon entrance
xor a
ld [wEnemySwitchMonIndex], a
ld hl, EnemySwitch_SetMode
call CallBattleCore
ld hl, ResetBattleParticipants
call CallBattleCore
ld a, TRUE
ld [wApplyStatLevelMultipliersToEnemy], a
ld hl, ApplyStatLevelMultiplierOnAllStats
call CallBattleCore
ld hl, SpikesDamage
call CallBattleCore
jr ResetBatonPassStatus
BatonPass_LinkPlayerSwitch:
ld a, [wLinkMode]
and a
ret z
ld a, BATTLEPLAYERACTION_USEITEM
ld [wBattlePlayerAction], a
call LoadStandardMenuHeader
ld hl, LinkBattleSendReceiveAction
call CallBattleCore
call CloseWindow
xor a ; BATTLEPLAYERACTION_USEMOVE
ld [wBattlePlayerAction], a
ret
BatonPass_LinkEnemySwitch:
ld a, [wLinkMode]
and a
ret z
call LoadStandardMenuHeader
ld hl, LinkBattleSendReceiveAction
call CallBattleCore
ld a, [wOTPartyCount]
add BATTLEACTION_SWITCH1
ld b, a
ld a, [wBattleAction]
cp BATTLEACTION_SWITCH1
jr c, .baton_pass
cp b
jr c, .switch
.baton_pass
ld a, [wCurOTMon]
add BATTLEACTION_SWITCH1
ld [wBattleAction], a
.switch
jp CloseWindow
FailedBatonPass:
call AnimateFailedMove
jp PrintButItFailed
ResetBatonPassStatus:
; Reset status changes that aren't passed by Baton Pass.
; Nightmare isn't passed.
ld a, BATTLE_VARS_STATUS
call GetBattleVar
and SLP_MASK
jr nz, .ok
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVarAddr
res SUBSTATUS_NIGHTMARE, [hl]
.ok
; Disable isn't passed.
call ResetActorDisable
; Attraction isn't passed.
ld hl, wPlayerSubStatus1
res SUBSTATUS_IN_LOVE, [hl]
ld hl, wEnemySubStatus1
res SUBSTATUS_IN_LOVE, [hl]
ld hl, wPlayerSubStatus5
ld a, BATTLE_VARS_SUBSTATUS5
call GetBattleVarAddr
res SUBSTATUS_TRANSFORMED, [hl]
res SUBSTATUS_ENCORED, [hl]
; New mon hasn't used a move yet.
ld a, BATTLE_VARS_LAST_MOVE
call GetBattleVarAddr
ld [hl], 0
xor a
ld [wPlayerWrapCount], a
ld [wEnemyWrapCount], a
ret
CheckAnyOtherAlivePartyMons:
ld hl, wPartyMon1HP
ld a, [wPartyCount]
ld d, a
ld a, [wCurBattleMon]
ld e, a
jr CheckAnyOtherAliveMons
CheckAnyOtherAliveEnemyMons:
ld hl, wOTPartyMon1HP
ld a, [wOTPartyCount]
ld d, a
ld a, [wCurOTMon]
ld e, a
; fallthrough
CheckAnyOtherAliveMons:
; Check for nonzero HP starting from partymon
; HP at hl for d partymons, besides current mon e.
; Return nz if any are alive.
xor a
ld b, a
ld c, a
.loop
ld a, c
cp d
jr z, .done
cp e
jr z, .next
ld a, [hli]
or b
ld b, a
ld a, [hld]
or b
ld b, a
.next
push bc
ld bc, PARTYMON_STRUCT_LENGTH
add hl, bc
pop bc
inc c
jr .loop
.done
ld a, b
and a
ret

View file

@ -0,0 +1,220 @@
BattleCommand_BeatUp:
call ResetDamage
ldh a, [hBattleTurn]
and a
jp nz, .enemy_beats_up
ld a, [wPlayerSubStatus3]
bit SUBSTATUS_IN_LOOP, a
jr nz, .next_mon
ld c, 20
call DelayFrames
xor a
ld [wPlayerRolloutCount], a
ld [wCurBeatUpPartyMon], a
ld [wBeatUpHitAtLeastOnce], a
jr .got_mon
.next_mon
ld a, [wPlayerRolloutCount]
ld b, a
ld a, [wPartyCount]
sub b
ld [wCurBeatUpPartyMon], a
.got_mon
; BUG: Beat Up can desynchronize link battles (see docs/bugs_and_glitches.md)
ld a, [wCurBeatUpPartyMon]
ld hl, wPartyMonNicknames
call GetNickname
ld a, MON_HP
call GetBeatupMonLocation
ld a, [hli]
or [hl]
jp z, .beatup_fail ; fainted
ld a, [wCurBeatUpPartyMon]
ld c, a
ld a, [wCurBattleMon]
cp [hl]
ld hl, wBattleMonStatus
jr z, .active_mon
ld a, MON_STATUS
call GetBeatupMonLocation
.active_mon
ld a, [hl]
and a
jp nz, .beatup_fail
ld a, $1
ld [wBeatUpHitAtLeastOnce], a
ld hl, BeatUpAttackText
call StdBattleTextbox
ld a, [wEnemyMonSpecies]
ld [wCurSpecies], a
call GetBaseData
ld a, [wBaseDefense]
ld c, a
push bc
ld a, MON_SPECIES
call GetBeatupMonLocation
ld a, [hl]
ld [wCurSpecies], a
call GetBaseData
ld a, [wBaseAttack]
pop bc
ld b, a
push bc
ld a, MON_LEVEL
call GetBeatupMonLocation
ld a, [hl]
ld e, a
pop bc
ld a, [wPlayerMoveStructPower]
ld d, a
ret
.enemy_beats_up
ld a, [wEnemySubStatus3]
bit SUBSTATUS_IN_LOOP, a
jr nz, .enemy_next_mon
xor a
ld [wEnemyRolloutCount], a
ld [wCurBeatUpPartyMon], a
ld [wBeatUpHitAtLeastOnce], a
jr .enemy_got_mon
.enemy_next_mon
ld a, [wEnemyRolloutCount]
ld b, a
ld a, [wOTPartyCount]
sub b
ld [wCurBeatUpPartyMon], a
.enemy_got_mon
ld a, [wBattleMode]
dec a
jr z, .wild
ld a, [wLinkMode]
and a
jr nz, .link_or_tower
ld a, [wInBattleTowerBattle]
and a
jr nz, .link_or_tower
ld a, [wCurBeatUpPartyMon]
ld c, a
ld b, 0
ld hl, wOTPartySpecies
add hl, bc
ld a, [hl]
ld [wNamedObjectIndex], a
call GetPokemonName
jr .got_enemy_nick
.link_or_tower
ld a, [wCurBeatUpPartyMon]
ld hl, wOTPartyMonNicknames
ld bc, NAME_LENGTH
call AddNTimes
ld de, wStringBuffer1
call CopyBytes
.got_enemy_nick
ld a, MON_HP
call GetBeatupMonLocation
ld a, [hli]
or [hl]
jp z, .beatup_fail
ld a, [wCurBeatUpPartyMon]
ld b, a
ld a, [wCurOTMon]
cp b
ld hl, wEnemyMonStatus
jr z, .active_enemy
ld a, MON_STATUS
call GetBeatupMonLocation
.active_enemy
ld a, [hl]
and a
jr nz, .beatup_fail
ld a, $1
ld [wBeatUpHitAtLeastOnce], a
jr .finish_beatup
.wild
ld a, [wEnemyMonSpecies]
ld [wNamedObjectIndex], a
call GetPokemonName
ld hl, BeatUpAttackText
call StdBattleTextbox
jp EnemyAttackDamage
.finish_beatup
ld hl, BeatUpAttackText
call StdBattleTextbox
ld a, [wBattleMonSpecies]
ld [wCurSpecies], a
call GetBaseData
ld a, [wBaseDefense]
ld c, a
push bc
ld a, MON_SPECIES
call GetBeatupMonLocation
ld a, [hl]
ld [wCurSpecies], a
call GetBaseData
ld a, [wBaseAttack]
pop bc
ld b, a
push bc
ld a, MON_LEVEL
call GetBeatupMonLocation
ld a, [hl]
ld e, a
pop bc
ld a, [wEnemyMoveStructPower]
ld d, a
ret
.beatup_fail
ld b, buildopponentrage_command
jp SkipToBattleCommand
BattleCommand_BeatUpFailText:
; BUG: Beat Up may trigger King's Rock even if it failed (see docs/bugs_and_glitches.md)
ld a, [wBeatUpHitAtLeastOnce]
and a
ret nz
jp PrintButItFailed
GetBeatupMonLocation:
push bc
ld c, a
ld b, 0
ldh a, [hBattleTurn]
and a
ld hl, wPartyMon1Species
jr z, .got_species
ld hl, wOTPartyMon1Species
.got_species
ld a, [wCurBeatUpPartyMon]
add hl, bc
call GetPartyLocation
pop bc
ret

View file

@ -0,0 +1,31 @@
BattleCommand_BellyDrum:
; BUG: Belly Drum sharply boosts Attack even with under 50% HP (see docs/bugs_and_glitches.md)
call BattleCommand_AttackUp2
ld a, [wAttackMissed]
and a
jr nz, .failed
callfar GetHalfMaxHP
callfar CheckUserHasEnoughHP
jr nc, .failed
push bc
call AnimateCurrentMove
pop bc
callfar SubtractHPFromUser
call UpdateUserInParty
ld a, MAX_STAT_LEVEL - BASE_STAT_LEVEL - 1
.max_attack_loop
push af
call BattleCommand_AttackUp2
pop af
dec a
jr nz, .max_attack_loop
ld hl, BellyDrumText
jp StdBattleTextbox
.failed
call AnimateFailedMove
jp PrintButItFailed

View file

@ -0,0 +1,99 @@
BattleCommand_StoreEnergy:
ld a, BATTLE_VARS_SUBSTATUS3
call GetBattleVar
bit SUBSTATUS_BIDE, a
ret z
ld hl, wPlayerRolloutCount
ldh a, [hBattleTurn]
and a
jr z, .check_still_storing_energy
ld hl, wEnemyRolloutCount
.check_still_storing_energy
dec [hl]
jr nz, .still_storing
ld a, BATTLE_VARS_SUBSTATUS3
call GetBattleVarAddr
res SUBSTATUS_BIDE, [hl]
ld hl, UnleashedEnergyText
call StdBattleTextbox
ld a, BATTLE_VARS_MOVE_POWER
call GetBattleVarAddr
ld a, 1
ld [hl], a
ld hl, wPlayerDamageTaken + 1
ld de, wPlayerCharging ; player
ldh a, [hBattleTurn]
and a
jr z, .player
ld hl, wEnemyDamageTaken + 1
ld de, wEnemyCharging ; enemy
.player
ld a, [hld]
add a
ld b, a
ld [wCurDamage + 1], a
ld a, [hl]
rl a
ld [wCurDamage], a
jr nc, .not_maxed
ld a, $ff
ld [wCurDamage], a
ld [wCurDamage + 1], a
.not_maxed
or b
jr nz, .built_up_something
ld a, 1
ld [wAttackMissed], a
.built_up_something
xor a
ld [hli], a
ld [hl], a
ld [de], a
ld a, BATTLE_VARS_MOVE_ANIM
call GetBattleVarAddr
push hl
ld hl, BIDE
call GetMoveIDFromIndex
pop hl
ld [hl], a
ld b, unleashenergy_command
jp SkipToBattleCommand
.still_storing
ld hl, StoringEnergyText
call StdBattleTextbox
jp EndMoveEffect
BattleCommand_UnleashEnergy:
ld de, wPlayerDamageTaken
ld bc, wPlayerRolloutCount
ldh a, [hBattleTurn]
and a
jr z, .got_damage
ld de, wEnemyDamageTaken
ld bc, wEnemyRolloutCount
.got_damage
ld a, BATTLE_VARS_SUBSTATUS3
call GetBattleVarAddr
set SUBSTATUS_BIDE, [hl]
xor a
ld [de], a
inc de
ld [de], a
ld [wPlayerMoveStructEffect], a
ld [wEnemyMoveStructEffect], a
call BattleRandom
and 1
inc a
inc a
ld [bc], a
ld a, 1
ld [wBattleAnimParam], a
call AnimateCurrentMove
jp EndMoveEffect

View file

@ -0,0 +1,94 @@
BattleCommand_Conversion:
ld hl, wBattleMonMoves
ld de, wBattleMonType1
ldh a, [hBattleTurn]
and a
jr z, .got_moves
ld hl, wEnemyMonMoves
ld de, wEnemyMonType1
.got_moves
push de
ld c, 0
ld de, wStringBuffer1
.loop
push hl
ld b, 0
add hl, bc
ld a, [hl]
pop hl
and a
jr z, .okay
push hl
push bc
ld l, a
ld a, MOVE_TYPE
call GetMoveAttribute
ld [de], a
inc de
pop bc
pop hl
inc c
ld a, c
cp NUM_MOVES
jr c, .loop
.okay
ld a, $ff
ld [de], a
inc de
ld [de], a
inc de
ld [de], a
pop de
ld hl, wStringBuffer1
.loop2
ld a, [hl]
cp -1
jr z, .fail
cp CURSE_TYPE
jr z, .next
ld a, [de]
cp [hl]
jr z, .next
inc de
ld a, [de]
dec de
cp [hl]
jr nz, .done
.next
inc hl
jr .loop2
.fail
call AnimateFailedMove
jp PrintButItFailed
.done
.loop3
call BattleRandom
maskbits NUM_MOVES
ld c, a
ld b, 0
ld hl, wStringBuffer1
add hl, bc
ld a, [hl]
cp -1
jr z, .loop3
cp CURSE_TYPE
jr z, .loop3
ld a, [de]
cp [hl]
jr z, .loop3
inc de
ld a, [de]
dec de
cp [hl]
jr z, .loop3
ld a, [hl]
ld [de], a
inc de
ld [de], a
ld [wNamedObjectIndex], a
farcall GetTypeName
call AnimateCurrentMove
ld hl, TransformedTypeText
jp StdBattleTextbox

View file

@ -0,0 +1,62 @@
BattleCommand_Conversion2:
ld a, [wAttackMissed]
and a
jr nz, .failed
ld hl, wBattleMonType1
ldh a, [hBattleTurn]
and a
jr z, .got_type
ld hl, wEnemyMonType1
.got_type
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
and a
jr z, .failed
push hl
ld l, a
ld a, MOVE_TYPE
call GetMoveAttribute
ld d, a
pop hl
cp CURSE_TYPE
jr z, .failed
call AnimateCurrentMove
call BattleCommand_SwitchTurn
.loop
call BattleRandom
maskbits TYPES_END
cp UNUSED_TYPES
jr c, .okay
cp UNUSED_TYPES_END
jr c, .loop
cp TYPES_END
jr nc, .loop
.okay
ld [hli], a
ld [hld], a
push hl
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVarAddr
push af
push hl
ld a, d
ld [hl], a
call BattleCheckTypeMatchup
pop hl
pop af
ld [hl], a
pop hl
ld a, [wTypeMatchup]
cp EFFECTIVE
jr nc, .loop
call BattleCommand_SwitchTurn
ld a, [hl]
ld [wNamedObjectIndex], a
predef GetTypeName
ld hl, TransformedTypeText
jp StdBattleTextbox
.failed
jp FailMove

View file

@ -0,0 +1,56 @@
BattleCommand_Counter:
ld a, 1
ld [wAttackMissed], a
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
and a
ret z
ld b, a
callfar GetMoveEffect
ld a, b
cp EFFECT_COUNTER
ret z
call BattleCommand_ResetTypeMatchup
ld a, [wTypeMatchup]
and a
ret z
call CheckOpponentWentFirst
ret z
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
ld de, wStringBuffer1
call GetMoveData
ld a, [wStringBuffer1 + MOVE_POWER]
and a
ret z
ld a, [wStringBuffer1 + MOVE_TYPE]
cp SPECIAL
ret nc
; BUG: Counter and Mirror Coat still work if the opponent uses an item (see docs/bugs_and_glitches.md)
ld hl, wCurDamage
ld a, [hli]
or [hl]
ret z
ld a, [hl]
add a
ld [hld], a
ld a, [hl]
adc a
ld [hl], a
jr nc, .capped
ld a, $ff
ld [hli], a
ld [hl], a
.capped
xor a
ld [wAttackMissed], a
ret

View file

@ -0,0 +1,91 @@
BattleCommand_Curse:
ld de, wBattleMonType1
ld bc, wPlayerStatLevels
ldh a, [hBattleTurn]
and a
jr z, .go
ld de, wEnemyMonType1
ld bc, wEnemyStatLevels
.go
; Curse is different for Ghost-types.
ld a, [de]
cp GHOST
jr z, .ghost
inc de
ld a, [de]
cp GHOST
jr z, .ghost
; If no stats can be increased, don't.
; Attack
ld a, [bc]
cp MAX_STAT_LEVEL
jr c, .raise
; Defense
inc bc
ld a, [bc]
cp MAX_STAT_LEVEL
jr nc, .cantraise
.raise
; Raise Attack and Defense, and lower Speed.
ld a, $1
ld [wBattleAnimParam], a
call AnimateCurrentMove
ld a, SPEED
call LowerStat
call BattleCommand_SwitchTurn
call BattleCommand_StatDownMessage
call ResetMiss
call BattleCommand_SwitchTurn
call BattleCommand_AttackUp
call BattleCommand_StatUpMessage
call ResetMiss
call BattleCommand_DefenseUp
jp BattleCommand_StatUpMessage
.ghost
; Cut HP in half and put a curse on the opponent.
call CheckHiddenOpponent
jr nz, .failed
call CheckSubstituteOpp
jr nz, .failed
ld a, BATTLE_VARS_SUBSTATUS1_OPP
call GetBattleVarAddr
bit SUBSTATUS_CURSE, [hl]
jr nz, .failed
set SUBSTATUS_CURSE, [hl]
call AnimateCurrentMove
ld hl, GetHalfMaxHP
call CallBattleCore
ld hl, SubtractHPFromUser
call CallBattleCore
call UpdateUserInParty
ld hl, PutACurseText
jp StdBattleTextbox
.failed
call AnimateFailedMove
jp PrintButItFailed
.cantraise
; Can't raise either stat.
ld b, ABILITY + 1
call GetStatName
call AnimateFailedMove
ld hl, WontRiseAnymoreText
jp StdBattleTextbox

View file

@ -0,0 +1,7 @@
BattleCommand_DestinyBond:
ld a, BATTLE_VARS_SUBSTATUS5
call GetBattleVarAddr
set SUBSTATUS_DESTINY_BOND, [hl]
call AnimateCurrentMove
ld hl, DestinyBondEffectText
jp StdBattleTextbox

View file

@ -0,0 +1,73 @@
BattleCommand_Disable:
ld a, [wAttackMissed]
and a
jr nz, .failed
ld de, wEnemyDisableCount
ld hl, wEnemyMonMoves
ldh a, [hBattleTurn]
and a
jr z, .got_moves
ld de, wPlayerDisableCount
ld hl, wBattleMonMoves
.got_moves
ld a, [de]
and a
jr nz, .failed
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
and a
jr z, .failed
ld b, a
push bc
ld bc, STRUGGLE
call CompareMove
pop bc
jr z, .failed
ld c, $ff
.loop
inc c
ld a, [hli]
cp b
jr nz, .loop
ldh a, [hBattleTurn]
and a
ld hl, wEnemyMonPP
jr z, .got_pp
ld hl, wBattleMonPP
.got_pp
ld b, 0
add hl, bc
ld a, [hl]
and a
jr z, .failed
.loop2
call BattleRandom
and 7
jr z, .loop2
inc a
inc c
swap c
add c
ld [de], a
call AnimateCurrentMove
ld hl, wDisabledMove
ldh a, [hBattleTurn]
and a
jr nz, .got_disabled_move_pointer
inc hl
.got_disabled_move_pointer
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
ld [hl], a
ld [wNamedObjectIndex], a
call GetMoveName
ld hl, WasDisabledText
jp StdBattleTextbox
.failed
jp FailMove

View file

@ -0,0 +1,120 @@
BattleCommand_Encore:
ld hl, wEnemyMonMoves
ld de, wEnemyEncoreCount
ldh a, [hBattleTurn]
and a
jr z, .ok
ld hl, wBattleMonMoves
ld de, wPlayerEncoreCount
.ok
ld a, BATTLE_VARS_LAST_MOVE_OPP
call GetBattleVar
ld b, a
push hl
ld hl, .invalid_moves
call CheckMoveInList
pop hl
jp c, .failed
.got_move
ld a, [hli]
cp b
jr nz, .got_move
ld bc, wBattleMonPP - wBattleMonMoves - 1
add hl, bc
ld a, [hl]
and PP_MASK
jp z, .failed
ld a, [wAttackMissed]
and a
jp nz, .failed
ld a, BATTLE_VARS_SUBSTATUS5_OPP
call GetBattleVarAddr
bit SUBSTATUS_ENCORED, [hl]
jp nz, .failed
set SUBSTATUS_ENCORED, [hl]
call BattleRandom
and $3
inc a
inc a
inc a
ld [de], a
call CheckOpponentWentFirst
jr nz, .finish_move
ldh a, [hBattleTurn]
and a
jr z, .force_last_enemy_move
push hl
ld a, [wLastPlayerMove]
ld b, a
ld c, 0
ld hl, wBattleMonMoves
.find_player_move
ld a, [hli]
cp b
jr z, .got_player_move
inc c
ld a, c
cp NUM_MOVES
jr c, .find_player_move
pop hl
res SUBSTATUS_ENCORED, [hl]
xor a
ld [de], a
jr .failed
.got_player_move
pop hl
ld a, c
ld [wCurMoveNum], a
ld a, b
ld [wCurPlayerMove], a
ld de, wPlayerMoveStruct
call GetMoveData
jr .finish_move
.force_last_enemy_move
push hl
ld a, [wLastEnemyMove]
ld b, a
ld c, 0
ld hl, wEnemyMonMoves
.find_enemy_move
ld a, [hli]
cp b
jr z, .got_enemy_move
inc c
ld a, c
cp NUM_MOVES
jr c, .find_enemy_move
pop hl
res SUBSTATUS_ENCORED, [hl]
xor a
ld [de], a
jr .failed
.got_enemy_move
pop hl
ld a, c
ld [wCurEnemyMoveNum], a
ld a, b
ld [wCurEnemyMove], a
ld de, wEnemyMoveStruct
call GetMoveData
.finish_move
call AnimateCurrentMove
ld hl, GotAnEncoreText
jp StdBattleTextbox
.failed
jp PrintDidntAffect2
.invalid_moves
dw NO_MOVE
dw STRUGGLE
dw ENCORE
dw MIRROR_MOVE
dw -1

View file

@ -0,0 +1,14 @@
BattleCommand_Endure:
; Endure shares code with Protect. See protect.asm.
call ProtectChance
ret c
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVarAddr
set SUBSTATUS_ENDURE, [hl]
call AnimateCurrentMove
ld hl, BracedItselfText
jp StdBattleTextbox

View file

@ -0,0 +1,46 @@
BattleCommand_FalseSwipe:
; Makes sure wCurDamage < MonHP
ld hl, wEnemyMonHP
ldh a, [hBattleTurn]
and a
jr z, .got_hp
ld hl, wBattleMonHP
.got_hp
ld de, wCurDamage
ld c, 2
push hl
push de
call CompareBytes
pop de
pop hl
jr c, .done
ld a, [hli]
ld [de], a
inc de
ld a, [hl]
dec a
ld [de], a
inc a
jr nz, .okay
dec de
ld a, [de]
dec a
ld [de], a
.okay
ld a, [wCriticalHit]
cp 2
jr nz, .carry
xor a
ld [wCriticalHit], a
.carry
scf
ret
.done
and a
ret

View file

@ -0,0 +1,13 @@
BattleCommand_FocusEnergy:
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVarAddr
bit SUBSTATUS_FOCUS_ENERGY, [hl]
jr nz, .already_pumped
set SUBSTATUS_FOCUS_ENERGY, [hl]
call AnimateCurrentMove
ld hl, GettingPumpedText
jp StdBattleTextbox
.already_pumped
call AnimateFailedMove
jp PrintButItFailed

View file

@ -0,0 +1,20 @@
BattleCommand_Foresight:
ld a, [wAttackMissed]
and a
jr nz, .failed
call CheckHiddenOpponent
jr nz, .failed
ld a, BATTLE_VARS_SUBSTATUS1_OPP
call GetBattleVarAddr
bit SUBSTATUS_IDENTIFIED, [hl]
jr nz, .failed
set SUBSTATUS_IDENTIFIED, [hl]
call AnimateCurrentMove
ld hl, IdentifiedText
jp StdBattleTextbox
.failed
jp FailMove

View file

@ -0,0 +1,26 @@
BattleCommand_FrustrationPower:
; BUG: Return and Frustration deal no damage when the user's happiness is low or high, respectively (see docs/bugs_and_glitches.md)
push bc
ld hl, wBattleMonHappiness
ldh a, [hBattleTurn]
and a
jr z, .ok
ld hl, wEnemyMonHappiness
.ok
ld a, $ff
sub [hl]
ldh [hMultiplicand + 2], a
xor a
ldh [hMultiplicand + 0], a
ldh [hMultiplicand + 1], a
ld a, 10
ldh [hMultiplier], a
call Multiply
ld a, 25
ldh [hDivisor], a
ld b, 4
call Divide
ldh a, [hQuotient + 3]
ld d, a
pop bc
ret

View file

@ -0,0 +1,53 @@
BattleCommand_FuryCutter:
ld hl, wPlayerFuryCutterCount
ldh a, [hBattleTurn]
and a
jr z, .go
ld hl, wEnemyFuryCutterCount
.go
ld a, [wAttackMissed]
and a
jp nz, ResetFuryCutterCount
inc [hl]
; Damage capped at 5 turns' worth (16x).
ld a, [hl]
ld b, a
cp 6
jr c, .checkdouble
ld b, 5
.checkdouble
dec b
ret z
; Double the damage
ld hl, wCurDamage + 1
sla [hl]
dec hl
rl [hl]
jr nc, .checkdouble
; No overflow
ld a, $ff
ld [hli], a
ld [hl], a
ret
ResetFuryCutterCount:
push hl
ld hl, wPlayerFuryCutterCount
ldh a, [hBattleTurn]
and a
jr z, .reset
ld hl, wEnemyFuryCutterCount
.reset
xor a
ld [hl], a
pop hl
ret

View file

@ -0,0 +1,77 @@
BattleCommand_CheckFutureSight:
ld hl, wPlayerFutureSightCount
ld de, wPlayerFutureSightDamage
ldh a, [hBattleTurn]
and a
jr z, .ok
ld hl, wEnemyFutureSightCount
ld de, wEnemyFutureSightDamage
.ok
ld a, [hl]
and a
ret z
cp 1
ret nz
ld [hl], 0
ld a, [de]
inc de
ld [wCurDamage], a
ld a, [de]
ld [wCurDamage + 1], a
ld b, futuresight_command
jp SkipToBattleCommand
BattleCommand_FutureSight:
call CheckUserIsCharging
jr nz, .AlreadyChargingFutureSight
ld a, BATTLE_VARS_MOVE_ANIM
call GetBattleVar
ld b, a
ld a, BATTLE_VARS_LAST_COUNTER_MOVE
call GetBattleVarAddr
ld [hl], b
ld a, BATTLE_VARS_LAST_MOVE
call GetBattleVarAddr
ld [hl], b
.AlreadyChargingFutureSight:
ld hl, wPlayerFutureSightCount
ldh a, [hBattleTurn]
and a
jr z, .GotFutureSightCount
ld hl, wEnemyFutureSightCount
.GotFutureSightCount:
ld a, [hl]
and a
jr nz, .failed
ld a, 4
ld [hl], a
call BattleCommand_LowerSub
call BattleCommand_MoveDelay
ld hl, ForesawAttackText
call StdBattleTextbox
call BattleCommand_RaiseSub
ld de, wPlayerFutureSightDamage
ldh a, [hBattleTurn]
and a
jr z, .StoreDamage
ld de, wEnemyFutureSightDamage
.StoreDamage:
ld hl, wCurDamage
ld a, [hl]
ld [de], a
ld [hl], 0
inc hl
inc de
ld a, [hl]
ld [de], a
ld [hl], 0
jp EndMoveEffect
.failed
pop bc
call ResetDamage
call AnimateFailedMove
call PrintButItFailed
jp EndMoveEffect

View file

@ -0,0 +1,32 @@
BattleCommand_HealBell:
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVarAddr
res SUBSTATUS_NIGHTMARE, [hl]
ld de, wPartyMon1Status
ldh a, [hBattleTurn]
and a
jr z, .got_status
ld de, wOTPartyMon1Status
.got_status
ld a, BATTLE_VARS_STATUS
call GetBattleVarAddr
xor a
ld [hl], a
ld h, d
ld l, e
ld bc, PARTYMON_STRUCT_LENGTH
ld d, PARTY_LENGTH
.loop
ld [hl], a
add hl, bc
dec d
jr nz, .loop
call AnimateCurrentMove
ld hl, BellChimedText
call StdBattleTextbox
ldh a, [hBattleTurn]
and a
jp z, CalcPlayerStats
jp CalcEnemyStats

View file

@ -0,0 +1,6 @@
BattleCommand_HiddenPower:
ld a, [wAttackMissed]
and a
ret nz
farcall HiddenPowerDamage
ret

View file

@ -0,0 +1,39 @@
BattleCommand_LeechSeed:
ld a, [wAttackMissed]
and a
jr nz, .evaded
call CheckSubstituteOpp
jr nz, .evaded
ld de, wEnemyMonType1
ldh a, [hBattleTurn]
and a
jr z, .ok
ld de, wBattleMonType1
.ok
ld a, [de]
cp GRASS
jr z, .grass
inc de
ld a, [de]
cp GRASS
jr z, .grass
ld a, BATTLE_VARS_SUBSTATUS4_OPP
call GetBattleVarAddr
bit SUBSTATUS_LEECH_SEED, [hl]
jr nz, .evaded
set SUBSTATUS_LEECH_SEED, [hl]
call AnimateCurrentMove
ld hl, WasSeededText
jp StdBattleTextbox
.grass
call AnimateFailedMove
jp PrintDoesntAffect
.evaded
call AnimateFailedMove
ld hl, EvadedText
jp StdBattleTextbox

View file

@ -0,0 +1,19 @@
BattleCommand_LockOn:
call CheckSubstituteOpp
jr nz, .fail
ld a, [wAttackMissed]
and a
jr nz, .fail
ld a, BATTLE_VARS_SUBSTATUS5_OPP
call GetBattleVarAddr
set SUBSTATUS_LOCK_ON, [hl]
call AnimateCurrentMove
ld hl, TookAimText
jp StdBattleTextbox
.fail
call AnimateFailedMove
jp PrintDidntAffect

View file

@ -0,0 +1,27 @@
BattleCommand_GetMagnitude:
push bc
call BattleRandom
ld b, a
ld hl, MagnitudePower
.loop
ld a, [hli]
cp b
jr nc, .ok
inc hl
inc hl
jr .loop
.ok
ld d, [hl]
push de
inc hl
ld a, [hl]
ld [wTextDecimalByte], a
call BattleCommand_MoveDelay
ld hl, MagnitudeText
call StdBattleTextbox
pop de
pop bc
ret
INCLUDE "data/moves/magnitude_power.asm"

View file

@ -0,0 +1,70 @@
BattleCommand_Metronome:
call ClearLastMove
call CheckUserIsCharging
jr nz, .charging
ld a, [wBattleAnimParam]
push af
call BattleCommand_LowerSub
pop af
ld [wBattleAnimParam], a
.charging
call LoadMoveAnim
.GetMove:
call ChooseRandomMove
; None of the moves in MetronomeExcepts.
ld de, 2
ld hl, MetronomeExcepts
call IsInWordArray
jr c, .GetMove
ld h, b
ld l, c
call GetMoveIDFromIndex
; No moves the user already has.
ld b, a
call CheckUserMove
jr z, .GetMove
ld a, BATTLE_VARS_MOVE
call GetBattleVarAddr
ld [hl], b
call UpdateMoveData
jp ResetTurn
ChooseRandomMove:
; chooses a random valid move and returns it in bc
call BattleRandom
if HIGH(NUM_ATTACKS)
maskbits HIGH(NUM_ATTACKS) + 1
if HIGH(NUM_ATTACKS) & (HIGH(NUM_ATTACKS) + 1)
; if HIGH(NUM_ATTACKS) is not one less than a power of two
cp HIGH(NUM_ATTACKS) + 1
jr nc, ChooseRandomMove
endc
ld b, a
call BattleRandom
ld c, a
or b
jr z, ChooseRandomMove
if LOW(NUM_ATTACKS) != $ff
ld a, b
cp HIGH(NUM_ATTACKS)
ret nz
ld a, c
cp LOW(NUM_ATTACKS) + 1
jr nc, ChooseRandomMove
endc
else
cp NUM_ATTACKS
jr nc, ChooseRandomMove
inc a
ld c, a
ld b, 0
endc
ret
INCLUDE "data/moves/metronome_exception_moves.asm"

View file

@ -0,0 +1,53 @@
BattleCommand_Mimic:
call ClearLastMove
call BattleCommand_MoveDelay
ld a, [wAttackMissed]
and a
jr nz, .fail
ld hl, wBattleMonMoves
ldh a, [hBattleTurn]
and a
jr z, .player_turn
ld hl, wEnemyMonMoves
.player_turn
call CheckHiddenOpponent
jr nz, .fail
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
and a
jr z, .fail
ld b, a
push bc
ld bc, STRUGGLE
call CompareMove
pop bc
jr z, .fail
ld c, NUM_MOVES
.check_already_knows_move
ld a, [hli]
cp b
jr z, .fail
dec c
jr nz, .check_already_knows_move
push hl
ld hl, MIMIC
call GetMoveIDFromIndex
pop hl
.find_mimic
dec hl
cp [hl]
jr nz, .find_mimic
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
ld [hl], a
ld [wNamedObjectIndex], a
ld bc, wBattleMonPP - wBattleMonMoves
add hl, bc
ld [hl], 5
call GetMoveName
call AnimateCurrentMove
ld hl, MimicLearnedMoveText
jp StdBattleTextbox
.fail
jp FailMimic

View file

@ -0,0 +1,57 @@
BattleCommand_MirrorCoat:
ld a, 1
ld [wAttackMissed], a
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
and a
ret z
ld b, a
callfar GetMoveEffect
ld a, b
cp EFFECT_MIRROR_COAT
ret z
call BattleCommand_ResetTypeMatchup
ld a, [wTypeMatchup]
and a
ret z
call CheckOpponentWentFirst
ret z
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
ld de, wStringBuffer1
call GetMoveData
ld a, [wStringBuffer1 + MOVE_POWER]
and a
ret z
ld a, [wStringBuffer1 + MOVE_TYPE]
cp SPECIAL
ret c
; BUG: Counter and Mirror Coat still work if the opponent uses an item (see docs/bugs_and_glitches.md)
ld hl, wCurDamage
ld a, [hli]
or [hl]
ret z
ld a, [hl]
add a
ld [hld], a
ld a, [hl]
adc a
ld [hl], a
jr nc, .capped
ld a, $ff
ld [hli], a
ld [hl], a
.capped
xor a
ld [wAttackMissed], a
ret

View file

@ -0,0 +1,48 @@
BattleCommand_MirrorMove:
call ClearLastMove
ld a, BATTLE_VARS_MOVE
call GetBattleVarAddr
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
and a
jr z, .failed
call CheckUserMove
jr nz, .use
.failed
call AnimateFailedMove
ld hl, MirrorMoveFailedText
call StdBattleTextbox
jp EndMoveEffect
.use
ld a, b
ld [hl], a
ld [wNamedObjectIndex], a
push af
ld a, BATTLE_VARS_MOVE_ANIM
call GetBattleVarAddr
ld d, h
ld e, l
pop af
call GetMoveData
call GetMoveName
call CopyName1
call CheckUserIsCharging
jr nz, .done
ld a, [wBattleAnimParam]
push af
call BattleCommand_LowerSub
pop af
ld [wBattleAnimParam], a
.done
call BattleCommand_MoveDelay
jp ResetTurn

View file

@ -0,0 +1,13 @@
BattleCommand_Mist:
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVarAddr
bit SUBSTATUS_MIST, [hl]
jr nz, .already_mist
set SUBSTATUS_MIST, [hl]
call AnimateCurrentMove
ld hl, MistText
jp StdBattleTextbox
.already_mist
call AnimateFailedMove
jp PrintButItFailed

View file

@ -0,0 +1,35 @@
BattleCommand_Nightmare:
; Can't hit an absent opponent.
call CheckHiddenOpponent
jr nz, .failed
; Can't hit a substitute.
call CheckSubstituteOpp
jr nz, .failed
; Only works on a sleeping opponent.
ld a, BATTLE_VARS_STATUS_OPP
call GetBattleVarAddr
and SLP_MASK
jr z, .failed
; Bail if the opponent is already having a nightmare.
ld a, BATTLE_VARS_SUBSTATUS1_OPP
call GetBattleVarAddr
bit SUBSTATUS_NIGHTMARE, [hl]
jr nz, .failed
; Otherwise give the opponent a nightmare.
set SUBSTATUS_NIGHTMARE, [hl]
call AnimateCurrentMove
ld hl, StartedNightmareText
jp StdBattleTextbox
.failed
call AnimateFailedMove
jp PrintButItFailed

View file

@ -0,0 +1,91 @@
BattleCommand_PainSplit:
ld a, [wAttackMissed]
and a
jp nz, .ButItFailed
call CheckSubstituteOpp
jp nz, .ButItFailed
call AnimateCurrentMove
ld hl, wBattleMonMaxHP + 1
ld de, wEnemyMonMaxHP + 1
call .PlayerShareHP
ld a, $1
ld [wWhichHPBar], a
hlcoord 10, 9
predef AnimateHPBar
ld hl, wEnemyMonHP
ld a, [hli]
ld [wHPBuffer2 + 1], a
ld a, [hli]
ld [wHPBuffer2], a
ld a, [hli]
ld [wHPBuffer1 + 1], a
ld a, [hl]
ld [wHPBuffer1], a
call .EnemyShareHP
xor a
ld [wWhichHPBar], a
call ResetDamage
hlcoord 2, 2
predef AnimateHPBar
farcall _UpdateBattleHUDs
ld hl, SharedPainText
jp StdBattleTextbox
.PlayerShareHP:
ld a, [hld]
ld [wHPBuffer1], a
ld a, [hld]
ld [wHPBuffer1 + 1], a
ld a, [hld]
ld b, a
ld [wHPBuffer2], a
ld a, [hl]
ld [wHPBuffer2 + 1], a
dec de
dec de
ld a, [de]
dec de
add b
ld [wCurDamage + 1], a
ld b, [hl]
ld a, [de]
adc b
srl a
ld [wCurDamage], a
ld a, [wCurDamage + 1]
rr a
ld [wCurDamage + 1], a
inc hl
inc hl
inc hl
inc de
inc de
inc de
.EnemyShareHP:
ld c, [hl]
dec hl
ld a, [wCurDamage + 1]
sub c
ld b, [hl]
dec hl
ld a, [wCurDamage]
sbc b
jr nc, .skip
ld a, [wCurDamage]
ld b, a
ld a, [wCurDamage + 1]
ld c, a
.skip
ld a, c
ld [hld], a
ld [wHPBuffer3], a
ld a, b
ld [hli], a
ld [wHPBuffer3 + 1], a
ret
.ButItFailed:
jp PrintDidntAffect2

View file

@ -0,0 +1,24 @@
BattleCommand_PayDay:
xor a
ld hl, wStringBuffer1
ld [hli], a
ldh a, [hBattleTurn]
and a
ld a, [wBattleMonLevel]
jr z, .ok
ld a, [wEnemyMonLevel]
.ok
add a
ld hl, wPayDayMoney + 2
add [hl]
ld [hld], a
jr nc, .done
inc [hl]
dec hl
jr nz, .done
inc [hl]
.done
ld hl, CoinsScatteredText
jp StdBattleTextbox

View file

@ -0,0 +1,36 @@
BattleCommand_PerishSong:
ld hl, wPlayerSubStatus1
ld de, wEnemySubStatus1
bit SUBSTATUS_PERISH, [hl]
jr z, .ok
ld a, [de]
bit SUBSTATUS_PERISH, a
jr nz, .failed
.ok
bit SUBSTATUS_PERISH, [hl]
jr nz, .enemy
set SUBSTATUS_PERISH, [hl]
ld a, 4
ld [wPlayerPerishCount], a
.enemy
ld a, [de]
bit SUBSTATUS_PERISH, a
jr nz, .done
set SUBSTATUS_PERISH, a
ld [de], a
ld a, 4
ld [wEnemyPerishCount], a
.done
call AnimateCurrentMove
ld hl, StartPerishText
jp StdBattleTextbox
.failed
call AnimateFailedMove
jp PrintButItFailed

View file

@ -0,0 +1,87 @@
BattleCommand_Present:
; BUG: Present damage is incorrect in link battles (see docs/bugs_and_glitches.md)
ld a, [wLinkMode]
cp LINK_COLOSSEUM
jr z, .colosseum_skippush
push bc
push de
.colosseum_skippush
call BattleCommand_Stab
ld a, [wLinkMode]
cp LINK_COLOSSEUM
jr z, .colosseum_skippop
pop de
pop bc
.colosseum_skippop
ld a, [wTypeMatchup]
and a
jp z, AnimateFailedMove
ld a, [wAttackMissed]
and a
jp nz, AnimateFailedMove
push bc
call BattleRandom
ld b, a
ld hl, PresentPower
ld c, 0
.next
ld a, [hli]
cp -1
jr z, .heal_effect
cp b
jr nc, .got_power
inc c
inc hl
jr .next
.got_power
ld a, c
ld [wBattleAnimParam], a
call AnimateCurrentMoveEitherSide
ld d, [hl]
pop bc
ret
.heal_effect
pop bc
ld a, $3 ; heal animation
ld [wBattleAnimParam], a
call AnimateCurrentMove
call BattleCommand_SwitchTurn
ld hl, AICheckPlayerMaxHP
ldh a, [hBattleTurn]
and a
jr z, .got_hp_fn_pointer
ld hl, AICheckEnemyMaxHP
.got_hp_fn_pointer
ld a, BANK(AICheckPlayerMaxHP) ; aka BANK(AICheckEnemyMaxHP)
rst FarCall
jr c, .already_fully_healed
ld hl, GetQuarterMaxHP
call CallBattleCore
call BattleCommand_SwitchTurn
ld hl, RestoreHP
call CallBattleCore
call BattleCommand_SwitchTurn
ld hl, RegainedHealthText
call StdBattleTextbox
call BattleCommand_SwitchTurn
call UpdateOpponentInParty
jr .do_animation
.already_fully_healed
call BattleCommand_SwitchTurn
call _CheckBattleScene
jr nc, .do_animation
call AnimateFailedMove
ld hl, PresentFailedText
call StdBattleTextbox
.do_animation
jp EndMoveEffect
INCLUDE "data/moves/present_power.asm"

View file

@ -0,0 +1,74 @@
BattleCommand_Protect:
call ProtectChance
ret c
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVarAddr
set SUBSTATUS_PROTECT, [hl]
call AnimateCurrentMove
ld hl, ProtectedItselfText
jp StdBattleTextbox
ProtectChance:
ld de, wPlayerProtectCount
ldh a, [hBattleTurn]
and a
jr z, .got_count
ld de, wEnemyProtectCount
.got_count
call CheckOpponentWentFirst
jr nz, .failed
; Can't have a substitute.
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVar
bit SUBSTATUS_SUBSTITUTE, a
jr nz, .failed
; Halve the chance of a successful Protect for each consecutive use.
ld b, $ff
ld a, [de]
ld c, a
.loop
ld a, c
and a
jr z, .done
dec c
srl b
ld a, b
and a
jr nz, .loop
jr .failed
.done
.rand
call BattleRandom
and a
jr z, .rand
dec a
cp b
jr nc, .failed
; Another consecutive Protect use.
ld a, [de]
inc a
ld [de], a
and a
ret
.failed
xor a
ld [de], a
call AnimateFailedMove
call PrintButItFailed
scf
ret

View file

@ -0,0 +1,47 @@
BattleCommand_PsychUp:
ld hl, wEnemyStatLevels
ld de, wPlayerStatLevels
ldh a, [hBattleTurn]
and a
jr z, .pointers_correct
; It's the enemy's turn, so swap the pointers.
push hl
ld h, d
ld l, e
pop de
.pointers_correct
push hl
ld b, NUM_LEVEL_STATS
; If any of the enemy's stats is modified from its base level,
; the move succeeds. Otherwise, it fails.
.loop
ld a, [hli]
cp BASE_STAT_LEVEL
jr nz, .break
dec b
jr nz, .loop
pop hl
call AnimateFailedMove
jp PrintButItFailed
.break
pop hl
ld b, NUM_LEVEL_STATS
.loop2
ld a, [hli]
ld [de], a
inc de
dec b
jr nz, .loop2
ldh a, [hBattleTurn]
and a
jr nz, .calc_enemy_stats
call CalcPlayerStats
jr .merge
.calc_enemy_stats
call CalcEnemyStats
.merge
call AnimateCurrentMove
ld hl, CopiedStatsText
jp StdBattleTextbox

View file

@ -0,0 +1,23 @@
BattleCommand_Pursuit:
; Double damage if the opponent is switching.
ld hl, wEnemyIsSwitching
ldh a, [hBattleTurn]
and a
jr z, .ok
ld hl, wPlayerIsSwitching
.ok
ld a, [hl]
and a
ret z
ld hl, wCurDamage + 1
sla [hl]
dec hl
rl [hl]
ret nc
ld a, $ff
ld [hli], a
ld [hl], a
ret

View file

@ -0,0 +1,5 @@
BattleCommand_Rage:
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVarAddr
set SUBSTATUS_RAGE, [hl]
ret

View file

@ -0,0 +1,8 @@
BattleCommand_StartRain:
ld a, WEATHER_RAIN
ld [wBattleWeather], a
ld a, 5
ld [wWeatherCount], a
call AnimateCurrentMove
ld hl, DownpourText
jp StdBattleTextbox

View file

@ -0,0 +1,34 @@
BattleCommand_ClearHazards:
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVarAddr
bit SUBSTATUS_LEECH_SEED, [hl]
jr z, .not_leeched
res SUBSTATUS_LEECH_SEED, [hl]
ld hl, ShedLeechSeedText
call StdBattleTextbox
.not_leeched
ld hl, wPlayerScreens
ld de, wPlayerWrapCount
ldh a, [hBattleTurn]
and a
jr z, .got_screens_wrap
ld hl, wEnemyScreens
ld de, wEnemyWrapCount
.got_screens_wrap
bit SCREENS_SPIKES, [hl]
jr z, .no_spikes
res SCREENS_SPIKES, [hl]
ld hl, BlewSpikesText
push de
call StdBattleTextbox
pop de
.no_spikes
ld a, [de]
and a
ret z
xor a
ld [de], a
ld hl, ReleasedByText
jp StdBattleTextbox

View file

@ -0,0 +1,25 @@
BattleCommand_HappinessPower:
; BUG: Return and Frustration deal no damage when the user's happiness is low or high, respectively (see docs/bugs_and_glitches.md)
push bc
ld hl, wBattleMonHappiness
ldh a, [hBattleTurn]
and a
jr z, .ok
ld hl, wEnemyMonHappiness
.ok
xor a
ldh [hMultiplicand + 0], a
ldh [hMultiplicand + 1], a
ld a, [hl]
ldh [hMultiplicand + 2], a
ld a, 10
ldh [hMultiplier], a
call Multiply
ld a, 25
ldh [hDivisor], a
ld b, 4
call Divide
ldh a, [hQuotient + 3]
ld d, a
pop bc
ret

View file

@ -0,0 +1,91 @@
DEF MAX_ROLLOUT_COUNT EQU 5
BattleCommand_CheckCurl:
ld de, wPlayerRolloutCount
ldh a, [hBattleTurn]
and a
jr z, .ok
ld de, wEnemyRolloutCount
.ok
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVar
bit SUBSTATUS_ROLLOUT, a
jr z, .reset
ld b, doturn_command
jp SkipToBattleCommand
.reset
xor a
ld [de], a
ret
BattleCommand_RolloutPower:
ld a, BATTLE_VARS_STATUS
call GetBattleVar
and SLP_MASK
ret nz
ld hl, wPlayerRolloutCount
ldh a, [hBattleTurn]
and a
jr z, .got_rollout_count
ld hl, wEnemyRolloutCount
.got_rollout_count
ld a, [hl]
and a
jr nz, .skip_set_rampage
ld a, 1
ld [wSomeoneIsRampaging], a
.skip_set_rampage
ld a, [wAttackMissed]
and a
jr z, .hit
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVarAddr
res 6, [hl]
ret
.hit
inc [hl]
ld a, [hl]
ld b, a
cp MAX_ROLLOUT_COUNT
jr c, .not_done_with_rollout
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVarAddr
res SUBSTATUS_ROLLOUT, [hl]
jr .done_with_substatus_flag
.not_done_with_rollout
ld a, BATTLE_VARS_SUBSTATUS1
call GetBattleVarAddr
set SUBSTATUS_ROLLOUT, [hl]
.done_with_substatus_flag
ld a, BATTLE_VARS_SUBSTATUS2
call GetBattleVar
bit SUBSTATUS_CURLED, a
jr z, .not_curled
inc b
.not_curled
.loop
dec b
jr z, .done_damage
ld hl, wCurDamage + 1
sla [hl]
dec hl
rl [hl]
jr nc, .loop
ld a, $ff
ld [hli], a
ld [hl], a
.done_damage
ret

View file

@ -0,0 +1,21 @@
BattleCommand_Safeguard:
ld hl, wPlayerScreens
ld de, wPlayerSafeguardCount
ldh a, [hBattleTurn]
and a
jr z, .ok
ld hl, wEnemyScreens
ld de, wEnemySafeguardCount
.ok
bit SCREENS_SAFEGUARD, [hl]
jr nz, .failed
set SCREENS_SAFEGUARD, [hl]
ld a, 5
ld [de], a
call AnimateCurrentMove
ld hl, CoveredByVeilText
jp StdBattleTextbox
.failed
call AnimateFailedMove
jp PrintButItFailed

View file

@ -0,0 +1,16 @@
BattleCommand_StartSandstorm:
ld a, [wBattleWeather]
cp WEATHER_SANDSTORM
jr z, .failed
ld a, WEATHER_SANDSTORM
ld [wBattleWeather], a
ld a, 5
ld [wWeatherCount], a
call AnimateCurrentMove
ld hl, SandstormBrewedText
jp StdBattleTextbox
.failed
call AnimateFailedMove
jp PrintButItFailed

View file

@ -0,0 +1,29 @@
BattleCommand_Selfdestruct:
farcall StubbedTrainerRankings_Selfdestruct
ld a, BATTLEANIM_PLAYER_DAMAGE
ld [wNumHits], a
ld c, 3
call DelayFrames
ld a, BATTLE_VARS_STATUS
call GetBattleVarAddr
xor a
ld [hli], a
inc hl
ld [hli], a
ld [hl], a
ld a, $1
ld [wBattleAnimParam], a
call BattleCommand_LowerSub
call LoadMoveAnim
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVarAddr
res SUBSTATUS_LEECH_SEED, [hl]
ld a, BATTLE_VARS_SUBSTATUS5_OPP
call GetBattleVarAddr
res SUBSTATUS_DESTINY_BOND, [hl]
call _CheckBattleScene
ret nc
farcall DrawPlayerHUD
farcall DrawEnemyHUD
call WaitBGMap
jp RefreshBattleHuds

View file

@ -0,0 +1,121 @@
BattleCommand_Sketch:
call ClearLastMove
; Don't sketch during a link battle
ld a, [wLinkMode]
and a
jr z, .not_linked
call AnimateFailedMove
jp PrintNothingHappened
.not_linked
; If the opponent has a substitute up, fail.
call CheckSubstituteOpp
jp nz, .fail
; If the opponent is transformed, fail.
; BUG: A Transformed Pokémon can use Sketch and learn otherwise unobtainable moves (see docs/bugs_and_glitches.md)
ld a, BATTLE_VARS_SUBSTATUS5_OPP
call GetBattleVarAddr
bit SUBSTATUS_TRANSFORMED, [hl]
jp nz, .fail
; Get the user's moveset in its party struct.
; This move replacement shall be permanent.
; Pointer will be in de.
ld a, MON_MOVES
call UserPartyAttr
ld d, h
ld e, l
; Get the battle move structs.
ld hl, wBattleMonMoves
ldh a, [hBattleTurn]
and a
jr z, .get_last_move
ld hl, wEnemyMonMoves
.get_last_move
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
ld [wNamedObjectIndex], a
ld b, a
; Fail if move is invalid or is Struggle.
and a
jr z, .fail
push bc
ld bc, STRUGGLE
call CompareMove
pop bc
jr z, .fail
; Fail if user already knows that move
ld c, NUM_MOVES
.does_user_already_know_move
ld a, [hli]
cp b
jr z, .fail
dec c
jr nz, .does_user_already_know_move
; Find Sketch in the user's moveset.
; Pointer in hl, and index in c.
push hl
ld hl, SKETCH
call GetMoveIDFromIndex
pop hl
ld c, NUM_MOVES
.find_sketch
dec c
dec hl
cp [hl]
jr nz, .find_sketch
; The Sketched move is loaded to that slot.
ld a, b
ld [hl], a
; Copy the base PP from that move.
push bc
push hl
ld l, a
ld a, MOVE_PP
call GetMoveAttribute
pop hl
ld bc, wBattleMonPP - wBattleMonMoves
add hl, bc
ld [hl], a
pop bc
ldh a, [hBattleTurn]
and a
jr z, .user_trainer
ld a, [wBattleMode]
dec a
jr nz, .user_trainer
; wildmon
ld a, [hl]
push bc
ld hl, wWildMonPP
ld b, 0
add hl, bc
ld [hl], a
ld hl, wWildMonMoves
add hl, bc
pop bc
ld [hl], b
jr .done_copy
.user_trainer
ld a, [hl]
push af
ld l, c
ld h, 0
add hl, de
ld a, b
ld [hl], a
pop af
ld de, MON_PP - MON_MOVES
add hl, de
ld [hl], a
.done_copy
call GetMoveName
call AnimateCurrentMove
ld hl, SketchedText
jp StdBattleTextbox
.fail
call AnimateFailedMove
jp PrintDidntAffect

View file

@ -0,0 +1,141 @@
BattleCommand_SleepTalk:
call ClearLastMove
ld a, [wAttackMissed]
and a
jr nz, .fail
ldh a, [hBattleTurn]
and a
ld hl, wBattleMonMoves + 1
ld a, [wDisabledMove]
ld d, a
jr z, .got_moves
ld hl, wEnemyMonMoves + 1
ld a, [wEnemyDisabledMove]
ld d, a
.got_moves
ld a, BATTLE_VARS_STATUS
call GetBattleVar
and SLP_MASK
jr z, .fail
ld a, [hl]
and a
jr z, .fail
call .safely_check_has_usable_move
jr c, .fail
dec hl
.sample_move
push hl
call BattleRandom
maskbits NUM_MOVES
ld c, a
ld b, 0
add hl, bc
ld a, [hl]
pop hl
and a
jr z, .sample_move
ld e, a
ld a, BATTLE_VARS_MOVE_ANIM
call GetBattleVar
cp e
jr z, .sample_move
ld a, e
cp d
jr z, .sample_move
call .check_two_turn_move
jr z, .sample_move
ld a, BATTLE_VARS_MOVE
call GetBattleVarAddr
ld a, e
ld [hl], a
call CheckUserIsCharging
jr nz, .charging
ld a, [wBattleAnimParam]
push af
call BattleCommand_LowerSub
pop af
ld [wBattleAnimParam], a
.charging
call LoadMoveAnim
call UpdateMoveData
jp ResetTurn
.fail
call AnimateFailedMove
jp TryPrintButItFailed
.safely_check_has_usable_move
push hl
push de
push bc
call .check_has_usable_move
pop bc
pop de
pop hl
ret
.check_has_usable_move
ldh a, [hBattleTurn]
and a
ld a, [wDisabledMove]
jr z, .got_move_2
ld a, [wEnemyDisabledMove]
.got_move_2
ld b, a
ld a, BATTLE_VARS_MOVE
call GetBattleVar
ld c, a
dec hl
ld d, NUM_MOVES
.loop2
ld a, [hl]
and a
jr z, .carry
cp c
jr z, .nope
cp b
jr z, .nope
call .check_two_turn_move
jr nz, .no_carry
.nope
inc hl
dec d
jr nz, .loop2
.carry
scf
ret
.no_carry
and a
ret
.check_two_turn_move
push hl
push de
push bc
ld b, a
callfar GetMoveEffect
ld a, b
pop bc
pop de
pop hl
cp EFFECT_SKULL_BASH
ret z
cp EFFECT_RAZOR_WIND
ret z
cp EFFECT_SKY_ATTACK
ret z
cp EFFECT_SOLARBEAM
ret z
cp EFFECT_FLY
ret z
cp EFFECT_BIDE
ret

View file

@ -0,0 +1,10 @@
BattleCommand_Snore:
ld a, BATTLE_VARS_STATUS
call GetBattleVar
and SLP_MASK
ret nz
call ResetDamage
ld a, $1
ld [wAttackMissed], a
call FailMove
jp EndMoveEffect

View file

@ -0,0 +1,24 @@
BattleCommand_Spikes:
ld hl, wEnemyScreens
ldh a, [hBattleTurn]
and a
jr z, .got_screens
ld hl, wPlayerScreens
.got_screens
; Fails if spikes are already down!
bit SCREENS_SPIKES, [hl]
jr nz, .failed
; Nothing else stops it from working.
set SCREENS_SPIKES, [hl]
call AnimateCurrentMove
ld hl, SpikesText
jp StdBattleTextbox
.failed
jp FailMove

View file

@ -0,0 +1,87 @@
BattleCommand_Spite:
ld a, [wAttackMissed]
and a
jp nz, .failed
ld bc, PARTYMON_STRUCT_LENGTH ; unused
ld hl, wEnemyMonMoves
ldh a, [hBattleTurn]
and a
jr z, .got_moves
ld hl, wBattleMonMoves
.got_moves
ld a, BATTLE_VARS_LAST_COUNTER_MOVE_OPP
call GetBattleVar
and a
jr z, .failed
ld b, a
push bc
ld bc, STRUGGLE
call CompareMove
pop bc
jr z, .failed
ld c, -1
.loop
inc c
ld a, [hli]
cp b
jr nz, .loop
ld [wNamedObjectIndex], a
dec hl
ld b, 0
push bc
ld c, wBattleMonPP - wBattleMonMoves
add hl, bc
pop bc
ld a, [hl]
and PP_MASK
jr z, .failed
push bc
call GetMoveName
; lose 2-5 PP
call BattleRandom
and %11
inc a
inc a
ld b, a
ld a, [hl]
and PP_MASK
cp b
jr nc, .deplete_pp
ld b, a
.deplete_pp
ld a, [hl]
sub b
ld [hl], a
push af
ld a, MON_PP
call OpponentPartyAttr
ld d, b
pop af
pop bc
add hl, bc
ld e, a
ld a, BATTLE_VARS_SUBSTATUS5_OPP
call GetBattleVar
bit SUBSTATUS_TRANSFORMED, a
jr nz, .transformed
ldh a, [hBattleTurn]
and a
jr nz, .not_wildmon
ld a, [wBattleMode]
dec a
jr nz, .not_wildmon
ld hl, wWildMonPP
add hl, bc
.not_wildmon
ld [hl], e
.transformed
push de
call AnimateCurrentMove
pop de
ld a, d
ld [wTextDecimalByte], a
ld hl, SpiteEffectText
jp StdBattleTextbox
.failed
jp PrintDidntAffect2

View file

@ -0,0 +1,4 @@
BattleCommand_Splash:
call AnimateCurrentMove
farcall StubbedTrainerRankings_Splash
jp PrintNothingHappened

View file

@ -0,0 +1,86 @@
BattleCommand_Substitute:
call BattleCommand_MoveDelay
ld hl, wBattleMonMaxHP
ld de, wPlayerSubstituteHP
ldh a, [hBattleTurn]
and a
jr z, .got_hp
ld hl, wEnemyMonMaxHP
ld de, wEnemySubstituteHP
.got_hp
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVar
bit SUBSTATUS_SUBSTITUTE, a
jr nz, .already_has_sub
ld a, [hli]
ld b, [hl]
srl a
rr b
srl a
rr b
dec hl
dec hl
ld a, b
ld [de], a
ld a, [hld]
sub b
ld e, a
ld a, [hl]
sbc 0
ld d, a
jr c, .too_weak_to_sub
ld a, d
or e
jr z, .too_weak_to_sub
ld [hl], d
inc hl
ld [hl], e
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVarAddr
set SUBSTATUS_SUBSTITUTE, [hl]
ld hl, wPlayerWrapCount
ld de, wPlayerTrappingMove
ldh a, [hBattleTurn]
and a
jr z, .player
ld hl, wEnemyWrapCount
ld de, wEnemyTrappingMove
.player
xor a
ld [hl], a
ld [de], a
call _CheckBattleScene
jr c, .no_anim
xor a
ld [wNumHits], a
ld [wBattleAnimParam], a
ld hl, SUBSTITUTE
call GetMoveIDFromIndex
call LoadAnim
jr .finish
.no_anim
call BattleCommand_RaiseSubNoAnim
.finish
ld hl, MadeSubstituteText
call StdBattleTextbox
jp RefreshBattleHuds
.already_has_sub
call CheckUserIsCharging
call nz, BattleCommand_RaiseSub
ld hl, HasSubstituteText
jr .jp_stdbattletextbox
.too_weak_to_sub
call CheckUserIsCharging
call nz, BattleCommand_RaiseSub
ld hl, TooWeakSubText
.jp_stdbattletextbox
jp StdBattleTextbox

View file

@ -0,0 +1,8 @@
BattleCommand_StartSun:
ld a, WEATHER_SUN
ld [wBattleWeather], a
ld a, 5
ld [wWeatherCount], a
call AnimateCurrentMove
ld hl, SunGotBrightText
jp StdBattleTextbox

View file

@ -0,0 +1,94 @@
BattleCommand_Teleport:
ld a, [wBattleType]
cp BATTLETYPE_SHINY
jr z, .failed
cp BATTLETYPE_TRAP
jr z, .failed
cp BATTLETYPE_CELEBI
jr z, .failed
cp BATTLETYPE_SUICUNE
jr z, .failed
ld a, BATTLE_VARS_SUBSTATUS5_OPP
call GetBattleVar
bit SUBSTATUS_CANT_RUN, a
jr nz, .failed
ldh a, [hBattleTurn]
and a
jr nz, .enemy_turn
; Can't teleport from a trainer battle
ld a, [wBattleMode]
dec a
jr nz, .failed
; b = player level
ld a, [wCurPartyLevel]
ld b, a
; If player level >= enemy level, Teleport will succeed
ld a, [wBattleMonLevel]
cp b
jr nc, .run_away
; c = player level + enemy level + 1
add b
ld c, a
inc c
; Generate a number less than c
.loop_player
call BattleRandom
cp c
jr nc, .loop_player
; b = enemy level / 4
srl b
srl b
; If the random number >= enemy level / 4, Teleport will succeed
cp b
jr nc, .run_away
.failed
call AnimateFailedMove
jp PrintButItFailed
.enemy_turn
; Can't teleport from a trainer battle
ld a, [wBattleMode]
dec a
jr nz, .failed
; b = enemy level
ld a, [wBattleMonLevel]
ld b, a
; If enemy level >= player level, Teleport will succeed
ld a, [wCurPartyLevel]
cp b
jr nc, .run_away
; c = enemy level + player level + 1
add b
ld c, a
inc c
; Generate a number less than c
.loop_enemy
; BUG: Wild Pokémon can always Teleport regardless of level difference (see docs/bugs_and_glitches.md)
call BattleRandom
cp c
jr nc, .loop_enemy
; b = player level / 4
srl b
srl b
cp b
jr nc, .run_away
.run_away
call UpdateBattleMonInParty
xor a
ld [wNumHits], a
inc a
ld [wForcedSwitch], a
ld [wBattleAnimParam], a
call SetBattleDraw
call BattleCommand_LowerSub
call LoadMoveAnim
ld c, 20
call DelayFrames
call SetBattleDraw
ld hl, FledFromBattleText
jp StdBattleTextbox

View file

@ -0,0 +1,110 @@
BattleCommand_Thief:
ldh a, [hBattleTurn]
and a
jr nz, .enemy
; The player needs to be able to steal an item.
call .playeritem
ld a, [hl]
and a
ret nz
; The enemy needs to have an item to steal.
call .enemyitem
ld a, [hl]
and a
ret z
; Can't steal mail.
ld [wNamedObjectIndex], a
ld d, a
farcall ItemIsMail
ret c
ld a, [wEffectFailed]
and a
ret nz
ld a, [wLinkMode]
and a
jr z, .stealenemyitem
ld a, [wBattleMode]
dec a
ret z
.stealenemyitem
call .enemyitem
xor a
ld [hl], a
ld [de], a
call .playeritem
ld a, [wNamedObjectIndex]
ld [hl], a
ld [de], a
jr .stole
.enemy
; The enemy can't already have an item.
call .enemyitem
ld a, [hl]
and a
ret nz
; The player must have an item to steal.
call .playeritem
ld a, [hl]
and a
ret z
; Can't steal mail!
ld [wNamedObjectIndex], a
ld d, a
farcall ItemIsMail
ret c
ld a, [wEffectFailed]
and a
ret nz
; If the enemy steals your item,
; it's gone for good if you don't get it back.
call .playeritem
xor a
ld [hl], a
ld [de], a
call .enemyitem
ld a, [wNamedObjectIndex]
ld [hl], a
ld [de], a
.stole
call GetItemName
ld hl, StoleText
jp StdBattleTextbox
.playeritem
ld a, MON_ITEM
call BattlePartyAttr
ld d, h
ld e, l
ld hl, wBattleMonItem
ret
.enemyitem
ld a, MON_ITEM
call OTPartyAttr
ld d, h
ld e, l
ld hl, wEnemyMonItem
ret

View file

@ -0,0 +1,16 @@
BattleCommand_ThunderAccuracy:
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVarAddr
inc hl
ld a, [wBattleWeather]
cp WEATHER_RAIN
jr z, .rain
cp WEATHER_SUN
ret nz
ld [hl], 50 percent + 1
ret
.rain
; Redundant with CheckHit guranteeing hit
ld [hl], 100 percent
ret

View file

@ -0,0 +1,156 @@
BattleCommand_Transform:
call ClearLastMove
ld a, BATTLE_VARS_SUBSTATUS5_OPP
call GetBattleVarAddr
bit SUBSTATUS_TRANSFORMED, [hl]
jp nz, BattleEffect_ButItFailed
call CheckHiddenOpponent
jp nz, BattleEffect_ButItFailed
xor a
ld [wNumHits], a
ld a, $1
ld [wBattleAnimParam], a
ld a, BATTLE_VARS_SUBSTATUS4
call GetBattleVarAddr
bit SUBSTATUS_SUBSTITUTE, [hl]
push af
jr z, .mimic_substitute
call CheckUserIsCharging
jr nz, .mimic_substitute
ld hl, SUBSTITUTE
call GetMoveIDFromIndex
call LoadAnim
.mimic_substitute
ld a, BATTLE_VARS_SUBSTATUS5
call GetBattleVarAddr
set SUBSTATUS_TRANSFORMED, [hl]
call ResetActorDisable
ld hl, wBattleMonSpecies
ld de, wEnemyMonSpecies
ldh a, [hBattleTurn]
and a
jr nz, .got_mon_species
ld hl, wEnemyMonSpecies
ld de, wBattleMonSpecies
xor a
ld [wCurMoveNum], a
.got_mon_species
push hl
ld a, [hli]
ld [de], a
inc hl
inc de
inc de
ld bc, NUM_MOVES
call CopyBytes
ldh a, [hBattleTurn]
and a
jr z, .mimic_enemy_backup
ld a, [de]
ld [wEnemyBackupDVs], a
inc de
ld a, [de]
ld [wEnemyBackupDVs + 1], a
dec de
.mimic_enemy_backup
; copy DVs
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
inc de
; move pointer to stats
ld bc, wBattleMonStats - wBattleMonPP
add hl, bc
push hl
ld h, d
ld l, e
add hl, bc
ld d, h
ld e, l
pop hl
ld bc, wBattleMonStructEnd - wBattleMonStats
call CopyBytes
; init the power points
ld bc, wBattleMonMoves - wBattleMonStructEnd
add hl, bc
push de
ld d, h
ld e, l
pop hl
ld bc, wBattleMonPP - wBattleMonStructEnd
add hl, bc
ld b, NUM_MOVES
.pp_loop
ld a, [de]
inc de
and a
jr z, .done_move
push bc
ld bc, SKETCH
call CompareMove
pop bc
ld a, 1
jr z, .done_move
ld a, 5
.done_move
ld [hli], a
dec b
jr nz, .pp_loop
pop hl
ld a, [hl]
ld [wNamedObjectIndex], a
call GetPokemonName
ld hl, wEnemyStats
ld de, wPlayerStats
ld bc, 2 * 5
call BattleSideCopy
ld hl, wEnemyStatLevels
ld de, wPlayerStatLevels
ld bc, 8
call BattleSideCopy
call _CheckBattleScene
jr c, .mimic_anims
ldh a, [hBattleTurn]
and a
ld a, [wPlayerMinimized]
jr z, .got_byte
ld a, [wEnemyMinimized]
.got_byte
and a
jr nz, .mimic_anims
call LoadMoveAnim
jr .after_anim
.mimic_anims
call BattleCommand_MoveDelay
call BattleCommand_RaiseSubNoAnim
.after_anim
xor a
ld [wNumHits], a
ld a, $2
ld [wBattleAnimParam], a
pop af
jr z, .no_substitute
ld hl, SUBSTITUTE
call GetMoveIDFromIndex
call LoadAnim
.no_substitute
ld hl, TransformedText
jp StdBattleTextbox
BattleSideCopy:
; Copy bc bytes from hl to de if it's the player's turn.
; Copy bc bytes from de to hl if it's the enemy's turn.
ldh a, [hBattleTurn]
and a
jr z, .copy
; Swap hl and de
push hl
ld h, d
ld l, e
pop de
.copy
jp CopyBytes

View file

@ -0,0 +1,30 @@
BattleCommand_TripleKick:
ld a, [wBattleAnimParam]
ld b, a
inc b
ld hl, wCurDamage + 1
ld a, [hld]
ld e, a
ld a, [hli]
ld d, a
.next_kick
dec b
ret z
ld a, [hl]
add e
ld [hld], a
ld a, [hl]
adc d
ld [hli], a
; No overflow.
jr nc, .next_kick
ld a, $ff
ld [hld], a
ld [hl], a
ret
BattleCommand_KickCounter:
ld hl, wBattleAnimParam
inc [hl]
ret

View file

@ -0,0 +1,66 @@
GetTrainerClassName:
ld hl, wRivalName
ld a, c
cp RIVAL1
jr z, .rival
ld [wCurSpecies], a
ld a, TRAINER_NAME
ld [wNamedObjectType], a
call GetName
ld de, wStringBuffer1
ret
.rival
ld de, wStringBuffer1
push de
ld bc, NAME_LENGTH
call CopyBytes
pop de
ret
GetOTName:
ld hl, wOTPlayerName
ld a, [wLinkMode]
and a
jr nz, .ok
ld hl, wRivalName
ld a, c
cp RIVAL1
jr z, .ok
ld [wCurSpecies], a
ld a, TRAINER_NAME
ld [wNamedObjectType], a
call GetName
ld hl, wStringBuffer1
.ok
ld bc, TRAINER_CLASS_NAME_LENGTH
ld de, wOTClassName
push de
call CopyBytes
pop de
ret
GetTrainerAttributes:
ld a, [wTrainerClass]
ld c, a
call GetOTName
ld a, [wTrainerClass]
dec a
ld hl, TrainerClassAttributes + TRNATTR_ITEM1
ld bc, NUM_TRAINER_ATTRIBUTES
call AddNTimes
ld de, wEnemyTrainerItem1
ld a, [hli]
ld [de], a
inc de
ld a, [hli]
ld [de], a
ld a, [hl]
ld [wEnemyTrainerBaseReward], a
ret
INCLUDE "data/trainers/attributes.asm"

View file

@ -0,0 +1,21 @@
GetTrainerDVs:
; Return the DVs of wOtherTrainerClass in bc
push hl
ld a, [wOtherTrainerClass]
dec a
ld c, a
ld b, 0
ld hl, TrainerClassDVs
add hl, bc
add hl, bc
ld a, [hli]
ld b, a
ld c, [hl]
pop hl
ret
INCLUDE "data/trainers/dvs.asm"

View file

@ -0,0 +1,298 @@
ReadTrainerParty:
ld a, [wInBattleTowerBattle]
bit 0, a
ret nz
ld a, [wLinkMode]
and a
ret nz
ld hl, wOTPartyCount
xor a
ld [hli], a
dec a
ld [hl], a
ld hl, wOTPartyMons
ld bc, PARTYMON_STRUCT_LENGTH * PARTY_LENGTH
xor a
call ByteFill
ld a, [wOtherTrainerClass]
cp CAL
jr nz, .not_cal2
ld a, [wOtherTrainerID]
cp CAL2
jr z, .cal2
ld a, [wOtherTrainerClass]
.not_cal2
dec a
ld c, a
ld b, 0
ld hl, TrainerGroups
add hl, bc
add hl, bc
add hl, bc
ld a, [hli]
ld [wTrainerGroupBank], a
ld a, [hli]
ld h, [hl]
ld l, a
ld a, [wOtherTrainerID]
ld b, a
.skip_trainer
dec b
jr z, .got_trainer
.loop
ld a, [wTrainerGroupBank]
call GetFarByte
add a, l
ld l, a
jr nc, .skip_trainer
inc h
jr .skip_trainer
.got_trainer
inc hl
.skip_name
call GetNextTrainerDataByte
cp "@"
jr nz, .skip_name
call GetNextTrainerDataByte
ld [wOtherTrainerType], a
ld d, h
ld e, l
call ReadTrainerPartyPieces
.done
jp ComputeTrainerReward
.cal2
ld a, BANK(sMysteryGiftTrainer)
call OpenSRAM
ld a, TRAINERTYPE_MOVES
ld [wOtherTrainerType], a
ld de, sMysteryGiftTrainer
call ReadTrainerPartyPieces
call CloseSRAM
jr .done
ReadTrainerPartyPieces:
ld h, d
ld l, e
.loop
call GetNextTrainerDataByte
cp $ff
ret z
ld [wCurPartyLevel], a
call GetNextTrainerDataByte
push hl
push af
call GetNextTrainerDataByte
ld h, a
pop af
ld l, a
call GetPokemonIDFromIndex
ld [wCurPartySpecies], a
ld a, OTPARTYMON
ld [wMonType], a
predef TryAddMonToParty
pop hl
inc hl ;because hl was pushed before the last call to GetNextTrainerDataByte
ld a, [wOtherTrainerType]
and TRAINERTYPE_ITEM
jr z, .no_item
push hl
ld a, [wOTPartyCount]
dec a
ld hl, wOTPartyMon1Item
call GetPartyLocation
ld d, h
ld e, l
pop hl
call GetNextTrainerDataByte
ld [de], a
.no_item
ld a, [wOtherTrainerType]
rra ; TRAINERTYPE_MOVES_F == 0
jr nc, .no_moves
push hl
ld a, [wOTPartyCount]
dec a
ld hl, wOTPartyMon1Moves
call GetPartyLocation
ld d, h
ld e, l
pop hl
ld b, NUM_MOVES
.copy_moves
call GetNextTrainerDataByte
push hl
push af
call GetNextTrainerDataByte
ld h, a
pop af
ld l, a
call GetMoveIDFromIndex
pop hl
inc hl
ld [de], a
inc de
dec b
jr nz, .copy_moves
push hl
ld a, [wOTPartyCount]
dec a
ld hl, wOTPartyMon1
call GetPartyLocation
ld d, h
ld e, l
ld hl, MON_PP
add hl, de
push hl
ld hl, MON_MOVES
add hl, de
pop de
ld b, NUM_MOVES
.copy_pp
ld a, [hli]
and a
jr z, .copied_pp
push hl
ld l, a
ld a, MOVE_PP
call GetMoveAttribute
pop hl
ld [de], a
inc de
dec b
jr nz, .copy_pp
.copied_pp
pop hl
.no_moves
jp .loop
ComputeTrainerReward:
ld hl, hProduct
xor a
ld [hli], a
ld [hli], a ; hMultiplicand + 0
ld [hli], a ; hMultiplicand + 1
ld a, [wEnemyTrainerBaseReward]
ld [hli], a ; hMultiplicand + 2
ld a, [wCurPartyLevel]
ld [hl], a ; hMultiplier
call Multiply
ld hl, wBattleReward
xor a
ld [hli], a
ldh a, [hProduct + 2]
ld [hli], a
ldh a, [hProduct + 3]
ld [hl], a
ret
Battle_GetTrainerName::
ld a, [wInBattleTowerBattle]
bit 0, a
ld hl, wOTPlayerName
ld a, BANK(@)
ld [wTrainerGroupBank], a
jp nz, CopyTrainerName
ld a, [wOtherTrainerID]
ld b, a
ld a, [wOtherTrainerClass]
ld c, a
; fallthrough
GetTrainerName::
ld a, c
cp CAL
jr nz, .not_cal2
ld a, BANK(sMysteryGiftTrainerHouseFlag)
call OpenSRAM
ld a, [sMysteryGiftTrainerHouseFlag]
and a
call CloseSRAM
jr z, .not_cal2
ld a, BANK(sMysteryGiftPartnerName)
call OpenSRAM
ld hl, sMysteryGiftPartnerName
call CopyTrainerName
jp CloseSRAM
.not_cal2
dec c
push bc
ld b, 0
ld hl, TrainerGroups
add hl, bc
add hl, bc
add hl, bc
ld a, [hli]
ld [wTrainerGroupBank], a
ld a, [hli]
ld h, [hl]
ld l, a
pop bc
.loop
dec b
jr z, .done
ld a, [wTrainerGroupBank]
call GetFarByte
add a, l
ld l, a
jr nc, .loop
inc h
jr .loop
.done
inc hl
; fallthrough
CopyTrainerName:
ld de, wStringBuffer1
push de
ld bc, NAME_LENGTH
ld a, [wTrainerGroupBank]
call FarCopyBytes
pop de
ret
IncompleteCopyNameFunction: ; unreferenced
; Copy of CopyTrainerName but without "call CopyBytes"
ld de, wStringBuffer1
push de
ld bc, NAME_LENGTH
pop de
ret
GetNextTrainerDataByte:
ld a, [wTrainerGroupBank]
call GetFarByte
inc hl
ret
INCLUDE "data/trainers/party_pointers.asm"

View file

@ -0,0 +1,19 @@
_ReturnToBattle_UseBall:
call ClearBGPalettes
call ClearTilemap
ld a, [wBattleType]
cp BATTLETYPE_TUTORIAL
jr z, .gettutorialbackpic
farcall GetBattleMonBackpic
jr .continue
.gettutorialbackpic
farcall GetTrainerBackpic
.continue
farcall GetEnemyMonFrontpic
farcall _LoadBattleFontsHPBar
call GetMemSGBLayout
call CloseWindow
call LoadStandardMenuHeader
call WaitBGMap
jp SetPalettes

View file

@ -0,0 +1,98 @@
BattleIntroSlidingPics:
ldh a, [rSVBK]
push af
ld a, BANK(wLYOverrides)
ldh [rSVBK], a
call .subfunction1
ld a, LOW(rSCX)
ldh [hLCDCPointer], a
call .subfunction2
xor a
ldh [hLCDCPointer], a
pop af
ldh [rSVBK], a
ret
.subfunction1
call .subfunction4
ld a, $90
ldh [hSCX], a
ld a, %11100100
call DmgToCgbBGPals
lb de, %11100100, %11100100
call DmgToCgbObjPals
ret
.subfunction2
ld d, $90
ld e, $72
ld a, $48
inc a
.loop1
push af
.loop2
ldh a, [rLY]
cp $60
jr c, .loop2
ld a, d
ldh [hSCX], a
call .subfunction5
inc e
inc e
dec d
dec d
pop af
push af
cp $1
jr z, .skip1
push de
call .subfunction3
pop de
.skip1
call DelayFrame
pop af
dec a
jr nz, .loop1
ret
.subfunction3
ld hl, wShadowOAMSprite00XCoord
ld c, $12 ; 18
ld de, SPRITEOAMSTRUCT_LENGTH
.loop3
dec [hl]
dec [hl]
add hl, de
dec c
jr nz, .loop3
ret
.subfunction4
ld hl, wLYOverrides
ld a, $90
ld bc, SCREEN_HEIGHT_PX
call ByteFill
ret
.subfunction5
ld hl, wLYOverrides
ld a, d
ld c, $3e ; 62
.loop4
ld [hli], a
dec c
jr nz, .loop4
ld a, e
ld c, $22 ; 34
.loop5
ld [hli], a
dec c
jr nz, .loop5
xor a
ld c, $30 ; 48
.loop6
ld [hli], a
dec c
jr nz, .loop6
ret

View file

@ -0,0 +1,199 @@
ShowLinkBattleParticipants:
; If we're not in a communications room,
; we don't need to be here.
ld a, [wLinkMode]
and a
ret z
farcall _ShowLinkBattleParticipants
ld c, 150
call DelayFrames
call ClearTilemap
call ClearSprites
ret
FindFirstAliveMonAndStartBattle:
xor a
ldh [hMapAnims], a
call DelayFrame
ld b, PARTY_LENGTH
ld hl, wPartyMon1HP
ld de, PARTYMON_STRUCT_LENGTH - 1
.loop
ld a, [hli]
or [hl]
jr nz, .okay
add hl, de
dec b
jr nz, .loop
.okay
ld de, MON_LEVEL - MON_HP
add hl, de
ld a, [hl]
ld [wBattleMonLevel], a
predef DoBattleTransition
farcall _LoadBattleFontsHPBar
ld a, 1
ldh [hBGMapMode], a
call ClearSprites
call ClearTilemap
xor a
ldh [hBGMapMode], a
ldh [hWY], a
ldh [rWY], a
ldh [hMapAnims], a
ret
PlayBattleMusic:
push hl
push de
push bc
xor a
ld [wMusicFade], a
ld de, MUSIC_NONE
call PlayMusic
call DelayFrame
call MaxVolume
ld a, [wBattleType]
cp BATTLETYPE_SUICUNE
ld de, MUSIC_SUICUNE_BATTLE
jp z, .done
cp BATTLETYPE_ROAMING
jp z, .done
; Are we fighting a trainer?
ld a, [wOtherTrainerClass]
and a
jr nz, .trainermusic
farcall RegionCheck
ld a, e
and a
jr nz, .kantowild
ld de, MUSIC_JOHTO_WILD_BATTLE
ld a, [wTimeOfDay]
cp NITE_F
jr nz, .done
ld de, MUSIC_JOHTO_WILD_BATTLE_NIGHT
jr .done
.kantowild
ld de, MUSIC_KANTO_WILD_BATTLE
jr .done
.trainermusic
ld de, MUSIC_CHAMPION_BATTLE
cp CHAMPION
jr z, .done
cp RED
jr z, .done
; BUG: Team Rocket battle music is not used for Executives or Scientists (see docs/bugs_and_glitches.md)
ld de, MUSIC_ROCKET_BATTLE
cp GRUNTM
jr z, .done
cp GRUNTF
jr z, .done
ld de, MUSIC_KANTO_GYM_LEADER_BATTLE
farcall IsKantoGymLeader
jr c, .done
; IsGymLeader also counts CHAMPION, RED, and the Kanto gym leaders
; but they have been taken care of before this
ld de, MUSIC_JOHTO_GYM_LEADER_BATTLE
farcall IsGymLeader
jr c, .done
ld de, MUSIC_RIVAL_BATTLE
ld a, [wOtherTrainerClass]
cp RIVAL1
jr z, .done
cp RIVAL2
jr nz, .othertrainer
ld a, [wOtherTrainerID]
cp RIVAL2_2_CHIKORITA ; Rival in Indigo Plateau
jr c, .done
ld de, MUSIC_CHAMPION_BATTLE
jr .done
.othertrainer
ld a, [wLinkMode]
and a
jr nz, .johtotrainer
farcall RegionCheck
ld a, e
and a
jr nz, .kantotrainer
.johtotrainer
ld de, MUSIC_JOHTO_TRAINER_BATTLE
jr .done
.kantotrainer
ld de, MUSIC_KANTO_TRAINER_BATTLE
.done
call PlayMusic
pop bc
pop de
pop hl
ret
ClearBattleRAM:
xor a
ld [wBattlePlayerAction], a
ld [wBattleResult], a
ld hl, wPartyMenuCursor
ld [hli], a
ld [hli], a
ld [hli], a
ld [hl], a
ld [wMenuScrollPosition], a
ld [wCriticalHit], a
ld [wBattleMonSpecies], a
ld [wBattleParticipantsNotFainted], a
ld [wCurBattleMon], a
ld [wForcedSwitch], a
ld [wTimeOfDayPal], a
ld [wPlayerTurnsTaken], a
ld [wEnemyTurnsTaken], a
ld [wEvolvableFlags], a
ld hl, wPlayerHPPal
ld [hli], a
ld [hl], a
ld hl, wBattleMonDVs
ld [hli], a
ld [hl], a
ld hl, wEnemyMonDVs
ld [hli], a
ld [hl], a
; Clear the entire BattleMons area
ld hl, wBattle
ld bc, wBattleEnd - wBattle
xor a
call ByteFill
callfar ResetEnemyStatLevels
call ClearWindowData
ld hl, hBGMapAddress
xor a ; LOW(vBGMap0)
ld [hli], a
ld [hl], HIGH(vBGMap0)
ret

View file

@ -0,0 +1,257 @@
BattleStart_TrainerHuds:
ld a, $e4
ldh [rOBP0], a
call LoadBallIconGFX
call ShowPlayerMonsRemaining
ld a, [wBattleMode]
dec a
ret z
jp ShowOTTrainerMonsRemaining
EnemySwitch_TrainerHud:
ld a, $e4
ldh [rOBP0], a
call LoadBallIconGFX
jp ShowOTTrainerMonsRemaining
ShowPlayerMonsRemaining:
call DrawPlayerPartyIconHUDBorder
ld hl, wPartyMon1HP
ld de, wPartyCount
call StageBallTilesData
; ldpixel wPlaceBallsX, 12, 12
ld a, 12 * 8
ld hl, wPlaceBallsX
ld [hli], a
ld [hl], a
ld a, 8
ld [wPlaceBallsDirection], a
ld hl, wShadowOAMSprite00
jp LoadTrainerHudOAM
ShowOTTrainerMonsRemaining:
call DrawEnemyHUDBorder
ld hl, wOTPartyMon1HP
ld de, wOTPartyCount
call StageBallTilesData
; ldpixel wPlaceBallsX, 9, 4
ld hl, wPlaceBallsX
ld a, 9 * 8
ld [hli], a
ld [hl], 4 * 8
ld a, -8
ld [wPlaceBallsDirection], a
ld hl, wShadowOAMSprite00 + PARTY_LENGTH * SPRITEOAMSTRUCT_LENGTH
jp LoadTrainerHudOAM
StageBallTilesData:
ld a, [de]
push af
ld de, wBattleHUDTiles
ld c, PARTY_LENGTH
ld a, $34 ; empty slot
.loop1
ld [de], a
inc de
dec c
jr nz, .loop1
pop af
ld de, wBattleHUDTiles
.loop2
push af
call .GetHUDTile
inc de
pop af
dec a
jr nz, .loop2
ret
.GetHUDTile:
ld a, [hli]
and a
jr nz, .got_hp
ld a, [hl]
and a
ld b, $33 ; fainted
jr z, .fainted
.got_hp
dec hl
dec hl
dec hl
ld a, [hl]
and a
ld b, $32 ; statused
jr nz, .load
dec b ; normal
jr .load
.fainted
dec hl
dec hl
dec hl
.load
ld a, b
ld [de], a
ld bc, PARTYMON_STRUCT_LENGTH + MON_HP - MON_STATUS
add hl, bc
ret
DrawPlayerHUDBorder:
ld hl, .tiles
ld de, wTrainerHUDTiles
ld bc, .tiles_end - .tiles
call CopyBytes
hlcoord 18, 10
ld de, -1 ; start on right
jr PlaceHUDBorderTiles
.tiles
db $73 ; right side
db $77 ; bottom right
db $6f ; bottom left
db $76 ; bottom side
.tiles_end
DrawPlayerPartyIconHUDBorder:
ld hl, .tiles
ld de, wTrainerHUDTiles
ld bc, .tiles_end - .tiles
call CopyBytes
hlcoord 18, 10
ld de, -1 ; start on right
jr PlaceHUDBorderTiles
.tiles
db $73 ; right side
db $5c ; bottom right
db $6f ; bottom left
db $76 ; bottom side
.tiles_end
DrawEnemyHUDBorder:
ld hl, .tiles
ld de, wTrainerHUDTiles
ld bc, .tiles_end - .tiles
call CopyBytes
hlcoord 1, 2
ld de, 1 ; start on left
call PlaceHUDBorderTiles
ld a, [wBattleMode]
dec a
ret nz
ld a, [wTempEnemyMonSpecies]
call CheckCaughtMon
ret z
hlcoord 1, 1
ld [hl], $5d
ret
.tiles
db $6d ; left side
db $74 ; bottom left
db $78 ; bottom right
db $76 ; bottom side
.tiles_end
PlaceHUDBorderTiles:
ld a, [wTrainerHUDTiles + 0]
ld [hl], a
ld bc, SCREEN_WIDTH
add hl, bc
ld a, [wTrainerHUDTiles + 1]
ld [hl], a
ld b, 8
.loop
add hl, de
ld a, [wTrainerHUDTiles + 3]
ld [hl], a
dec b
jr nz, .loop
add hl, de
ld a, [wTrainerHUDTiles + 2]
ld [hl], a
ret
LinkBattle_TrainerHuds:
call LoadBallIconGFX
ld hl, wPartyMon1HP
ld de, wPartyCount
call StageBallTilesData
ld hl, wPlaceBallsX
ld a, 10 * 8
ld [hli], a
ld [hl], 8 * 8
ld a, 8
ld [wPlaceBallsDirection], a
ld hl, wShadowOAMSprite00
call LoadTrainerHudOAM
ld hl, wOTPartyMon1HP
ld de, wOTPartyCount
call StageBallTilesData
ld hl, wPlaceBallsX
ld a, 10 * 8
ld [hli], a
ld [hl], 13 * 8
ld hl, wShadowOAMSprite00 + PARTY_LENGTH * SPRITEOAMSTRUCT_LENGTH
jp LoadTrainerHudOAM
LoadTrainerHudOAM:
ld de, wBattleHUDTiles
ld c, PARTY_LENGTH
.loop
ld a, [wPlaceBallsY]
ld [hli], a ; y
ld a, [wPlaceBallsX]
ld [hli], a ; x
ld a, [de]
ld [hli], a ; tile id
ld a, PAL_BATTLE_OB_YELLOW
ld [hli], a ; attributes
ld a, [wPlaceBallsX]
ld b, a
ld a, [wPlaceBallsDirection]
add b
ld [wPlaceBallsX], a
inc de
dec c
jr nz, .loop
ret
LoadBallIconGFX:
ld de, .gfx
ld hl, vTiles0 tile $31
lb bc, BANK(LoadBallIconGFX), 4
call Get2bppViaHDMA
ret
.gfx
INCBIN "gfx/battle/balls.2bpp"
_ShowLinkBattleParticipants:
call ClearBGPalettes
call LoadFontsExtra
hlcoord 2, 3
ld b, 9
ld c, 14
call Textbox
hlcoord 4, 5
ld de, wPlayerName
call PlaceString
hlcoord 4, 10
ld de, wOTPlayerName
call PlaceString
hlcoord 9, 8
ld a, "<BOLD_V>"
ld [hli], a
ld [hl], "<BOLD_S>"
farcall LinkBattle_TrainerHuds ; no need to farcall
ld b, SCGB_DIPLOMA
call GetSGBLayout
call SetPalettes
ld a, $e4
ldh [rOBP0], a
ret

View file

@ -0,0 +1,9 @@
_UpdateBattleHUDs:
farcall DrawPlayerHUD
ld hl, wPlayerHPPal
call SetHPPal
farcall DrawEnemyHUD
ld hl, wEnemyHPPal
call SetHPPal
farcall FinishBattleAnim
ret

View file

@ -0,0 +1,119 @@
DisplayUsedMoveText:
ld hl, UsedMoveText
call BattleTextbox
jp WaitBGMap
UsedMoveText:
text_far _ActorNameText
text_asm
ldh a, [hBattleTurn]
and a
jr nz, .start
ld a, [wPlayerMoveStruct + MOVE_ANIM]
call UpdateUsedMoves
.start
ld a, BATTLE_VARS_LAST_MOVE
call GetBattleVarAddr
ld d, h
ld e, l
ld a, BATTLE_VARS_LAST_COUNTER_MOVE
call GetBattleVarAddr
ld a, BATTLE_VARS_MOVE_ANIM
call GetBattleVar
ld [wTempByteValue], a
push hl
farcall CheckUserIsCharging
pop hl
jr nz, .ok
; update last move
ld a, [wTempByteValue]
ld [hl], a
ld [de], a
.ok
ld hl, UsedMoveInsteadText
ret
UsedMoveInsteadText:
text_far _UsedMoveText
text_asm
; check obedience
ld a, [wAlreadyDisobeyed]
and a
jr z, .GetMoveNameText
; print "instead,"
ld hl, .UsedInsteadText
ret
.UsedInsteadText:
text_far _UsedInsteadText
text_asm
.GetMoveNameText:
ld hl, MoveNameText
ret
MoveNameText:
text_far _MoveNameText
text_end
UpdateUsedMoves:
; append move a to wPlayerUsedMoves unless it has already been used
push bc
; start of list
ld hl, wPlayerUsedMoves
; get move id
ld b, a
; next count
ld c, NUM_MOVES
.loop
; get move from the list
ld a, [hli]
; not used yet?
and a
jr z, .add
; already used?
cp b
jr z, .quit
; next byte
dec c
jr nz, .loop
; if the list is full and the move hasn't already been used
; shift the list back one byte, deleting the first move used
; this can occur with struggle or a new learned move
ld hl, wPlayerUsedMoves + 1
; 1 = 2
ld a, [hld]
ld [hli], a
; 2 = 3
inc hl
ld a, [hld]
ld [hli], a
; 3 = 4
inc hl
ld a, [hld]
ld [hl], a
; 4 = new move
ld a, b
ld [wPlayerUsedMoves + 3], a
jr .quit
.add
; go back to the byte we just inced from
dec hl
; add the new move
ld [hl], b
.quit
; list updated
pop bc
ret