Skip to content

Add a fourth stats page

AustinN10 edited this page May 1, 2022 · 10 revisions

This tutorial is for how to add a fourth page to Pokémon stats. As an example, we'll make an orange page after the pink, green, and blue ones.

(The code for this feature was adapted from i-am-the-pokeman.)

Contents

  1. Define the fourth page's colors
  2. Load and apply the fourth page's palette
  3. Display the fourth page's content
  4. Account for the shifted page indexes
  5. Show Caught Data on the fourth page

1. Define the fourth page's colors

Edit gfx/stats/pages.pal:

 ; pink
 	RGB 31, 31, 31
 	RGB 31, 19, 31
 	RGB 31, 15, 31
 	RGB 00, 00, 00
 ; green
 	RGB 31, 31, 31
 	RGB 21, 31, 14
 	RGB 17, 31, 00
 	RGB 00, 00, 00
 ; blue
 	RGB 31, 31, 31
 	RGB 17, 31, 31
 	RGB 17, 31, 31
 	RGB 00, 00, 00
+; orange
+	RGB 31, 31, 31
+	RGB 30, 24, 16
+	RGB 30, 22, 12
+	RGB 00, 00, 00

And edit gfx/stats/stats.pal:

 ; pink
 	RGB 31, 19, 31
 ; green
 	RGB 21, 31, 14
 ; blue
 	RGB 17, 31, 31
+; orange
+	RGB 30, 24, 16

RGB colors define 5-bit red, green, and blue channels, each from 0 to 31. (On a PC, typically 8-bit color channels go from 0 to 255, or in hex 00 to FF. Just multiply or divide by 8 to convert between them; e.g. RGB 30, 24, 16 looks like #F0C080.)

Both of these files sort the pages in order from left to right.

2. Load and apply the fourth page's palette

Edit engine/gfx/cgb_layouts.asm:

 _CGB_StatsScreenHPPals:
 	ld de, wBGPals1
 	ld a, [wCurHPPal]
 	ld l, a
 	ld h, $0
 	add hl, hl
 	add hl, hl
 	ld bc, HPBarPals
 	add hl, bc
 	call LoadPalette_White_Col1_Col2_Black ; hp palette
 	ld a, [wCurPartySpecies]
 	ld bc, wTempMonDVs
 	call GetPlayerOrMonPalettePointer
 	call LoadPalette_White_Col1_Col2_Black ; mon palette
 	ld hl, ExpBarPalette
 	call LoadPalette_White_Col1_Col2_Black ; exp palette
 	ld hl, StatsScreenPagePals
 	ld de, wBGPals1 palette 3
-	ld bc, 3 palettes ; pink, green, and blue page palettes
+	ld bc, 4 palettes ; pink, green, blue, and orange page palettes
 	ld a, BANK(wBGPals1)
 	call FarCopyWRAM
 	call WipeAttrmap

 	hlcoord 0, 0, wAttrmap
 	lb bc, 8, SCREEN_WIDTH
 	ld a, $1 ; mon palette
 	call FillBoxCGB

 	hlcoord 10, 16, wAttrmap
 	ld bc, 10
 	ld a, $2 ; exp palette
 	call ByteFill

-	hlcoord 13, 5, wAttrmap
+	hlcoord 11, 5, wAttrmap
 	lb bc, 2, 2
 	ld a, $3 ; pink page palette
 	call FillBoxCGB

-	hlcoord 15, 5, wAttrmap
+	hlcoord 13, 5, wAttrmap
 	lb bc, 2, 2
 	ld a, $4 ; green page palette
 	call FillBoxCGB

-	hlcoord 17, 5, wAttrmap
+	hlcoord 15, 5, wAttrmap
 	lb bc, 2, 2
 	ld a, $5 ; blue page palette
 	call FillBoxCGB
+
+	hlcoord 17, 5, wAttrmap
+	lb bc, 2, 2
+	ld a, $6 ; orange page palette
+	call FillBoxCGB

 	call ApplyAttrmap
 	call ApplyPals
 	ld a, $1
 	ldh [hCGBPalUpdate], a
 	ret

 StatsScreenPagePals:
 INCLUDE "gfx/stats/pages.pal"

 StatsScreenPals:
 INCLUDE "gfx/stats/stats.pal"

Here we load the new fourth palette, and change where the palettes are applied to the screen. The pink, green, and blue page icons are going to shift left to make room for a fourth orange icon.

3. Display the fourth page's content

Edit engine/pokemon/stats_screen.asm:

-	const_def 1
-	const PINK_PAGE  ; 1
-	const GREEN_PAGE ; 2
-	const BLUE_PAGE  ; 3
-NUM_STAT_PAGES EQU const_value + -1
+	const_def
+	const PINK_PAGE   ; 0
+	const GREEN_PAGE  ; 1
+	const BLUE_PAGE   ; 2
+	const ORANGE_PAGE ; 3
+NUM_STAT_PAGES EQU const_value

Instead of three constants from 1 to 3, we'll use four constants from 0 to 3. We're not using 1 to 4, because 4 in binary is %100, and these values need to fit in two bits.

 StatsScreenMain:
 	xor a
 	ld [wJumptableIndex], a
-; ???
-	ld [wStatsScreenFlags], a
-	ld a, [wStatsScreenFlags]
-	and ~STAT_PAGE_MASK
-	or PINK_PAGE ; first_page
-	ld [wStatsScreenFlags], a
+	ld [wStatsScreenFlags], a ; PINK_PAGE
 .loop
 	ld a, [wJumptableIndex]
 	and ~(1 << 7)
 	ld hl, StatsScreenPointerTable
 	rst JumpTable
 	call StatsScreen_WaitAnim ; check for keys?
 	ld a, [wJumptableIndex]
 	bit 7, a
 	jr z, .loop
 	ret

 StatsScreenMobile:
 	xor a
 	ld [wJumptableIndex], a
- ???
-	ld [wStatsScreenFlags], a
-	ld a, [wStatsScreenFlags]
-	and ~STAT_PAGE_MASK
-	or PINK_PAGE ; first_page
-	ld [wStatsScreenFlags], a
+	ld [wStatsScreenFlags], a ; PINK_PAGE
 .loop
 	farcall Mobile_SetOverworldDelay
 	ld a, [wJumptableIndex]
 	and ~(1 << 7)
 	ld hl, StatsScreenPointerTable
 	rst JumpTable
 	call StatsScreen_WaitAnim
 	farcall MobileComms_CheckInactivityTimer
 	jr c, .exit
 	ld a, [wJumptableIndex]
 	bit 7, a
 	jr z, .loop

 .exit
 	ret

This code was a bit messy and redundant, but the intended effect is to start at the pink page. We changed its index from 1 to 0, so that needed updating.

 StatsScreen_JoypadAction:
 	...

 .a_button
 	ld a, c
-	cp BLUE_PAGE ; last page
+	cp ORANGE_PAGE ; last page
 	jr z, .b_button
 .d_right
 	inc c
-	ld a, BLUE_PAGE ; last page
+	ld a, ORANGE_PAGE ; last page
 	cp c
 	jr nc, .set_page
 	ld c, PINK_PAGE ; first page
 	jr .set_page

 .d_left
+	ld a, c
 	dec c
+	and a ; cp PINK_PAGE ; first page
 	jr nz, .set_page
-	ld c, BLUE_PAGE ; last page
+	ld c, ORANGE_PAGE ; last page
 	jr .set_page

 .done
 	ret

 .set_page
 	ld a, [wcf64]
 	and %11111100
 	or c
 	ld [wcf64], a
 	ld h, 4
 	call StatsScreen_SetJumptableIndex
 	ret

Pressing left and right navigates between the pages, wrapping around the ends if necessary. The orange page is last, not the blue page any more, and some logic also needed updating since a page index of 0 is now possible.

 StatsScreen_PlacePageSwitchArrows:
-	hlcoord 12, 6
+	hlcoord 10, 6
 	ld [hl], "◀"
 	hlcoord 19, 6
 	ld [hl], "▶"
 	ret

The arrow left of the page icons needs shifting further left to make room for a fourth icon.

 StatsScreen_LoadGFX:
 	...

 .PageTilemap:
 	ld a, [wStatsScreenFlags]
 	maskbits NUM_STAT_PAGES
-	dec a
 	ld hl, .Jumptable
 	rst JumpTable
 	ret

 .Jumptable:
 ; entries correspond to *_PAGE constants
 	dw LoadPinkPage
 	dw LoadGreenPage
 	dw LoadBluePage
+	dw LoadOrangePage

 	...

+LoadOrangePage:
+	ld de, HelloWorldString
+	hlcoord 1, 9
+	call PlaceString
+	ret
+
+HelloWorldString:
+	db "Hello world!@"
+
 IDNoString:
 	db "<ID>№.@"

 OTString:
 	db "OT/@"

This is where we display the actual content of the orange page (as well as another adjustment to account for the pink page's new index 0). This example will just say "Hello world!"

 StatsScreen_LoadPageIndicators:
+	hlcoord 11, 5
+	ld a, $36 ; " " " "
+	call .load_square
 	hlcoord 13, 5
 	ld a, $36 ; first of 4 small square tiles
 	call .load_square
 	hlcoord 15, 5
 	ld a, $36 ; " " " "
 	call .load_square
 	hlcoord 17, 5
 	ld a, $36 ; " " " "
 	call .load_square
 	ld a, c
-	cp GREEN_PAGE
-	ld a, $3a ; first of 4 large square tiles
-	hlcoord 13, 5 ; PINK_PAGE (< GREEN_PAGE)
-	jr c, .load_square
-	hlcoord 15, 5 ; GREEN_PAGE (= GREEN_PAGE)
-	jr z, .load_square
-	hlcoord 17, 5 ; BLUE_PAGE (> GREEN_PAGE)
+	cp PINK_PAGE
+	hlcoord 11, 5
+	jr z, .load_highlighted_square
+	cp GREEN_PAGE
+	hlcoord 13, 5
+	jr z, .load_highlighted_square
+	cp BLUE_PAGE
+	hlcoord 15, 5
+	jr z, .load_highlighted_square
+	; must be ORANGE_PAGE
+	hlcoord 17, 5
+.load_highlighted_square
+	ld a, $3a ; first of 4 large square tiles
 .load_square
 	push bc
 	ld [hli], a
 	inc a
 	ld [hld], a
 	ld bc, SCREEN_WIDTH
 	add hl, bc
 	inc a
 	ld [hli], a
 	inc a
 	ld [hl], a
 	pop bc
 	ret

The current page's icon is shown larger than the rest. Apart from changing their coordinates, we also need to update the logic. With only three pages, a single less/equal/greater comparison to the middle page's index was enough to tell which one to highlight, but four pages need more thorough checking.

4. Account for the shifted page indexes

Edit engine/gfx/color.asm:

 LoadStatsScreenPals:
 	call CheckCGB
 	ret z
 	ld hl, StatsScreenPals
 	ld b, 0
-	dec c
 	add hl, bc
 	add hl, bc
 	ldh a, [rSVBK]
 	push af
 	ld a, BANK(wBGPals1)
 	ldh [rSVBK], a
 	ld a, [hli]
 	ld [wBGPals1 palette 0], a
 	ld [wBGPals1 palette 2], a
 	ld a, [hl]
 	ld [wBGPals1 palette 0 + 1], a
 	ld [wBGPals1 palette 2 + 1], a
 	pop af
 	ldh [rSVBK], a
 	call ApplyPals
 	ld a, $1
 	ret

This function used to adjust the page index in c from 1–3 to 0–2. Now that indexes are 0–3, they don't need adjustment.

Now we have a fourth stats page in-game!

Screenshot

5. Show Caught Data on the fourth page

This is an alternative loadOrangePage, as opposed to the "Hello world!" example earlier. It displays the Caught Location (or "UNKNOWN"), the caught Time (or a blank row if the Caught Location was unknown) as well as the level the Pokémon was met at (or "???").

+LoadOrangePage:
+	call .placeCaughtLocation
+	ld de, MetAtMapString
+	hlcoord 1, 9
+	call PlaceString
+	call .placeCaughtLevel
+	ret
+
+.placeCaughtLocation
+	ld a, [wTempMonCaughtLocation]
+	and CAUGHT_LOCATION_MASK
+	jr z, .unknown_location
+	cp LANDMARK_EVENT
+	jr z, .unknown_location
+	cp LANDMARK_GIFT
+	jr z, .unknown_location
+	ld e, a
+	farcall GetLandmarkName
+	ld de, wStringBuffer1
+	hlcoord 2, 10
+	call PlaceString
+	ld a, [wTempMonCaughtTime]
+	and CAUGHT_TIME_MASK
+	ret z ; no time
+	rlca
+	rlca
+	dec a
+	ld hl, .times
+	call GetNthString
+	ld d, h
+	ld e, l
+	call CopyName1
+	ld de, wStringBuffer2
+	hlcoord 2, 11
+	call PlaceString
+	ret
+
+.unknown_location:
+	ld de, MetUnknownMapString
+	hlcoord 2, 10
+	call PlaceString
+	ret
+
+.times
+	db "MORN@"
+	db "DAY@"
+	db "NITE@"
+
+.placeCaughtLevel
+	; caught level
+	; Limited to between 1 and 63 since it's a 6-bit quantity.
+	ld a, [wTempMonCaughtLevel]
+	and CAUGHT_LEVEL_MASK
+	jr z, .unknown_level
+	cp CAUGHT_EGG_LEVEL ; egg marker value
+	jr nz, .print
+	ld a, EGG_LEVEL ; egg hatch level
+
+.print
+	ld [wTextDecimalByte], a
+	hlcoord 3, 13
+	ld de, wTextDecimalByte
+	lb bc, PRINTNUM_LEFTALIGN | 1, 3
+	call PrintNum
+	ld de, MetAtLevelString
+	hlcoord 1, 12
+	call PlaceString
+	hlcoord 2, 13
+	ld [hl], "<LV>"
+	ret
+
+.unknown_level
+	ld de, MetUnknownLevelString
+	hlcoord 2, 12
+	call PlaceString
+	ret
+
+MetAtMapString:
+	db "MET AT:@"
+
+MetUnknownMapString:
+	db "UNKNOWN@"
+	
+MetAtLevelString:
+	db "MET LEVEL:@"    
+MetUnknownLevelString:
+	db "???@"

Note: If you're using an older version of 16b Pokecrystal, you'll want to replace the following with their appropriate 16b versions.

LANDMARK_EVENT -> EVENT_LOCATION

LANDMARK_GIFT -> GIFT_LOCATION

[wTextDecimalByte] -> [wDeciramBuffer]

Clone this wiki locally