Skip to content

Add female player character

ElectroDeoxys edited this page Aug 7, 2024 · 7 revisions

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. Adjust player portrait
  8. Adjust player OW sprite
  9. 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 A_BUTTON_F, a
	jr nz, .selection_made
	ldh a, [hDPadHeld]
	and D_RIGHT | D_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. 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.

8. 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.

9. 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