Add a fourth stats page - pret/pokecrystal GitHub Wiki
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
- Define the fourth page's colors
- Load and apply the fourth page's palette
- Display the fourth page's content
- Account for the shifted page indexes
- 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!
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]