Skip to content

Implement move priority system

Xillicis edited this page Jun 29, 2023 · 3 revisions

Introduction

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

  1. COUNTER: -6
  2. QUICK_ATTACK: 1
  3. 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.

Priority Move List

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).

Battle Engine Changes

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.

Clone this wiki locally