14 Add female player character
Sha0den edited this page 2025-09-29 19:26:16 -04:00

In the sequel the player has the option to play as a female character, Mint. We'll see how we can port that feature to this game using event flags.

Contents

  1. Add female player graphics
  2. Create a new portrait
  3. Create a new OW sprite
  4. Add new event flag for player gender
  5. Create gender selection screen
  6. Make default name gender-specific
  7. Deal with gender references in the text files
  8. Adjust player portrait
  9. Adjust player OW sprite
  10. Adjust Main Menu screen

1. Add female player graphics

As a first step we will add the new graphics. We will be using the following image for the portrait, place it in gfx/duelists/ and name it mint.png:

mint.png

Next the overworld sprite. Place it in gfx/overworld_sprites/ and name it mint.png:

mint.png

Since all Gfx sections are pretty full, we will create a new section in ROM where we will put these graphics. Also we will add a palette for the new portrait. Edit src/gfx.asm:

 FightingGfx::
 	dw $04
 	INCBIN "gfx/titlescreen/energies/fighting.2bpp"
 
+
+SECTION "Gfx 13", ROMX
+
+MintGfx::
+	dw 36
+	INCBIN "gfx/duelists/mint.2bpp"
+
+OWMintGfx::
+	dw $14
+	INCBIN "gfx/overworld_sprites/mint.2bpp"
+
+Palette161::
+	db 0
+	db 1
+
+	rgb 28, 28, 24
+	rgb 28, 16, 12
+	rgb  4,  8, 28
+	rgb  0,  0,  8
+
 SECTION "Anims 1", ROMX
 	INCLUDE "data/duel/animations/anims1.asm"

And to include this section in ROM let's use bank $2f, which is empty. Edit src/layout.link:

 	"Palettes1"
 ROMX $2e
 	"Palettes2"
+ROMX $2f
+	"Gfx 13"
 ROMX $31
 	"Card Gfx 1"
 ROMX $32

2. Create a new portrait

Let's put everything in place to use the new portrait. For the tileset edit src/constants/tileset_constants.asm:

 	const TILESET_JESSICA                     ; $54
 	const TILESET_STEPHANIE                   ; $55
 	const TILESET_AARON                       ; $56
+	const TILESET_MINT                        ; $57
 
 DEF NUM_TILESETS EQU const_value

And edit src/engine/gfx/tilesets.asm:

 	tileset JessicaGfx,                     36 ; TILESET_JESSICA
 	tileset StephanieGfx,                   36 ; TILESET_STEPHANIE
 	tileset AaronGfx,                       36 ; TILESET_AARON
+	tileset MintGfx,                        36 ; TILESET_MINT
 	assert_table_length NUM_TILESETS

For the palette edit src/constants/palette_constants.asm:

 	const PALETTE_158               ; $9e
 	const PALETTE_159               ; $9f
 	const PALETTE_160               ; $a0
+	const PALETTE_161               ; $a1
 
 DEF NUM_PALETTES EQU const_value

And edit src/data/palette_pointers.asm:

 	palette_pointer Palette158, 1, 0 ; PALETTE_158
 	palette_pointer Palette159, 1, 0 ; PALETTE_159
 	palette_pointer Palette160, 1, 0 ; PALETTE_160
+	palette_pointer Palette161, 1, 0 ; PALETTE_161
 	assert_table_length NUM_PALETTES

For the portrait edit src/constants/npc_constants.asm:

 	const STEPHANIE_PIC ; $28
 	const AARON_PIC     ; $29
 	const LINK_OPP_PIC  ; $2a
+	const MINT_PIC      ; $2b
 DEF NUM_PICS EQU const_value

And finally we put everything together in src/data/duel/portraits.asm:

 	portrait TILESET_STEPHANIE, PALETTE_159, SGBData_StephaniePortraitPals    ; STEPHANIE_PIC
 	portrait TILESET_AARON,     PALETTE_160, SGBData_AaronPortraitPals        ; AARON_PIC
 	portrait TILESET_PLAYER,    PALETTE_120, SGBData_LinkOpponentPortraitPals ; LINK_OPP_PIC
+	portrait TILESET_MINT,      PALETTE_161, SGBData_PlayerPortraitPals       ; MINT_PIC
 	assert_table_length NUM_PICS

You may notice that we used the default player portrait pals for the SGB, SGBData_PlayerPortraitPals. The reason for this is that creating new SGB palettes is more involved and not as streamlined as regular palettes. We shall keep it as the default colours for the sake of simplicity.

We are now able to use MINT_PIC to refer to this newly created portrait.

3. Create a new OW sprite

Similar to the previous step, we will create a new constant and pointer to the graphics related to the OW sprites. Edit src/constants/sprite_constants.asm:

 	const SPRITE_LIGHTNING          ; $6f
 	const SPRITE_PSYCHIC            ; $70
 	const SPRITE_FIGHTING           ; $71
+	const SPRITE_OW_MINT            ; $72
 
 DEF NUM_SPRITES EQU const_value

Next edit src/engine/gfx/sprites.asm:

 	gfx_pointer LightningGfx,        $04 ; SPRITE_LIGHTNING
 	gfx_pointer PsychicGfx,          $04 ; SPRITE_PSYCHIC
 	gfx_pointer FightingGfx,         $04 ; SPRITE_FIGHTING
+	gfx_pointer OWMintGfx,           $14 ; SPRITE_OW_MINT
 	assert_table_length NUM_SPRITES

Here, SPRITE_OW_MINT will now refer to the new OW graphics.

4. Add new event flag for player gender

We will implement this in code by storing the player's gender in an event flag. This has the advantage that event flags are loaded and written to SRAM by the game by default, and they are as compact as possible to relay the information they represent. In our case, we need only a single bit: 0 for male, 1 for female. Edit src/constants/script_constants.asm and add a new event to the end of the list:

 	const EVENT_CONSOLE                                ; $74
 	const EVENT_SAM_MENU_CHOICE                        ; $75
 	const EVENT_AARON_DECK_MENU_CHOICE                 ; $76
+	const EVENT_PLAYER_GENDER                          ; $77
 DEF NUM_EVENT_FLAGS EQU const_value
 
 DEF EVENT_VAR_BYTES EQU $40

Next we will add it to the EventVarMasks list. Edit src/engine/overworld/scripting.asm:

 EventVarMasks:
 	...
 	event_def $1b, %11111111 ; EVENT_CONSOLE
 	event_def $1c, %11110000 ; EVENT_SAM_MENU_CHOICE
 	event_def $1c, %00001111 ; EVENT_AARON_DECK_MENU_CHOICE
+	event_def $1d, %00000001 ; EVENT_PLAYER_GENDER
 	assert_table_length NUM_EVENT_FLAGS

This makes it so that the lowest significant bit of byte $1d in wEventVars represents the player's gender choice.

5. Create gender selection screen

Clearly the main point is that the player has a choice when starting a new game to select a gender. For this example we will present a screen similar to Pokémon TCG 2, before the player inputs their name. Create a new file src/engine/menus/choose_gender.asm:

PlayerGenderSelection:
	; setup the screen
	xor a
	ld [wTileMapFill], a
	call EmptyScreen
	call ZeroObjectPositions
	ld a, $01
	ld [wVBlankOAMCopyToggle], a
	call LoadSymbolsFont
	lb de, $38, $bf
	call SetupText

	; draw male portrait
	ld a, PLAYER_PIC
	ld [wCurPortrait], a
	ld a, TILEMAP_PLAYER
	lb bc, 2, 4
	call DrawPortrait

	; draw female portrait
	ld a, MINT_PIC
	ld [wCurPortrait], a
	ld a, TILEMAP_OPPONENT
	lb bc, 12, 4
	call DrawPortrait

	; print text
	ld hl, .TextItems
	call PlaceTextItems
	ldtx hl, AreYouBoyOrGirlText
	call DrawWideTextBox_PrintText

	; set parameters for the cursor
	lb de, 3, 2 ; cursor x and y
	lb bc, SYM_CURSOR_R, SYM_SPACE
	call SetCursorParametersForTextBox

	; start loop for selection
	ld a, [wCurMenuItem]
	jr .refresh_menu

.loop_input
	call DoFrame
	call RefreshMenuCursor
	ldh a, [hKeysPressed]
	bit B_PAD_A, a
	jr nz, .selection_made
	ldh a, [hDPadHeld]
	and PAD_RIGHT | PAD_LEFT
	jr z, .loop_input
	ld a, SFX_CURSOR
	call PlaySFX
	call EraseCursor
	ld hl, wCurMenuItem
	ld a, [hl]
	xor $1 ; toggle selected gender
	ld [hl], a
.refresh_menu
	or a
	ld a, 3 ; "Boy" cursor x
	jr z, .got_cursor_x
	ld a, 13 ; "Girl" cursor x
.got_cursor_x
	ld [wMenuCursorXOffset], a
	xor a
	ld [wCursorBlinkCounter], a
	jr .loop_input

.selection_made
	; set the gender event value
	ld a, [wCurMenuItem]
	or a
	ld a, EVENT_PLAYER_GENDER
	jr nz, .female
	farcall ZeroOutEventValue ; bit unset
	ret
.female
	farcall MaxOutEventValue ; bit set
	ret

.TextItems:
	textitem  4, 2, BoyText
	textitem 14, 2, GirlText
	db $ff

The new texts used here are the following (check here for a tutorial on adding new texts):

AreYouBoyOrGirlText:
	text "Are you a boy"
	line "or a girl?"
	done

BoyText:
	text "Boy"
	done

GirlText:
	text "Girl"
	done

And include this file in src/main.asm:

 INCLUDE "engine/menus/wait_keys.asm"
 INCLUDE "engine/gfx/default_palettes.asm"
 INCLUDE "engine/menus/naming.asm"
+INCLUDE "engine/menus/choose_gender.asm"
 
 SECTION "Sprite Animations", ROMX
 INCLUDE "engine/gfx/sprite_animations.asm"

In summary, the code sets up everything needed to print text and show the portraits, then enters a loop to wait for player selection. When the selection is made, it will set or unset EVENT_PLAYER_GENDER accordingly.

To open this gender selection screen, we'll call it when the player selects New Game from the Main Menu. Edit src/engine/menus/main_menu.asm:

 MainMenu_NewGame:
 	farcall Func_c1b1
+	call PlayerGenderSelection
 	call DisplayPlayerNamingScreen
 	farcall InitSaveData
 	call EnableSRAM

And with that, we got a working selection screen which stores the player gender. Next we will make use of this event flag to influence various aspects of the gameplay.

6. Make default name gender-specific

Right away in the naming screen we can add some logic to attribute a default name to the player, depending on the selected gender. We shall keep the default male name as Mark, and add a new female name, Mint. Edit DisplayPlayerNamingScreen in src/engine/menus/naming.asm:

 DisplayPlayerNamingScreen:
 	...
 	ld a, [hl]
 	or a
 	; check if anything typed.
-	jr nz, .no_name
-	ld hl, .default_name
-.no_name
+	jr nz, .got_name
+
+	ld a, EVENT_PLAYER_GENDER
+	farcall GetEventValue
+	or a
+	ld hl, .default_name_male
+	jr z, .got_name
+	ld hl, .default_name_female
+
+.got_name
 	; set the default name.
 	ld de, sPlayerName
 	ld bc, NAME_BUFFER_LENGTH

 	...

 	call DisableSRAM
 	ret
 
-.default_name
-	; "MARK": default player name.
+.default_name_male
+	; "MARK": default male player name.
 	textfw "MARK"
 	db TX_END, TX_END, TX_END, TX_END
 
+.default_name_female
+	; "MINT": default female player name.
+	textfw "MINT"
+	db TX_END, TX_END, TX_END, TX_END
+
 Unknown_128f7:

7. Deal with gender references in the text files

There are several texts in the base game that use masculine pronouns like "he" or "his" to refer to the player character. These can either be edited to use something more neutral, like the player's name or a second-person pronoun (i.e. you/your), or you can create alternate versions of these texts which use feminine pronouns. You may need to reference the How to add a new text tutorial once again if you're not yet familiar with the process. There are a total of 5 texts that will need to be addressed. They are Text05a6 from src/text/text6.asm, Text05e3, Text05f1, and Text0657 from src/text/text7.asm, and Text06a0 from src/text/text8.asm.

Let's start with Text0657 since the most sensible solution for that one is to create a female-specific alternate text. First, create the following text by copying the original and replacing "his" with "her". Remember to add an entry to src/text/text_offsets.asm as well.

Text0657_Mint:
	text "A single match with 4 prizes!"
	line "Come on, my precious ghosts!"
	line "Let's make her Pokémon disappear!"
	done

Next, open src/scripts/psychic_club_lobby.asm and make the following changes.

 Script_Robert:
 	...
 .ows_e98d
-	print_npc_text Text0657
+	test_if_event_false EVENT_PLAYER_GENDER
+	print_variable_npc_text Text0657, Text0657_Mint
 	start_duel PRIZES_4, GHOST_DECK_ID, MUSIC_DUEL_THEME_1
 	quit_script_fully

Note that if the original scripting command happens to be print_text_quit_fully, then you'll also need to insert quit_script_fully in the line below print_variable_npc_text. You can either create alternate versions for the remaining 4 texts or simply edit them to be gender-neutral. For this tutorial, we'll do the latter.

 Text05a6:
-	text "No, Ronald! That cannot be "
-	line "allowed! He, too, has earned "
+	text "No, Ronald! That cannot be allowed!"
+	line "<RAMNAME>, too, has earned "
 	line "the right to inherit the "
 	line "Legendary Pokémon Cards!"
 	line "Ronald! <RAMNAME>!"
 	line "You two must duel to determine who "
 	line "will inherit the Legendary Pokémon "
 	line "Cards. So say the Rules!"
 	done
 Text05e3:
 	text "Oh! Why the rush, <RAMNAME>?"
 	line "What? "
 	line "You want to learn how to play"
 	line "the Pokémon Trading Card Game?"
 	line "So you, too, finally want to "
 	line "start playing the card game. "
 	line "Well, dueling is more fun than "
 	line "just collecting cards!"
 	line "First, you should try playing "
 	line "with a Practice Deck. "
 	line "Here, I'll give you this Deck. "
 	line "And now you need an opponent..."
 	line "Hey, Sam!"
-	line "Play with him for a while!"
+	line "Play with <RAMNAME> for a while!"
 	done
 Text05f1:
-	text "<RAMNAME> handed his cards to "
+	text "You give all of your cards to"
 	line "Dr. Mason."
 	done
 Text06a0:
-	text "<RAMNAME> lost all "
-	line "his Energy cards!"
+	text "You donate all of your extra"
+	line "Energy cards!"
 	done

There are also several texts that imply the player is male/not female. It's up to you whether or not to change any of these texts. The list includes Text06a7, Text06d8, Text0703, and Text0708. They can all be found in src/text/text8.asm.

8. Adjust player portrait

Now the player's portrait is also dependent on the gender choice. We'll add a branch in DrawPlayerPortrait, in src/home/load_animation.asm:

 ; draws player's portrait at b,c
 DrawPlayerPortrait::
+	ld a, EVENT_PLAYER_GENDER
+	farcall GetEventValue
+	or a
 	ld a, PLAYER_PIC
+	jr z, .got_pic
+	ld a, MINT_PIC
+.got_pic
 	ld [wCurPortrait], a
 	ld a, TILEMAP_PLAYER
 ;	fallthrough

What's great about this modification is that it takes care of a lot of places where the portrait appears, such as in duels and in the Diary menu.

9. Adjust player OW sprite

To adjust the OW sprite we'll tell the game to load SPRITE_OW_MINT instead of the male player sprite. Edit src/engine/overworld/overworld.asm:

 	ld b, SPRITE_ANIM_LIGHT_NPC_UP
 	ld a, [wConsole]
 	cp CONSOLE_CGB
-	jr nz, .not_cgb
+	jr nz, .got_anim
+
+	ld a, EVENT_PLAYER_GENDER
+	farcall GetEventValue
+	or a
 	ld b, SPRITE_ANIM_RED_NPC_UP
-.not_cgb
+	jr z, .got_anim
+	ld b, SPRITE_ANIM_BLUE_NPC_UP
+.got_anim
 	ld a, b
 	ld [wPlayerSpriteBaseAnimation], a
 
 	; load Player's sprite for overworld
+	ld a, EVENT_PLAYER_GENDER
+	farcall GetEventValue
+	or a
 	ld a, SPRITE_OW_PLAYER
+	jr z, .got_player_ow_sprite
+	ld a, SPRITE_OW_MINT
+.got_player_ow_sprite
 	farcall CreateSpriteAndAnimBufferEntry
 	ld a, [wWhichSprite]
 	ld [wPlayerSpriteIndex], a

Besides the change in sprite, we also attributed the blue NPC color to the player character.

10. Adjust Main Menu screen

Almost done! As the last step, we will tweak the Main Menu screen so that it reflects the gender selection made by the player if there is save data. It would be weird to show the male character when the gender choice was female!

You might expect that DrawPlayerPortrait that was edited in a previous step would take care of this, but there's a caveat: wEventVars isn't loaded into WRAM until the player chooses the Continue From Diary option. The result is that the bit is 0 by default and so the game assumes a male portrait. This doesn't work for what we intend to achieve since we have to know the value of the event flag. Change HandleTitleScreen so that it loads only the event flags from SRAM (if there is actual save data), in src/engine/menus/start.asm:

 HandleTitleScreen:
 
 .start_menu
 	call CheckIfHasSaveData
+       ld a, [wHasSaveData]
+       or a
+       call nz, LoadEventsFromSRAM
 	call HandleStartMenu
 
 	...

 	cp START_MENU_CARD_POP
 	jr nz, .continue_duel
 	call ShowCardPopCGBDisclaimer
-	jr c, HandleTitleScreen
+	jp c, HandleTitleScreen
 .continue_duel
 	call ResetDoFrameFunction
 	call EnableAndClearSpriteAnimations

And just below write this new routine, LoadEventsFromSRAM:

        farcall ValidateBackupGeneralSaveData
        ret

+LoadEventsFromSRAM:
+       ld hl, sEventVars
+       ld de, wEventVars
+       ld bc, EVENT_VAR_BYTES
+       call EnableSRAM
+       call CopyDataHLtoDE
+       jp DisableSRAM
+
 ; handles printing the Start Menu
 ; and getting player input and choice
 HandleStartMenu:

And presto! You've successfully added a new player character option and selection screen. What's stopping you from creating more character options now?

female_player