-
Notifications
You must be signed in to change notification settings - Fork 995
Implement move priority system
This tutorial is for implementing the priority system introduced in
future generations of Pokemon. In Pokemon Red/Blue/Yellow, there are
only two moves which have a priority effect: QUICK_ATTACK
and
COUNTER
. QUICK_ATTACK
always goes first and COUNTER
always goes
last. However in later generations, new moves were added which have a
numeric priority value indicating which move is to be used first. It
ranges from -7 to +5 where 0 is the base priority.
DISCLAIMER: Modifications to the battle engine can introduce unintended consequences. If you discover any issues with the following modifications, please let me know on Discord (@Xillicis) or offer your own fix here in this tutorial.
The scope of this tutorial is to introduce an base priority
structure to offer easy implementation of priority moves. For simplicity, we
demonstrate with just one additional move, EXTREME_SPEED
. If you
haven't done so already go ahead and implement EXTREME_SPEED
by
following this tutorial: add a new move
and give it the move effect NO_ADDITIONAL_EFFECT
.
The priority values for these three moves are
-
COUNTER
: -6 -
QUICK_ATTACK
: 1 -
EXTREME_SPEED
: 2 (from Gen V and on)
To avoid confusion with negative numbers in assembly, we will shift the scale from 0 to 12, with 7 being the base priority.
The first thing we do is introduce a new file priority_moves.asm
in
the data/battle/
directory with the following,
; Since Generation IV, the move priority ranges from -7 to 5.
; To avoid working with negative numbers, we shift the priority
; range to be from 0 to 12.
PriorityMovesList:
db COUNTER, 1
db QUICK_ATTACK, 8
db EXTREME_SPEED, 9
db -1 ; end
The move names are the move IDs and the second column contains the priority values (in the shifted scale).
Next, open up engine/battle/core.asm and add the following
subroutine at the end of the subroutine MainInBattleLoop
and before HandlePoisonBurnLeechSeed
.
...
call CheckNumAttacksLeft
jp MainInBattleLoop
+HandleMovePriority:
+; This subroutine modifies registers a, hl, bc, and de
+; The player's priority value will be stored in register c and
+; the enemy's priority value will be stored in register e.
+; These values will be compared after the 'ret' instruction is called
+
+ ld a, [wPlayerSelectedMove]
+ ld b, a
+ ld hl, PriorityMovesList
+ ld c, 7 ; no priority is 7
+.playerPriorityMoveLoop
+ ld a, [hli] ; load the move ID from priority list and
+ ; increment address to the priority value address
+ cp b ; compare with move being used
+ jr z, .playerUsingPriorityMove
+ inc a ; if at end of list: -1 + 1 = 0xFF + 0x01 = 0
+ jr z, .noPlayerPriorityMove
+ inc hl ; increment address to the next move
+ jr .playerPriorityMoveLoop
+.playerUsingPriorityMove
+ ld c, [hl] ; get new priority value
+.noPlayerPriorityMove
+
+; Now check enemy priority
+ ld a, [wEnemySelectedMove]
+ ld d, a
+ ld hl, PriorityMovesList
+ ld e, 7 ; no priority is 7
+.enemyPriorityMoveLoop
+ ld a, [hli] ; load the move ID from priority list and
+ ; increment address to the priority value address
+ cp d ; compare with move being used
+ jr z, .enemyUsingPriorityMove
+ inc a ; if at end of list: -1 + 1 = 0xFF + 0x01 = 0
+ jr z, .noEnemyPriorityMove
+ inc hl ; increment address to the next move
+ jr .enemyPriorityMoveLoop
+.enemyUsingPriorityMove
+ ld e, [hl] ; get new priority value
+.noEnemyPriorityMove
+ ret
+
+INCLUDE "data/battle/priority_moves.asm"
HandlePoisonBurnLeechSeed:
...
I've provided some comments in the code to help explain what's happening.
Next, let's perform the comparison in MainInBattleLoop
. Go back up a
little bit and make the following changes,
...
.noLinkBattle
- ld a, [wPlayerSelectedMove]
- cp QUICK_ATTACK
- jr nz, .playerDidNotUseQuickAttack
- ld a, [wEnemySelectedMove]
- cp QUICK_ATTACK
- jr z, .compareSpeed ; if both used Quick Attack
- jp .playerMovesFirst ; if player used Quick Attack and enemy didn't
-.playerDidNotUseQuickAttack
- ld a, [wEnemySelectedMove]
- cp QUICK_ATTACK
- jr z, .enemyMovesFirst ; if enemy used Quick Attack and player didn't
- ld a, [wPlayerSelectedMove]
- cp COUNTER
- jr nz, .playerDidNotUseCounter
- ld a, [wEnemySelectedMove]
- cp COUNTER
+ call HandleMovePriority
+ ; c = player priority, e = enemy priority
+ ld a, c
+ cp e
jr z, .compareSpeed ; if both used Counter
- jr .enemyMovesFirst ; if player used Counter and enemy didn't
+ jr c, .enemyMovesFirst
+ jr .playerMovesFirst
-.playerDidNotUseCounter
- ld a, [wEnemySelectedMove]
- cp COUNTER
- jr z, .playerMovesFirst ; if enemy used Counter and player didn't
.compareSpeed
ld de, wBattleMonSpeed ; player speed value
ld hl, wEnemyMonSpeed ; enemy speed value
...
That's it. There are a few remarks to be had. If you want to implement
a new priority move, you just need to add it into the file with its
corresponding priority value in data/battle/priority_moves.asm
. The
order of the bytes in this file doesn't matter, I personally just
sorted it by priority order.