Skip to content

Add a third trainer card page for Kanto badges

Grate Oracle Lewot edited this page Feb 12, 2024 · 16 revisions

The engine for the Trainer Card screen has unused (and incomplete) code intended to display a third page for the Kanto Badges. The following tutorial outlines the necessary steps needed for restoring the feature.

Note: This tutorial assumes you have not implemented either colored trainer card badges or show the tops of leaders heads on the trainer card. However, both of those can be implemented by following the steps listed in those tutorials, but substituting the existing Johto relevant code with the new Kanto relevant code from below. See the appendix for additional details.

Table of Contents

  1. Define new SGB and CGB color palette constant
  2. Reuse the SGB palette routine
  3. Create the CGB palette routine
  4. Modify the trainer card engine code
  5. Appendix

1) Define new SGB and CGB color palette constant

First, we need to define a new constant in constants/scgb_constants.asm:

    ; CGBLayoutJumptable indexes (see engine/gfx/cgb_layouts.asm)
    ; SGBLayoutJumptable indexes (see engine/gfx/sgb_layouts.asm)
	const_def
 	...
	const SCGB_POKEPIC
	const SCGB_MAGNET_TRAIN
	const SCGB_PACKPALS
	const SCGB_TRAINER_CARD
+	const SCGB_TRAINER_CARD_KANTO
 	const SCGB_POKEDEX_UNOWN_MODE
	const SCGB_BILLS_PC
	const SCGB_UNOWN_PUZZLE
 	...
    DEF NUM_SCGB_LAYOUTS EQU const_value
    ...

2) Reuse the SGB palette routine

Open engine/gfx/sgb_layouts.asm

These are the routines for loading palette data when running the game in Super Game Boy mode. We need to associate the new constant with a routine that will load the correct palette. Luckily for us, we can just point to and reuse the existing routine.

Associate a routine pointer for the new constant in the SGBLayoutJumptable table:

    SGBLayoutJumptable:
	table_width 2, SGBLayoutJumptable
 	...
	dw .SGB_Pokepic
	dw .SGB_MagnetTrain
	dw .SGB_PackPals
	dw .SGB_TrainerCard
+	dw .SGB_TrainerCardKanto
	dw .SGB_PokedexUnownMode
	dw .SGB_BillsPC
	dw .SGB_UnownPuzzle
 	...
 	assert_table_length NUM_SCGB_LAYOUTS
    ...

And then add the new label to the existing routine SGB_TrainerCard:

    .SGB_Unused0D:
    .SGB_TrainerCard:
+   .SGB_TrainerCardKanto:
	ld hl, PalPacket_Diploma
	ld de, BlkPacket_AllPal0
	ret

3) Create the CGB palette routine

Open engine/gfx/cgb_layouts.asm

These are the routines for loading palette data when running the game in Color Game Boy mode. Naturally, these functions are quite a degree more complicated than their SGB counterparts. Same as before, we need to associate the new constant with a routine that will load the desired palette. Except, this time a new routine will have to be created.

  1. Create CGB jump table pointer
  2. Change trainer constants
  3. Change background palette assignments

3.1) Create CGB jump table pointer

Associate a routine pointer for the new constant in the CGBLayoutJumptable table:

    CGBLayoutJumptable:
	table_width 2, CGBLayoutJumptable
 	...
	dw _CGB_Pokepic
	dw _CGB_MagnetTrain
	dw _CGB_PackPals
	dw _CGB_TrainerCard
+	dw _CGB_TrainerCardKanto
	dw _CGB_PokedexUnownMode
	dw _CGB_BillsPC
	dw _CGB_UnownPuzzle
 	...
 	assert_table_length NUM_SCGB_LAYOUTS
    ...

Then create the new routine label _CGB_TrainerCardKanto between the existing routine _CGB_TrainerCard and the routine _CGB_MoveList:

    _CGB_TrainerCard:
	...
	ret
+
+   _CGB_TrainerCardKanto:
+       < C&P EXISTING CODE HERE >
+
    _CGB_MoveList:
	...
	ret

Next, we will be copy & pasting the existing code from _CGB_TrainerCard to our new routine _CGB_TrainerCardKanto, but with the following changes:

3.2) Change trainer constants

In the first half of the code, change all the Johto trainer constants to the corresponding Kanto trainer constants. Note that palettes 0 and 1 are reserved for the male and female player palettes. This only leaves 6 open palettes for 8 gym leaders. In much the same way that the old code reuses the same palette for Kris, Falkner, and Claire; Chris will share a palette with Misty, and Lt.Surge with Erika.

    _CGB_TrainerCard:
	...
	ret
    
    _CGB_TrainerCardKanto:
	ld de, wBGPals1
-	xor a ; CHRIS
+	xor a ; CHRIS & MISTY
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
	ld a, FALKNER ; KRIS
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
-	ld a, BUGSY
+	ld a, BROCK
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
-	ld a, WHITNEY
+	ld a, LT_SURGE ; ERIKA
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
-	ld a, MORTY
+	ld a, JANINE
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
-	ld a, CHUCK
+	ld a, SABRINA
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
-	ld a, JASMINE
+	ld a, BLAINE
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
-	ld a, PRYCE
+	ld a, BLUE
	call GetTrainerPalettePointer
	call LoadPalette_White_Col1_Col2_Black
	ld a, PREDEFPAL_CGB_BADGE
	call GetPredefPal
	call LoadHLPaletteIntoDE

	...

    _CGB_MoveList:
	...
	ret

3.3) Change background palette assignments

In the second half we will keep most of the same code, save for changing the palette $id that we load into each section of the screen. We will also take this moment to refactor a leftover remnant of code from GS that recolors the upper right hand corner by moving that logic to the end. It might not be a bad idea to do the same for the existing code as well.

    _CGB_TrainerCard:
	...
	ret
    
    _CGB_TrainerCardKanto:

	...

	; fill screen with opposite-gender palette for the card border
	hlcoord 0, 0, wAttrmap
	ld bc, SCREEN_WIDTH * SCREEN_HEIGHT
	ld a, [wPlayerGender]
	and a
	ld a, $1 ; kris
	jr z, .got_gender
	ld a, $0 ; chris
    .got_gender
	call ByteFill
	; fill trainer sprite area with same-gender palette
	hlcoord 14, 1, wAttrmap
	lb bc, 7, 5
	ld a, [wPlayerGender]
	and a
	ld a, $0 ; chris
	jr z, .got_gender2
	ld a, $1 ; kris
    .got_gender2
	call FillBoxCGB
-	; top-right corner still uses the border's palette
-	hlcoord 18, 1, wAttrmap
-	ld [hl], $1
	hlcoord 2, 11, wAttrmap
	lb bc, 2, 4
-	ld a, $1 ; falkner
+	ld a, $2 ; brock
	call FillBoxCGB
	hlcoord 6, 11, wAttrmap
	lb bc, 2, 4
-	ld a, $2 ; bugsy
+	ld a, $0 ; misty / chris
	call FillBoxCGB
	hlcoord 10, 11, wAttrmap
	lb bc, 2, 4
-	ld a, $3 ; whitney
+	ld a, $3 ; lt.surge / erika
	call FillBoxCGB
	hlcoord 14, 11, wAttrmap
	lb bc, 2, 4
-	ld a, $4 ; morty
+	ld a, $3 ; erika / lt.surge
	call FillBoxCGB
	hlcoord 2, 14, wAttrmap
	lb bc, 2, 4
-	ld a, $5 ; chuck
+	ld a, $4 ; janine
	call FillBoxCGB
	hlcoord 6, 14, wAttrmap
	lb bc, 2, 4
-	ld a, $6 ; jasmine
+	ld a, $5 ; sabrina
	call FillBoxCGB
	hlcoord 10, 14, wAttrmap
	lb bc, 2, 4
-	ld a, $7 ; pryce
+	ld a, $6 ; blaine
	call FillBoxCGB
-	; clair uses kris's palette
-	ld a, [wPlayerGender]
-	and a
-	push af
-	jr z, .got_gender3
	hlcoord 14, 14, wAttrmap
	lb bc, 2, 4
-	ld a, $1
+	ld a, $7 ; blue
	call FillBoxCGB
+	; top-right corner still uses the border's palette
+	ld a, [wPlayerGender]
+	and a
+	ld a, $1 ; kris
+	jr z, .got_gender3
+	ld a, $0 ; chris
    .got_gender3
-	pop af
-	ld c, $0
-	jr nz, .got_gender4
-	inc c
-   .got_gender4
-	ld a, c
	hlcoord 18, 1, wAttrmap
	ld [hl], a
	call ApplyAttrmap
	call ApplyPals
	ld a, TRUE
	ldh [hCGBPalUpdate], a
	ret

    _CGB_MoveList:
	...
	ret

4) Modify the trainer card engine code

Open engine/menus/trainer_card.asm

These are the routines that control the trainer card screen. Everything from drawing the graphics, handling input and screen state navigation, and displaying the correct data in each screen state. Scrolling through this code, you will find many references to a page 3 and Kanto badges. At one point in development it must have been planned for a page 3 that displays Kanto badges to be included. However, since the feature was scrapped, most of the code is in an incomplete state. To restore the missing page 3, we need to complete the following:

  1. Fix jump table navigation code
  2. Edit OAM data and include new GFX files
  3. Load per-page palette and OAM data

4.1) Fix jump table navigation code

First, we can remove this unused Kanto check under TrainerCard_Page1_Joypad:

	...
    .pressed_right_a
	ld a, TRAINERCARDSTATE_PAGE2_LOADGFX
	ld [wJumptableIndex], a
	ret

-   .KantoBadgeCheck: ; unreferenced
-	ld a, [wKantoBadges]
-	and a
-	ret z
-	ld a, TRAINERCARDSTATE_PAGE3_LOADGFX
-	ld [wJumptableIndex], a
-	ret

    TrainerCard_Page2_LoadGFX:
	...

Second, alter the navigation code for the Johto page under TrainerCard_Page2_Joypad such that pressing A or Right navigates to the third page when the player has 1 or more Kanto badges:

	...
    TrainerCard_Page2_Joypad:
	ld hl, TrainerCard_JohtoBadgesOAM
	call TrainerCard_Page2_3_AnimateBadges
	ld hl, hJoyLast
	ld a, [hl]
+	and D_LEFT
+	jr nz, .pressed_left
+	ld a, [wKantoBadges]
+	and a
+	jr nz, .has_kanto_badges
+	ld a, [hl]
	and A_BUTTON
	jr nz, .Quit
-	ld a, [hl]
-	and D_LEFT
-	jr nz, .d_left
	ret
+   .has_kanto_badges
+	ld a, [hl]
+	and D_RIGHT | A_BUTTON
+	jr nz, .pressed_right_a
+	ret

-   .d_left
+   .pressed_left
	ld a, TRAINERCARDSTATE_PAGE1_LOADGFX
	ld [wJumptableIndex], a
	ret

-   .KantoBadgeCheck: ; unreferenced
-	ld a, [wKantoBadges]
-	and a
-	ret z
+   .pressed_right_a
	ld a, TRAINERCARDSTATE_PAGE3_LOADGFX
	ld [wJumptableIndex], a
	ret

    .Quit:
	ld a, TRAINERCARDSTATE_QUIT
	ld [wJumptableIndex], a
	ret

    TrainerCard_Page3_LoadGFX:
	...

Third, alter the navigation code for the Kanto page under TrainerCard_Page3_Joypad such that pressing A quits and pressing right does not loop back around to the first page:

	...
    TrainerCard_Page3_Joypad:
	ld hl, TrainerCard_JohtoBadgesOAM
	call TrainerCard_Page2_3_AnimateBadges
	ld hl, hJoyLast
	ld a, [hl]
	and D_LEFT
-	jr nz, .left
+	jr nz, .pressed_left
	ld a, [hl]
-	and D_RIGHT
-	jr nz, .right
+	and A_BUTTON
+	jr nz, .pressed_a
	ret

-   .left
+   .pressed_left
	ld a, TRAINERCARDSTATE_PAGE2_LOADGFX
	ld [wJumptableIndex], a
	ret

-   .right
-	ld a, TRAINERCARDSTATE_PAGE1_LOADGFX
+   .pressed_a
+	ld a, TRAINERCARDSTATE_QUIT
	ld [wJumptableIndex], a
	ret

4.2) Edit OAM data and include new GFX files

We need to define the OAM data for the Kanto badge screen. This data defines which wram address has the badge count to use (Johto or Kanto) as well as the individual tile data for each badge. Copy the code under TrainerCard_JohtoBadgesOAM into a new label TrainerCard_KantoBadgesOAM:

Note: In the example below, the Kanto badges have their int1-int3 tiles x-flipped opposite of the Johto badges. This creates the illusion as if they were spinning the opposite way. This is purely a cosmetic change to make them unique.

    TrainerCard_JohtoBadgesOAM:
-   ; Template OAM data for each badge on the trainer card.
+   ; Template OAM data for Johto badges on the trainer card.
    ; Format:
	; y, x, palette
	; cycle 1: face tile, in1 tile, in2 tile, in3 tile
	; cycle 2: face tile, in1 tile, in2 tile, in3 tile

	dw wJohtoBadges

	...

+   TrainerCard_KantoBadgesOAM:
+   ; Template OAM data for Kanto badges on the trainer card.
+   ; Format:
+	; y, x, palette
+	; cycle 1: face tile, in1 tile, in2 tile, in3 tile
+	; cycle 2: face tile, in1 tile, in2 tile, in3 tile
+
+	dw wKantoBadges
+
+	; Boulderbadge
+	db $68, $18, 0
+	db $00, $20 | (1 << 7), $24, $20
+	db $00, $20 | (1 << 7), $24, $20
+
+	; Cascadebadge
+	db $68, $38, 0
+	db $04, $20 | (1 << 7), $24, $20
+	db $04, $20 | (1 << 7), $24, $20
+
+	; Thunderbadge
+	db $68, $58, 0
+	db $08, $20 | (1 << 7), $24, $20
+	db $08, $20 | (1 << 7), $24, $20
+
+	; Rainbowbadge
+	db $68, $78, 0
+	db $0c, $20 | (1 << 7), $24, $20
+	db $0c, $20 | (1 << 7), $24, $20
+
+	; Soulbadge
+	db $80, $18, 0
+	db $10, $20 | (1 << 7), $24, $20
+	db $10, $20 | (1 << 7), $24, $20
+
+	; Marshbadge
+	db $80, $38, 0
+	db $14, $20 | (1 << 7), $24, $20
+	db $14, $20 | (1 << 7), $24, $20
+
+	; Volcanobadge
+	db $80, $58, 0
+	db $18, $20 | (1 << 7), $24, $20
+	db $18, $20 | (1 << 7), $24, $20
+
+	; Earthbadge
+	; X-flips on alternate cycles.
+	db $80, $78, 0
+	db $1c,            $20 | (1 << 7), $24, $20
+	db $1c | (1 << 7), $20 | (1 << 7), $24, $20

    CardStatusGFX: INCBIN "gfx/trainer_card/card_status.2bpp"

-   LeaderGFX:  INCBIN "gfx/trainer_card/leaders.2bpp"
-   LeaderGFX2: INCBIN "gfx/trainer_card/leaders.2bpp"
-   BadgeGFX:   INCBIN "gfx/trainer_card/badges.2bpp"
-   BadgeGFX2:  INCBIN "gfx/trainer_card/badges.2bpp"
+   LeaderGFX:  INCBIN "gfx/trainer_card/johto_leaders.2bpp"
+   LeaderGFX2: INCBIN "gfx/trainer_card/kanto_leaders.2bpp"
+   BadgeGFX:   INCBIN "gfx/trainer_card/johto_badges.2bpp"
+   BadgeGFX2:  INCBIN "gfx/trainer_card/kanto_badges.2bpp"

    CardRightCornerGFX: INCBIN "gfx/trainer_card/card_right_corner.2bpp"

Add these files to the /gfx/trainer_card directory and rename the existing files accordingly.

kanto_leaders.png

kanto_leaders

kanto_badges.png

kanto_badges

4.3) Load per-page palette and OAM data

kanto_badges

Here under TrainerCard_Page1_LoadGFX:

    TrainerCard_Page1_LoadGFX:
	call ClearSprites
	hlcoord 0, 8
	ld d, 6
	call TrainerCard_InitBorder
	call WaitBGMap
+	ld b, SCGB_TRAINER_CARD
+	call GetSGBLayout
+	call SetDefaultBGPAndOBP ; this function was called 'SetPalettes' in older versions of pokecrystal
+	call WaitBGMap
	ld de, CardStatusGFX
	ld hl, vTiles2 tile $29
	lb bc, BANK(CardStatusGFX), 86
	...

Here under TrainerCard_Page2_LoadGFX:

    TrainerCard_Page2_LoadGFX:
	call ClearSprites
	hlcoord 0, 8
	ld d, 6
	call TrainerCard_InitBorder
	call WaitBGMap
+	ld b, SCGB_TRAINER_CARD
+	call GetSGBLayout
+	call SetDefaultBGPAndOBP ; this function was called 'SetPalettes' in older versions of pokecrystal
+	call WaitBGMap
	ld de, LeaderGFX
	ld hl, vTiles2 tile $29
	lb bc, BANK(LeaderGFX), 86
	call Request2bpp
	ld de, BadgeGFX
	ld hl, vTiles0 tile $00
	lb bc, BANK(BadgeGFX), 44
	call Request2bpp
+	ld hl, TrainerCard_JohtoBadgesOAM
	call TrainerCard_Page2_3_InitObjectsAndStrings
	call TrainerCard_IncrementJumptable
	ret

    TrainerCard_Page2_Joypad:
	ld hl, TrainerCard_JohtoBadgesOAM
	call TrainerCard_Page2_3_AnimateBadges
	...

And here under TrainerCard_Page3_LoadGFX, this time with our new Kanto constant and Kanto OAM data:

    TrainerCard_Page3_LoadGFX:
	call ClearSprites
	hlcoord 0, 8
	ld d, 6
	call TrainerCard_InitBorder
	call WaitBGMap
+	ld b, SCGB_TRAINER_CARD_KANTO
+	call GetSGBLayout
+	call SetDefaultBGPAndOBP ; this function was called 'SetPalettes' in older versions of pokecrystal
+	call WaitBGMap
	ld de, LeaderGFX2
	ld hl, vTiles2 tile $29
	lb bc, BANK(LeaderGFX2), 86
	call Request2bpp
+	ld hl, TrainerCard_KantoBadgesOAM
	call TrainerCard_Page2_3_InitObjectsAndStrings
	call TrainerCard_IncrementJumptable
	ret

    TrainerCard_Page3_Joypad:
-	ld hl, TrainerCard_JohtoBadgesOAM
+	ld hl, TrainerCard_KantoBadgesOAM
	call TrainerCard_Page2_3_AnimateBadges
	...

Then we must change the routine TrainerCard_Page2_3_InitObjectsAndStrings so it works with either OAM data set:

    TrainerCard_Page2_3_InitObjectsAndStrings:
+	push hl
	hlcoord 2, 8
	ld de, .BadgesTilemap
	call TrainerCardSetup_PlaceTilemapString
	hlcoord 2, 10
	ld a, $29
	ld c, 4
	...
	xor a
	ld [wTrainerCardBadgeFrameCounter], a
-	ld hl, TrainerCard_JohtoBadgesOAM
+	pop hl
	call TrainerCard_Page2_3_OAMUpdate
	ret

5) Appendix

If you want to follow the tutorial to display colored trainer card badges, here is the palette data for the kanto badges:

Create gfx/trainer_card/kanto_badges.pal:

+	RGB 31,31,31, 23,22,22, 14,13,13, 00,00,00 ; Boulder Badge
+	RGB 31,31,31, 19,31,30, 00,23,30, 00,00,00 ; Cascade Badge
+	RGB 31,31,31, 31,26,05, 31,11,00, 00,00,00 ; Thunder Badge
+	RGB 31,31,31, 31,31,14, 00,29,07, 00,00,00 ; Rainbow Badge
+	RGB 31,31,31, 31,19,30, 31,09,30, 00,00,00 ; Marsh Badge
+	RGB 31,31,31, 31,22,04, 19,13,01, 00,00,00 ; Soul Badge
+	RGB 31,31,31, 31,17,23, 31,00,06, 00,00,00 ; Volcano Badge
+	RGB 31,31,31, 19,30,12, 00,16,06, 00,00,00 ; Earth Badge

If you want to follow the tutorial to show the tops of leaders heads, here is an updated gfx file with the blank tiles restored:

kanto_leaders.png

kanto_leaders

Clone this wiki locally