-
Notifications
You must be signed in to change notification settings - Fork 830
Badge Level Caps
Part of making a Pokemon game competitive is removing the ability for players to over-level and brute force their way to a win. If you're interested in making level caps for player's Pokemon based on which badges they've obtained, this is your tutorial. Let's get started.
- Badge Level Caps File
- Import the Badge Level Caps File
- Update Rare Candy Routine
- Update Battle Engine
- Update Daycare
First, we need to make a new file that will be the source of truth for our level caps. This file will store the level caps by badge and also allow retrieval via a subroutine. I made the file at: engine/pokemon/get_max_level.asm
.
SECTION "Reserved Bytes", ROMX
ds 4 ; reserve 4 bytes
GetMaxLevel:
ld hl, wKantoBadges
bit EARTHBADGE, [hl]
ld a, 8
jr nz, .exit
ld hl, wJohtoBadges
bit RISINGBADGE, [hl]
ld a, MAX_LEVEL + 1
jr nz, .exit
; stormbadge
bit STORMBADGE, [hl]
ld a, 70
jr nz, .exit
; fogbadge
bit FOGBADGE, [hl]
ld a, 50
jr nz, .exit
; hivebadge
bit HIVEBADGE, [hl]
ld a, 10
jr nz, .exit
; no badges
ld a, 5
.exit
ld b, a
ret
This file does not contain all the badges at this point because it's up to the game designer which badges to include in this list. You can include all 16 badges here.
Here's how the routine works.
GetMaxLevel:
; omitted code
bit HIVEBADGE, [hl]
ld a, 10
jr nz, .exit
; no badges
ld a, 5
.exit
ld b, a
ret
In this code snippet, we check the bit
for HIVEBADGE
first. This is simply checking for 0 or 1, which means: is the badge flag enabled (badge received by player)? If it is, we jump to the .exit
routine with 10
loaded into the a
register.
If the HIVEBADGE wasn't obtained yet, the execution of the routine would go to ld a, 5
next, which would make the level cap 5
, instead of 10
with the HIVEBADGE.
So, define the badge level caps in descending order (descending in terms of when the player receives them in the game). A hypothetical order would be EARTHBADGE, then VOLCANOBADGE, then the rest of the Kanto gyms, and then the Johto gyms.
This is the simplest step. Go to the file main.asm
and go down to the last SECTION
titled "Crystal Events"
. Beneath that and its imports, make a new section and import the file.
+SECTION "Level Caps", ROMX
+
+INCLUDE "engine/pokemon/get_max_level.asm"
+
+
SECTION "Stadium 2 Checksums", ROMX[$7DE0], BANK[$7F]
Now we can call the subroutine GetMaxLevel
from anywhere in our code using callfar GetMaxLevel
.
Rare candy leveling up should respect badge level caps. To do so, we need to change the routine for how rare candies are used.
In the file engine/items/item_effects.asm
go to the label RareCandyEffect
. There, modify the code as follows:
RareCandyEffect:
ld b, PARTYMENUACTION_HEALING_ITEM
call UseItem_SelectMon
jp c, RareCandy_StatBooster_ExitMenu
call RareCandy_StatBooster_GetParameters
ld a, MON_LEVEL
call GetPartyParamLocation
+ push hl
+ farcall GetMaxLevel
+ pop hl
ld a, [hl]
- cp MAX_LEVEL
+ cp b
jp nc, NoEffectMessage
Instead of comparing to the constant MAX_LEVEL
(which is 100) we are using GetMaxLevel
to get the current level cap for the latest badge the player obtained.
This is the most complicated part of the tutorial. Most leveling up occurs during battle and we need to make a lot of edits to the battle engine to respect dynamic level caps.
Go to the file engine/battle/core.asm
. Make the following changes:
@@ -6973,6 +6973,18 @@ GiveExperiencePoints:
inc de
dec c
jr nz, .stat_exp_loop
+ pop bc
+ ld hl, MON_LEVEL
+ add hl, bc
+ push bc
+ push hl
+ callfar GetMaxLevel
+ pop hl
+ ld a, [hl]
+ cp b
+ pop bc
+ jp nc, .next_mon
+ push bc
xor a
ldh [hMultiplicand + 0], a
ldh [hMultiplicand + 1], a
@@ -7062,7 +7074,8 @@ GiveExperiencePoints:
ld [wCurSpecies], a
call GetBaseData
push bc
- ld d, MAX_LEVEL
+ callfar GetMaxLevel
+ ld d, b
callfar CalcExpAtLevel
pop bc
ld hl, MON_EXP + 2
@@ -7089,8 +7102,9 @@ GiveExperiencePoints:
ld [hld], a
.not_max_exp
-; Check if the mon leveled up
- xor a ; PARTYMON
+ call GetMaxLevel
+ ld e, b
+ xor a
ld [wMonType], a
predef CopyMonToTempMon
callfar CalcLevel
@@ -7098,7 +7112,7 @@ GiveExperiencePoints:
ld hl, MON_LEVEL
add hl, bc
ld a, [hl]
- cp MAX_LEVEL
+ cp e
jp nc, .next_mon
cp d
jp z, .next_mon
@@ -7268,12 +7282,33 @@ GiveExperiencePoints:
ld a, [wBattleParticipantsNotFainted]
ld b, a
ld c, PARTY_LENGTH
- ld d, 0
+ ld de, 0
.count_loop
+ push bc
+ push de
+ callfar GetMaxLevel
+ ld a, b
+ ld [wTempByteValue], a
+ ld a, e
+ ld hl, wPartyMon1Level
+ call GetPartyLocation
+ ld a, [wTempByteValue]
+ ld b, a
+ ld a, [hl]
+ cp b
+ pop de
+ pop bc
+ jr c, .gains_exp
+ srl b
+ ld a, d
+ jr .no_exp
+.gains_exp
xor a
srl b
adc d
ld d, a
+.no_exp
+ inc e
dec c
jr nz, .count_loop
cp 2
@@ -7339,13 +7374,16 @@ ExpPointsText:
AnimateExpBar:
push bc
+ callfar GetMaxLevel
+ ld e, b
+
ld hl, wCurPartyMon
ld a, [wCurBattleMon]
cp [hl]
jp nz, .finish
ld a, [wBattleMonLevel]
- cp MAX_LEVEL
+ cp e
jp nc, .finish
ldh a, [hProduct + 3]
@@ -7382,7 +7420,10 @@ AnimateExpBar:
ld [hl], a
.NoOverflow:
- ld d, MAX_LEVEL
+ callfar GetMaxLevel
+ ld d, b
+ pop bc
+ push bc
callfar CalcExpAtLevel
ldh a, [hProduct + 1]
ld b, a
@@ -7417,8 +7458,17 @@ AnimateExpBar:
ld d, a
.LoopLevels:
+ push bc
+ callfar GetMaxLevel
+ ld a, b
+ ld [wTempByteValue], a
+ pop bc
+
+ ld a, [wTempByteValue]
+ ld l, a
+
ld a, e
- cp MAX_LEVEL
+ cp l
jr nc, .FinishExpBar
cp d
jr z, .FinishExpBar
The essential idea of these changes is to replace the constant MAX_LEVEL
with the dynamic value from GetMaxLevel
. We have to make a bunch of changes here to ensure that we don't overwrite any of the registers used for other parts of the battle routine.
If you are encountering errors here, you probably mistyped something and a register is being overwritten that shouldn't be. Double check that your diff matches the diff in this tutorial.
The daycare is an easy update. We are just changing the MAX_LEVEL
check to be our dynamic value returned from GetMaxLevel
.
Go to the file engine/events/happiness_egg.asm
. Match the following diff:
@@ -146,8 +146,9 @@ DayCareStep::
bit DAYCAREMAN_HAS_MON_F, a
jr z, .day_care_lady
+ callfar GetMaxLevel
ld a, [wBreedMon1Level] ; level
- cp MAX_LEVEL
+ cp b
jr nc, .day_care_lady
ld hl, wBreedMon1Exp + 2 ; exp
inc [hl]
@@ -168,8 +169,9 @@ DayCareStep::
bit DAYCARELADY_HAS_MON_F, a
jr z, .check_egg
+ callfar GetMaxLevel
ld a, [wBreedMon2Level] ; level
- cp MAX_LEVEL
+ cp b
jr nc, .check_egg
ld hl, wBreedMon2Exp + 2 ; exp
inc [hl]
That's it! Let me know if you have any questions.