Level cap - nickjwilde/pokecrystal GitHub Wiki

This tutorial guides you through changing the max level. You will add a new variable, modify level cap logic, and optionally remove gaining exp. once a pokemon is the max level.

TODO: Investigate wether one is able to set the level cap by modifying MAX_LEVEL EQU [0-255] in constants/battle_constants.asm.

1. Add Level Cap variable

First, we want to add a wLevelCap variable to wram.asm in the root folder. In wram.asm, go to line 2668, wBeverlyFightCount:: db ; unused. Since this byte is unused, you can replace it. Replace this line with wLevelCap:: db. This is the value we'll be checking for the level cap

; fight counts
wJackFightCount::    db ; d9f2
- wBeverlyFightCount:: db ; unused
+ wLevelCap:: db
wHueyFightCount::    db
wGavenFightCount::   db

To set the level cap, write this in a script: loadmem wLevelCap, [0-255] ; number from 0 to 255 representing what your level cap should be

2. Replace first level cap check

In engine/pokemon/experience.asm, go to line 9: cp LOW(MAX_LEVEL + 1). This is what we first need to change. Before the ld a, d on the line before it, add code to load the new level cap into register b.

.next_level
	inc d
+	ld a, [wLevelCap]
+	inc a
+	push bc
+	ld b, a
	ld a, d
+	cp b
+	pop bc
	cp LOW(MAX_LEVEL + 1)
	jr z, .got_level
	call CalcExpAtLevel

3. Replace the rest of the cap checks

There are now five more instances of MAX_LEVEL that you'll need to change. First, go to the .no_exp_overflow label located in engine/battle/core.asm. Instead of storing the MAX_LEVEL value, we load our custom max level.

.no_exp_overflow
	ld a, [wCurPartyMon]
	ld e, a
	ld d, 0
	ld hl, wPartySpecies
	add hl, de
	ld a, [hl]
	ld [wCurSpecies], a
	call GetBaseData
	push bc
-	ld d, MAX_LEVEL
+	push af
+	ld a, [wLevelCap]
+	ld d, a
+	pop af
	callfar CalcExpAtLevel

Next, ctrl + f for the next instance of MAX_LEVEL, located in the .not_max_exp label. You'll notice that the line before this reads ld a, [hl] Before this line, load the level cap into register b. Then, after ld a, [hl], add a comparison and restore the bc register. Here we use the comparison to control the jump.

.not_max_exp
; Check if the mon leveled up
	xor a ; PARTYMON
	ld [wMonType], a
	predef CopyMonToTempMon
	callfar CalcLevel
	pop bc
	ld hl, MON_LEVEL
	add hl, bc
+	ld a, [wLevelCap]
+	push bc
+	ld b, a
	ld a, [hl]
+	cp b
+	pop bc
-	cp MAX_LEVEL
	jp nc, .next_mon

Third, ctrl + f for the next instance of MAX_LEVEL, located in the AnimateExpBar label. You'll notice there's a ld a, [wBattleMonLevel] before it. Before this, add

AnimateExpBar:
	push bc

	ld hl, wCurPartyMon
	ld a, [wCurBattleMon]
	cp [hl]
	jp nz, .finish

+	ld a, [wLevelCap]
+	push bc
+	ld b, a
	ld a, [wBattleMonLevel]
+	cp b
+	pop bc
-	cp MAX_LEVEL
	jp nc, .finish

Fourth, ctrl + f for the next instance of MAX_LEVEL. Replace ld d, MAX_LEVEL with loading the custom max level.

	ld [hli], a
	ld [hl], a

.NoOverflow:
+	push af
+	ld a, [wLevelCap]
+	ld d, a
+	pop af
	callfar CalcExpAtLevel
	ldh a, [hProduct + 1]
	ld b, a

Fifth, ctrl + f for the final instance of MAX_LEVEL. You'll notice before it reads ld a, e. Before this, lod the level cap into b, then do the comparison on that.

	ld a, e
	ld d, a

.LoopLevels:
+	ld a, [wLevelCap]
+	push bc
+	ld b, a
	ld a, e
-	cp MAX_LEVEL
+	cp b
+	pop bc
	jr nc, .FinishExpBar
	cp d
	jr z, .FinishExpBar
	inc a

Now you're done with replacing the max level checks with the level cap checks.

4. Optional: Remove exp. gain text at max level

You'll notice that the exp. gain text still displays when you reach the level cap. To remove this, go to line 7059 in engine/battle/core.asm, which should be about the lines of

.stat_exp_awarded
	inc de
	inc de
	dec c
	jr nz, .stat_exp_loop
+	pop bc
+	ld hl, MON_LEVEL
+	add hl, bc
+	ld a, [wLevelCap]
+	push bc
+	ld b, a
+	ld a, [hl]
+	cp b
+	pop bc
+	jp nc, .next_mon
+	push bc
	xor a
	ldh [hMultiplicand + 0], a
	ldh [hMultiplicand + 1], a
	ld a, [wEnemyMonBaseExp]

Next, go to

.EvenlyDivideExpAmongParticipants:
; count number of battle participants
	ld a, [wBattleParticipantsNotFainted]
	ld b, a
	ld c, PARTY_LENGTH
	ld d, 0
.count_loop
+	push bc
+	push de
+	ld a, [wLevelCap]
+	ld b, a
+	ld a, [wPartyCount]
+	cp c
+	jr c, .no_mon
+	ld a, c
+	dec a
+	ld hl, wPartyMon1Level
+	call GetPartyLocation
+	ld a, [hl]
+.no_mon
+	cp b
+	pop de
+	pop bc
+	jr nz, .gains_exp
+	srl b
+	ld a, d
+	jr .no_exp
+.gains_exp
	xor a
	srl b
	adc d
	ld d, a
+.no_exp
	dec c
	jr nz, .count_loop