Erratic and Fluctuating experience growth rates - pret/pokecrystal GitHub Wiki

A Pokémon's growth rate determines how much experience it needs to reach a given level. Gen 1 and 2 each used four different growth rates, known as Fast, Slow, Medium Fast, and Medium Slow. (Gen 2 also has leftover formula data for two unused growth rates, "Slightly Fast" and "Slightly Slow".) The Fast rate reaches level 100 with just 800,000 experience, while the Slow rate needs 1,250,000.

Gen 3 introduced two new growth rates: Erratic, which needs a mere 600,000 experience to reach level 100; and Fluctuating, which needs 1,640,000. Neither of these rates is used by many Pokémon even in Gen 3; and beyond Gen 3, only Cranidos → Rampardos, Shieldon → Bastiodon, and Finneon → Lumineon are in the Erratic group, and only Drifloon → Drifblim are in the Fluctuating group.

The original growth rate formulas were all cubic polynomials, but the Erratic and Fluctuating formulas are more complicated piecewise quartic polynomials. It's simplest to do the calculating beforehand and store a lookup table of experience amounts for each level in the ROM (the same approach that Gen 3 took for every growth rate).

Contents

  1. Define growth rate constants
  2. Define lookup tables of experience amounts
  3. Look up experience amounts for a given level
  4. Fix a bank overflow error

1. Define growth rate constants

Edit constants/pokemon_data_constants.asm:

 ; wBaseGrowthRate values
 ; GrowthRates indexes (see data/growth_rates.asm)
 	const_def
 	const GROWTH_MEDIUM_FAST
 	const GROWTH_SLIGHTLY_FAST
 	const GROWTH_SLIGHTLY_SLOW
 	const GROWTH_MEDIUM_SLOW
 	const GROWTH_FAST
 	const GROWTH_SLOW
+	const GROWTH_ERRATIC
+	const GROWTH_FLUCTUATING
 DEF NUM_GROWTH_RATES EQU const_value

2. Define lookup tables of experience amounts

Edit data/growth_rates.asm:

 GrowthRates:
 ; entries correspond to GROWTH_* (see constants/pokemon_data_constants.asm)
 	table_width 4, GrowthRates
 	growth_rate 1, 1,   0,   0,   0 ; Medium Fast
 	growth_rate 3, 4,  10,   0,  30 ; Slightly Fast
 	growth_rate 3, 4,  20,   0,  70 ; Slightly Slow
 	growth_rate 6, 5, -15, 100, 140 ; Medium Slow
 	growth_rate 4, 5,   0,   0,   0 ; Fast
 	growth_rate 5, 4,   0,   0,   0 ; Slow
-	assert_table_length NUM_GROWTH_RATES
+	assert_table_length NUM_GROWTH_RATES - 2 ; exclude Erratic and Fluctuating
+
+ErraticExperience:
+	table_width 3, ErraticExperience
+	for n, 1, MAX_LEVEL + 1
+		if n == 1
+			dt 0
+		elif n < 50
+			dt (n**3 * (100 - n)) / 50
+		elif n < 68
+			dt (n**3 * (150 - n)) / 100
+		elif n < 98
+			dt (n**3 * ((1911 - 10 * n) / 3)) / 500
+		else
+			dt (n**3 * (160 - n)) / 100
+		endc
+	endr
+	assert_table_length MAX_LEVEL
+
+FluctuatingExperience:
+	table_width 3, FluctuatingExperience
+	for n, 1, MAX_LEVEL + 1
+		if n == 1
+			dt 0
+		elif n < 15
+			dt (n**3 * ((n + 1) / 3 + 24)) / 50
+		elif n < 36
+			dt (n**3 * (n + 14)) / 50
+		else
+			dt (n**3 * (n / 2 + 32)) / 50
+		endc
+	endr
+	assert_table_length MAX_LEVEL

The values in these tables should match the ones from src/data/pokemon/experience_tables.h in the pokeruby disassembly. We do the computation with macros at assembly time, instead of dynamically calculating while the game runs.

3. Look up experience amounts for a given level

Edit engine/pokemon/experience.asm:

 CalcExpAtLevel:
 ; (a/b)*n**3 + c*n**2 + d*n - e
 	ld a, [wBaseGrowthRate]
+	cp GROWTH_ERRATIC
+	jp z, .erratic
+	cp GROWTH_FLUCTUATING
+	jp z, .fluctuating
 	add a
 	add a
 	ld c, a
 	ld b, 0
 	ld hl, GrowthRates
 	add hl, bc
 ; Cube the level
 	call .LevelSquared
 	ld a, d
 	ldh [hMultiplier], a
 	call Multiply

 	...

 .LevelSquared:
 	xor a
 	ldh [hMultiplicand + 0], a
 	ldh [hMultiplicand + 1], a
 	ld a, d
 	ldh [hMultiplicand + 2], a
 	ldh [hMultiplier], a
 	jp Multiply
+
+.erratic:
+	ld hl, ErraticExperience
+	jr .lookup_table
+
+.fluctuating:
+	ld hl, FluctuatingExperience
+.lookup_table
+	ld c, d
+	ld b, 0
+	dec c
+	add hl, bc
+	add hl, bc
+	add hl, bc
+	xor a
+	ldh [hProduct + 0], a
+	ld a, [hli]
+	ldh [hProduct + 1], a
+	ld a, [hli]
+	ldh [hProduct + 2], a
+	ld a, [hli]
+	ldh [hProduct + 3], a
+	ret

 INCLUDE "data/growth_rates.asm"

4. Fix a bank overflow error

We're done implementing the new growth rates, but make won't compile the ROM:

Section 'bank14' is too big (max size = 0x4000 bytes).

It turns out that the lookup tables overflowed their ROM bank, so we need to move some of content from bank14 to a different bank, or delete some. In fact, there is some unused content in bank14 that can be deleted to free up space.

Edit main.asm:

 SECTION "bank14", ROMX

 INCLUDE "engine/pokemon/party_menu.asm"
 INCLUDE "engine/events/poisonstep.asm"
 INCLUDE "engine/events/sweet_scent.asm"
 INCLUDE "engine/events/squirtbottle.asm"
 INCLUDE "engine/events/card_key.asm"
 INCLUDE "engine/events/basement_key.asm"
 INCLUDE "engine/events/sacred_ash.asm"
 INCLUDE "engine/pokemon/tempmon.asm"
 INCLUDE "engine/pokemon/types.asm"
-INCLUDE "engine/battle/unreferenced_getgen1trainerclassname.asm"
 INCLUDE "engine/pokemon/mon_stats.asm"
 INCLUDE "engine/link/init_list.asm"
 INCLUDE "engine/pokemon/experience.asm"
 INCLUDE "engine/pokemon/switchpartymons.asm"
 INCLUDE "engine/gfx/load_pics.asm"
 INCLUDE "engine/pokemon/move_mon_wo_mail.asm"
 INCLUDE "data/pokemon/base_stats.asm"
 INCLUDE "data/pokemon/names.asm"
-INCLUDE "data/pokemon/unused_pic_banks.asm"
-
-UnknownEggPic::
-; Another egg pic. This is shifted up a few pixels.
-INCBIN "gfx/unknown/unknown_egg.2bpp.lz"

Now edit home/mon_data.asm to remove the pointless use of UnknownEggPic:

 .egg
-; ????
-	ld de, UnknownEggPic
-
 ; Sprite dimensions
 	ld b, $55 ; 5x5
 	ld hl, wBasePicSize
 	ld [hl], b
-
-; ????
-	ld hl, wBasePadding
-	ld [hl], e
-	inc hl
-	ld [hl], d
-	inc hl
-	ld [hl], e
-	inc hl
-	ld [hl], d
-	jr .end

And finally delete engine/battle/unreferenced_getgen1trainerclassname.asm, data/pokemon/unused_pic_banks.asm, gfx/unknown/unknown_egg.png, and gfx/unknown/unknown_egg.2bpp.lz.a5b6cbfa.

Now you'll be able to use the constants GROWTH_ERRATIC and GROWTH_FLUCTUATING when defining new Pokémon in data/pokemon/base_stats.