Level cap - pret/pokecrystal GitHub Wiki

This tutorial guides you through changing the max level cap. In order to achieve this you'll add a new variable, modify the level cap logic, and optionally remove gaining experience once a Pokémon has reached max level.

Contents

  1. Add a level cap variable and initialize it
  2. Modify the battle engine's level cap logic
  3. Make Pokémon not level up at the Day Care after reaching new level cap
  4. Make Rare Candy ineffective after reaching new level cap
  5. (Optional) Stop gaining experience at max level
  6. Adjust the level cap depending on some conditions
  7. Other notes about the level cap

1. Add a level cap variable and initialize it

First of all, we need to create the variable to store the new level cap. For that we'll edit ram/wram.asm and replace one of the many unused bytes; in this case we can choose one of the unused fight counts for rematchable trainers:

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

And now let's initialize it. We can give it an initial value whenever we start a new game, and for that we'll edit engine/menus/intro_menu.asm. In this example we'll set it to Lv. 50:

 _ResetWRAM:
 	...
 	ld hl, wMomItemTriggerBalance
 	ld [hl], HIGH(MOM_MONEY >> 8)
 	inc hl
 	ld [hl], HIGH(MOM_MONEY) ; mid
 	inc hl
 	ld [hl], LOW(MOM_MONEY)
 
+	ld a, 50
+	ld [wLevelCap], a
 
 	call InitializeNPCNames
 
 	farcall InitDecorations
 	...

The level cap has been set, but now we'll have to adjust the game's logic to work with it.

2. Modify the battle engine's level cap logic

We'll have to edit multiple files to accept our new custom level cap. First, let's go to engine/pokemon/experience.asm:

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

And in engine/battle/core.asm:

 .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
+	ld a, [wLevelCap]
+	ld d, a
	callfar CalcExpAtLevel

	...

 .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 MAX_LEVEL
+	cp b
+	pop bc
	jp nc, .next_mon
 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 MAX_LEVEL
+	cp b
+	pop bc
	jp nc, .finish

	...

 .NoOverflow:
-	ld d, MAX_LEVEL
+	ld a, [wLevelCap]
+	ld d, a
	callfar CalcExpAtLevel
	ldh a, [hProduct + 1]
	ld b, 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

3. Make Pokémon not level up at the Day Care after reaching new level cap

Our Pokémon won't level up past the cap while battling, but they will if they're deposited in the Day Care, so we'll need to also adjust its logic. Edit engine/events/happiness_egg.asm:

 DayCareStep::
 ; Raise the experience of Day-Care Pokémon every step cycle.

	ld a, [wDayCareMan]
	bit DAYCAREMAN_HAS_MON_F, a
	jr z, .day_care_lady

+	ld a, [wLevelCap]
+	ld b, a
	ld a, [wBreedMon1Level] ; level
-	cp MAX_LEVEL
+	cp b
	jr nc, .day_care_lady
	
	...

 .day_care_lady
	ld a, [wDayCareLady]
	bit DAYCARELADY_HAS_MON_F, a
	jr z, .check_egg

	ld a, [wBreedMon2Level] ; level
-	cp MAX_LEVEL
+	cp b
	jr nc, .check_egg

4. Make Rare Candy ineffective after reaching new level cap

But aren't we forgetting about another way to level up? That's right, rare candies! They also need to be adjusted to work with our new level cap. Go to engine/items/item_effects.asm:

 RareCandyEffect:
	ld b, PARTYMENUACTION_HEALING_ITEM
	call UseItem_SelectMon

	jp c, RareCandy_StatBooster_ExitMenu

	call RareCandy_StatBooster_GetParameters

	ld a, MON_LEVEL
	call GetPartyParamLocation

+	ld a, [wLevelCap]
+	ld b, a
	ld a, [hl]
-	cp MAX_LEVEL
+	cp b
	jp nc, NoEffectMessage

5. (Optional) Stop gaining experience at max level

You'll notice that our max level Pokémon still get accounted for when dividing the experience among the battle participants, even though they can't gain more experience. If you want to modify this you'll have to go to engine/battle/core.asm and change how it works:

 GiveExperiencePoints:

	...

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

	...

 .EvenlyDivideExpAmongParticipants:
 ; count number of battle participants
	ld a, [wBattleParticipantsNotFainted]
	ld b, a
	ld c, PARTY_LENGTH
-	ld d, 0
+	ld de, 0
 .count_loop
+	push bc
+	push de
+	ld a, e
+	ld hl, wPartyMon1Level
+	call GetPartyLocation
+	ld a, [wLevelCap]
+	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

6. Adjust the level cap depending on some conditions

Now we have a functional level cap! What's left is to learn how to change it according to our needs. You can modify the level cap in any map script by using the loadmem command, which takes two parameters: the memory address and the value to load into it.

For example, if you want to increase the level cap from 50 to 60 after beating Falkner, the Gym Leader of Violet City, you can do it this way:

 VioletGymFalknerScript:
 	...
 .FightDone:
	checkevent EVENT_GOT_TM31_MUD_SLAP
	iftrue .SpeechAfterTM
+	loadmem wLevelCap, 60
	setevent EVENT_BEAT_BIRD_KEEPER_ROD
	setevent EVENT_BEAT_BIRD_KEEPER_ABE
	...

Which events or actions to use to modify the level cap are up to you!

7. Other notes about the level cap

In our example we adjusted the level cap to be Lv. 50, but we could change it to any value ranging from 1 to 255. However, if you change it to high values such as 255 itself then you'll have to modify other parts in the code. For example, there are instances which take the level cap as an input and the output is expected to be an 8-bit value. This tutorial won't cover those cases, but this warning is placed for those who want to venture into it.