Replace Menu Account with a small clock in the corner of the screen - pret/pokecrystal GitHub Wiki

This tutorial will cover conversion of the "menu account" on the pause menu into a simple clock that displays the current time. I hadn't thought this would be a particularly applicable feature outside of my own hack since this information is available in the PokéGear, but I've gotten a few requests for a tutorial on it so here it is!

Contents

  1. Update the Option name
  2. Implement Menu Clock functionality

1. Update the Option name

Since we're changing what this option does fundamentally, we'll want to change its name to something more accurate. I've opted to swap it from "MENU ACCOUNT" to "MENU CLOCK" as follows:

Edit engine/menus/options_menu.asm:

	db "        :<LF>"
-	db "MENU ACCOUNT<LF>"
+	db "MENU CLOCK<LF>"
	db "        :<LF>"

Technically, you could end this step here, but your future self will thank you for writing organized, understandable code in the present (and if you don't the rest of this tutorial won't work without modification 😈). The rest of this step will be updating constants, comments, and labels for accuracy.

Edit constants/wram_constants.asm:

; wOptions2::
	const_def
-	const MENU_ACCOUNT ; 0
+	const MENU_CLOCK ; 0

; wWalkingDirection::

Edit data/default_options.asm:

; wGBPrinterBrightness: normal
	db GBPRINTER_NORMAL
-; wOptions2: menu account on
-	db 1 << MENU_ACCOUNT
+; wOptions2: menu clock on
+	db 1 << MENU_CLOCK

	db $00

Edit engine/menus/options_menu.asm again:

	const OPT_PRINT         ; 4
-	const OPT_MENU_ACCOUNT  ; 5
+	const OPT_MENU_CLOCK    ; 5
	const OPT_FRAME         ; 6
...
	dw Options_Print
-	dw Options_MenuAccount
+	dw Options_MenuClock
	dw Options_Frame
...
	ret

-Options_MenuAccount:
+Options_MenuClock:
	ld hl, wOptions2
	ldh a, [hJoyPressed]
	bit D_LEFT_F, a
	jr nz, .LeftPressed
	bit D_RIGHT_F, a
	jr z, .NonePressed
-	bit MENU_ACCOUNT, [hl]
+	bit MENU_CLOCK, [hl]
	jr nz, .ToggleOff
	jr .ToggleOn

.LeftPressed:
-	bit MENU_ACCOUNT, [hl]
+	bit MENU_CLOCK, [hl]
	jr z, .ToggleOn
	jr .ToggleOff

.NonePressed:
-	bit MENU_ACCOUNT, [hl]
+	bit MENU_CLOCK, [hl]
	jr nz, .ToggleOn

.ToggleOff:
-	res MENU_ACCOUNT, [hl]
+	res MENU_CLOCK, [hl]
	ld de, .Off
	jr .Display

.ToggleOn:
-	set MENU_ACCOUNT, [hl]
+	set MENU_CLOCK, [hl]
	ld de, .On

.Display:
...
.DownPressed:
	ld a, [hl]
	cp OPT_CANCEL ; maximum option index
-	jr nz, .CheckMenuAccount
+	jr nz, .CheckMenuClock
	ld [hl], OPT_TEXT_SPEED ; first option
	scf
	ret

-.CheckMenuAccount: ; I have no idea why this exists...
-	cp OPT_MENU_ACCOUNT
+.CheckMenuClock: ; I have no idea why this exists...
+	cp OPT_MENU_CLOCK
	jr nz, .Increase
-	ld [hl], OPT_MENU_ACCOUNT
+	ld [hl], OPT_MENU_CLOCK

.Increase:
...
; Another thing where I'm not sure why it exists
	cp OPT_FRAME
	jr nz, .NotFrame
-	ld [hl], OPT_MENU_ACCOUNT
+	ld [hl], OPT_MENU_CLOCK
	scf
	ret

Edit ram/wram.asm:

	db
wOptions2::
-; bit 1: menu account off/on
+; bit 1: menu clock off/on
	db
	ds 2

That's all for this step, but the game won't compile yet because there's still references to "Menu Account" in start_menu.asm. I've opted to save that file entirely for step two since all our functional changes will happen there.

2. Implement Menu Clock functionality

There are a lot of changes to make in this step, so I'll be breaking them up a bit with explanations of what we're accomplishing with these changes. Let's start with the fun part and handle the boring cleanup at the end.

Edit engine/menus/start_menu.asm:

	ret

-.MenuDesc:
-	push de
-	ld a, [wMenuSelection]
-	cp $ff
-	jr z, .none
-	call .GetMenuAccountTextPointer
-rept 4
-	inc hl
-endr
-	ld a, [hli]
-	ld d, [hl]
-	ld e, a
-	pop hl
-	call PlaceString
-	ret
-.none
-	pop de
-	ret
-
-.GetMenuAccountTextPointer:
+.MenuClockText:
+    push bc
+    push de
+    push hl
+    ldh a, [hHours]
+    ld b, a
+    ldh a, [hMinutes]
+    ld c, a
+    decoord 1, 16
+    farcall PrintHoursMins
+    pop hl
+    pop de
+    pop bc
+    ret
+
+.GetMenuEmptyTextPointer:
	ld e, a
	ld d, 0
...
	inc c
	ret

-.DrawMenuAccount:
-	jp ._DrawMenuAccount
+.DrawMenuClockTextBox:
+	jp ._DrawMenuClockTextBox

-.PrintMenuAccount:
-	call .IsMenuAccountOn
+.PrintMenuClock:
+	call .IsMenuClockOn
	ret z
-	call ._DrawMenuAccount
-	decoord 0, 14
-	jp .MenuDesc
+	call ._DrawMenuClockTextBox
+	jp .MenuClockText

-._DrawMenuAccount:
-	call .IsMenuAccountOn
+._DrawMenuClockTextBox:
+	call .IsMenuClockOn
	ret z
-	hlcoord 0, 13
-	lb bc, 5, 10
-	call ClearBox
-	hlcoord 0, 13
-	ld b, 3
-	ld c, 8
-	jp TextboxPalette
+	hlcoord 0, 15
+	lb bc, 1, 8
+	jp Textbox

-.IsMenuAccountOn:
+.IsMenuClockOn:
	ld a, [wOptions2]
-	and 1 << MENU_ACCOUNT
+	and 1 << MENU_CLOCK
	ret

.DrawBugContestStatusBox:

In this chunk of code, we've:

  1. Swapped out the plain white area of the menu account for a bordered textbox and resized it to fit neatly around the displayed time.
  2. Modified the text to display the current time regardless of the selected option on the pause menu.

But why did we rename that one function from GetMenuAccountTextPointer to GetMenuEmptyTextPointer?

Well, if we're just showing the time now, we don't have much use for those descriptions of the menu options anymore. Rather than fully stripping everything to do with them out, I've taken a simplified approach wherein I just replace them all with a single empty string to save a little memory.

Edit engine/menus/start_menu.asm again:

.Items:
; entries correspond to STARTMENUITEM_* constants
-	dw StartMenu_Pokedex,  .PokedexString,  .PokedexDesc
-	dw StartMenu_Pokemon,  .PartyString,    .PartyDesc
-	dw StartMenu_Pack,     .PackString,     .PackDesc
-	dw StartMenu_Status,   .StatusString,   .StatusDesc
-	dw StartMenu_Save,     .SaveString,     .SaveDesc
-	dw StartMenu_Option,   .OptionString,   .OptionDesc
-	dw StartMenu_Exit,     .ExitString,     .ExitDesc
-	dw StartMenu_Pokegear, .PokegearString, .PokegearDesc
-	dw StartMenu_Quit,     .QuitString,     .QuitDesc
+	dw StartMenu_Pokedex,  .PokedexString,  .EmptyDesc
+	dw StartMenu_Pokemon,  .PartyString,    .EmptyDesc
+	dw StartMenu_Pack,     .PackString,     .EmptyDesc
+	dw StartMenu_Status,   .StatusString,   .EmptyDesc
+	dw StartMenu_Save,     .SaveString,     .EmptyDesc
+	dw StartMenu_Option,   .OptionString,   .EmptyDesc
+	dw StartMenu_Exit,     .ExitString,     .EmptyDesc
+	dw StartMenu_Pokegear, .PokegearString, .EmptyDesc
+	dw StartMenu_Quit,     .QuitString,     .EmptyDesc

.PokedexString:  db "#DEX@"
...
.QuitString:     db "QUIT@"

-.PokedexDesc:
-	db   "#MON"
-	next "database@"
-
-.PartyDesc:
-	db   "Party <PKMN>"
-	next "status@"
-
-.PackDesc:
-	db   "Contains"
-	next "items@"
-
-.PokegearDesc:
-	db   "Trainer's"
-	next "key device@"
-
-.StatusDesc:
-	db   "Your own"
-	next "status@"
-
-.SaveDesc:
-	db   "Save your"
-	next "progress@"
-
-.OptionDesc:
-	db   "Change"
-	next "settings@"
-
-.ExitDesc:
-	db   "Close this"
-	next "menu@"
-
-.QuitDesc:
-	db   "Quit and"
-	next "be judged.@"
+.EmptyDesc:
+	db   "@"

.OpenMenu:
	ld a, [wMenuSelection]
-	call .GetMenuAccountTextPointer
+	call .GetMenuEmptyTextPointer
	ld a, [hli]
	ld h, [hl]
	ld l, a
	jp hl

.MenuString:
	push de
	ld a, [wMenuSelection]
-	call .GetMenuAccountTextPointer
+	call .GetMenuEmptyTextPointer
	inc hl

Not the best solution, but a quick, painless one for a tutorial frankly already overstaying its welcome. Still with me? Don't worry, we're almost there! We just need to clean up a few spots to account for changes to function names.

Edit engine/menus/start_menu.asm once more, with feeling!

ld a, [wBattleMenuCursorPosition]
	ld [wMenuCursorPosition], a
-	call .DrawMenuAccount
+	call .DrawMenuClockTextBox
	call DrawVariableLengthMenuBox
	call .DrawBugContestStatusBox
...
.Select:
	call .GetInput
	jr c, .Exit
-	call ._DrawMenuAccount
+	call ._DrawMenuClockTextBox
	ld a, [wMenuCursorPosition]
	ld [wBattleMenuCursorPosition], a
...
; Return carry on exit, and no-carry on selection.
	xor a
	ldh [hBGMapMode], a
-	call ._DrawMenuAccount
+	call ._DrawMenuClockTextBox
	call SetUpMenu
	ld a, $ff
	ld [wMenuSelection], a
.loop
-	call .PrintMenuAccount
+	call .PrintMenuClock
	call GetScrollingMenuJoypad
	ld a, [wMenuJoypad]
	cp B_BUTTON
...
	call ClearBGPalettes
	call Call_ExitMenu
	call ReloadTilesetAndPalettes
-	call .DrawMenuAccount
+	call .DrawMenuClockTextBox
	call DrawVariableLengthMenuBox
	call .DrawBugContestStatus
	call UpdateSprites

And with that, the game should compile and you should be greeted by a lovely new clock on your pause menu!

Example: Menu Clock on Options Menu Example: Menu Clock on Pause Menu

⚠️ **GitHub.com Fallback** ⚠️