Reduce the command queue system to just stone tables - pret/pokecrystal GitHub Wiki

One feature of the Gen 2 overworld engine is the "command queue". Event scripts may queue up to six entries with the writecmdqueue and delcmdqueue script commands. Then every time the player takes a step, the queued commands run depending on their type. In practice, the only usable command type is CMDQUEUE_STONETABLE, for pushing Strength boulders into holes. The other command types are dummied out or incomplete.

This tutorial simplifies the system to replace the entire queue with a single pointer to a "stone table", which may be null or may point to a table defining how Strength boulders interact with holes. It saves ROM and RAM space, and simplifies the map callbacks that set up stone tables.

Contents

  1. Remove the command queue–related constants
  2. Replace writecmdqueue and delcmdqueue with usestonetable and clearstonetable
  3. Replace MAPCALLBACK_CMDQUEUE with MAPCALLBACK_STONETABLE
  4. Update the map scripts that use stone tables
  5. Implement the simplified stone table system

1. Remove the command queue–related constants

Edit constants/script_constants.asm:

-; command queue members
-CMDQUEUE_TYPE  EQU 0
-CMDQUEUE_ADDR  EQU 1
-CMDQUEUE_02    EQU 2
-CMDQUEUE_03    EQU 3
-CMDQUEUE_04    EQU 4
-CMDQUEUE_05    EQU 5
-CMDQUEUE_ENTRY_SIZE EQU 6
-CMDQUEUE_CAPACITY EQU 4
-
-; HandleQueuedCommand.Jumptable indexes (see engine/overworld/events.asm)
-	const_def
-	const CMDQUEUE_NULL
-	const CMDQUEUE_TYPE1
-	const CMDQUEUE_STONETABLE
-	const CMDQUEUE_TYPE3
-	const CMDQUEUE_TYPE4
-NUM_CMDQUEUE_TYPES EQU const_value

2. Replace writecmdqueue and delcmdqueue with usestonetable and clearstonetable

Edit macros/scripts/events.asm:

-	const writecmdqueue_command ; $7d
-MACRO writecmdqueue
-	db writecmdqueue_command
-	dw \1 ; queue_pointer
-ENDM
+	const usestonetable_command ; $7d
+MACRO usestonetable
+	db usestonetable_command
+	dw \1 ; stonetable_pointer
+ENDM

-	const delcmdqueue_command ; $7e
-MACRO delcmdqueue
-	db delcmdqueue_command
-	db \1 ; byte
-ENDM
+	const clearstonetable_command ; $7e
+MACRO clearstonetable
+	db clearstonetable_command
+ENDM

(The clearstonetable command isn't really necessary, since maps are unlikely to need it and usestonetable NULL will do the same thing, but I'm including it for completeness.)

Then edit engine/overworld/scripting.asm:

-	dw Script_writecmdqueue              ; 7d
-	dw Script_delcmdqueue                ; 7e
+	dw Script_usestonetable              ; 7d
+	dw Script_clearstonetable            ; 7e
-Script_writecmdqueue:
+Script_usestonetable:
 	call GetScriptByte
-	ld e, a
+	ld [wStoneTableAddress], a
 	call GetScriptByte
-	ld d, a
+	ld [wStoneTableAddress+1], a
-	ld a, [wScriptBank]
-	ld b, a
-	farcall WriteCmdQueue ; no need to farcall
	ret

-Script_delcmdqueue:
+Script_clearstonetable:
 	xor a
-	ld [wScriptVar], a
+	ld [wStoneTableAddress], a
+	ld [wStoneTableAddress+1], a
-	call GetScriptByte
-	ld b, a
-	farcall DelCmdQueue ; no need to farcall
-	ret c
-	ld a, TRUE
-	ld [wScriptVar], a
	ret

And edit wram.asm:

-wCmdQueue:: ds CMDQUEUE_CAPACITY * CMDQUEUE_ENTRY_SIZE
+wStoneTableAddress:: dw

-	ds 40
+	ds 62

Now usestonetable .StoneTable will store the address .StoneTable in wStoneTableAddress, and clearstonetable will set wStoneTableAddress to NULL. The wStoneTableAddress pointer only needs two bytes, whereas wCmdQueue needed 24, so this saves 22 bytes of RAM.

3. Replace MAPCALLBACK_CMDQUEUE with MAPCALLBACK_STONETABLE

Edit constants/map_setup_constants.asm:

 ; callback types
 	const_def 1
 	const MAPCALLBACK_TILES
 	const MAPCALLBACK_OBJECTS
-	const MAPCALLBACK_CMDQUEUE
+	const MAPCALLBACK_STONETABLE
 	const MAPCALLBACK_SPRITES
 	const MAPCALLBACK_NEWMAP

Then edit engine/overworld/warp_connection.asm:

 HandleContinueMap:
-	farcall ClearCmdQueue
+	xor a
+	ld [wStoneTableAddress], a
+	ld [wStoneTableAddress+1], a
-	ld a, MAPCALLBACK_CMDQUEUE
+	ld a, MAPCALLBACK_STONETABLE
 	call RunMapCallback
 	call GetMapTimeOfDay
 	ld [wMapTimeOfDay], a
 	ret

4. Update the map scripts that use stone tables

Edit maps/BlackthornGym2F.asm:

 	def_callbacks
-	callback MAPCALLBACK_CMDQUEUE, .SetUpStoneTable
+	callback MAPCALLBACK_STONETABLE, .SetUpStoneTable

 .SetUpStoneTable:
-	writecmdqueue .CommandQueue
+	usestonetable .StoneTable
 	endcallback

-.CommandQueue:
-	cmdqueue CMDQUEUE_STONETABLE, .StoneTable ; check if any stones are sitting on a warp
-
 .StoneTable:
 	stonetable 5, BLACKTHORNGYM2F_BOULDER1, .Boulder1
 	stonetable 3, BLACKTHORNGYM2F_BOULDER2, .Boulder2
 	stonetable 4, BLACKTHORNGYM2F_BOULDER3, .Boulder3
 	db -1 ; end

And edit maps/IcePathB1F.asm:

 	def_callbacks
-	callback MAPCALLBACK_CMDQUEUE, .SetUpStoneTable
+	callback MAPCALLBACK_STONETABLE, .SetUpStoneTable

 .SetUpStoneTable:
-	writecmdqueue .CommandQueue
+	usestonetable .StoneTable
 	endcallback

-.CommandQueue:
-	cmdqueue CMDQUEUE_STONETABLE, .StoneTable ; check if any stones are sitting on a warp
-
 .StoneTable:
 	stonetable 3, ICEPATHB1F_BOULDER1, .Boulder1
 	stonetable 4, ICEPATHB1F_BOULDER2, .Boulder2
 	stonetable 5, ICEPATHB1F_BOULDER3, .Boulder3
 	stonetable 6, ICEPATHB1F_BOULDER4, .Boulder4
 	db -1 ; end

Both of these maps allow you to push boulders into holes with Strength. Now they use the new callback type and event commands.

Previously, the writecmdqueue event command took a pointer to a command queue definition (.CommandQueue), and that command queue then pointed to its own data depending on its type (so the CMDQUEUE_STONETABLE had the .StoneTable pointer with stonetable data). Now, the usestonetable event command directly takes a .StoneTable pointer to stonetable data, without needing an intermediate cmdqueue.

5. Implement the simplified stone table system

Edit engine/overworld/events.asm:

 HandleMap:
 	call ResetOverworldDelay
 	call HandleMapTimeAndJoypad
-	farcall HandleCmdQueue ; no need to farcall
+	call HandleStoneTable
 	call MapEvents

Then edit engine/overworld/cmd_queue.asm:

-ClearCmdQueue::
-	...
-
-HandleCmdQueue::
-	...
-
-GetNthCmdQueueEntry: ; unreferenced
-	...
-
-WriteCmdQueue::
-	...
-
-DelCmdQueue::
-	...
-
-HandleQueuedCommand:
-	...
-
-CmdQueues_AnonJumptable:
-	...
-
-CmdQueues_IncAnonJumptableIndex:
-	...
-
-CmdQueues_DecAnonJumptableIndex:
-	...
-
-CmdQueue_Null:
-	...
-
-CmdQueue_Type1:
-	...
-
-CmdQueue_Type4:
-	...
-
-CmdQueue_Type3:
-	...
-
-CmdQueue_StoneTable:
+HandleStoneTable::
+	ld hl, wStoneTableAddress
+	ld a, [hli]
+	ld b, [hl]
+	ld c, a
+	or b
+	ret z

 	ld de, wPlayerStruct
 	ld a, NUM_OBJECT_STRUCTS
 .loop
 	push af

 	...

 	pop af
 	dec a
 	jr nz, .loop
 	ret

 .fall_down_hole
 	pop af
 	ret

Everything gets deleted except CmdQueue_StoneTable, which we rename to HandleStoneTable and add some code at the beginning to use wStoneTableAddress (or return if it's NULL).

Finally, edit home/stone_queue.asm:

.IsObjectInStoneTable:
	inc e
-	ld hl, CMDQUEUE_ADDR
-	add hl, bc
-	ld a, [hli]
-	ld h, [hl]
-	ld l, a
+	ld h, b
+	ld l, c

Since HandleStoneTable now stores the wStoneTableAddress value in bc, here we need to use that value instead of getting the relevant part of some command queue data.

With these changes, the Blackthorn Gym and Ice Path puzzles with Strength boulders will work just like before, but the code and data are more efficient.

Doing all this frees a mere 357 bytes or 0.018% of the 2 Mb ROM. But every little helps!