-
Notifications
You must be signed in to change notification settings - Fork 817
Level cap
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.
- Add a level cap variable and initialize it
- Modify the battle engine's level cap logic
- Make Pokémon not level up at the Day Care after reaching new level cap
- Make Rare Candy ineffective after reaching new level cap
- (Optional) Stop gaining experience at max level
- Adjust the level cap depending on some conditions
- Other notes about the level cap
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.
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
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
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
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
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!
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.