More space for code with Bank 'boomerangs'!

Hello everyone!
I have seen a lot of people starting to running out of the limited space they have available for code in bank 14.
I ran into this issue as well, but thanks to a huge amount of help from Kasumi I have an extremely effective workaround.

Warning: this is a long read and an advanced topic.

You might use this if you want:
More complicated code for player movement and controls
More complicated / varied monster AI
You keep running into overflow on bank 14 and you need a bit more space.

This method will get you a -TON- more space. An entire extra 16kb bank full at minimum.
It will cost a small amount of space in the static bank for every subroutine created.


Before you go further though, understand a few things:

-This is going to be an advanced look at nesmaker scripting, and you will be modifying where nesmaker stores things. Before continuing please make sure you understand how to
make and use a subroutine. PLEASE PLEASE make a backup, because this can break your entire project if you get something wrong.
-I'm hesitant to share this so close to the release of the new version of Nesmaker, because some of this won't be
valid anymore. This tutorial is strictly for games using the 4.1.5 version of Nesmaker. It is intended to help those
not planning to update to finish their games.


Ok, with that out of the way, a quick primer on the concept of what is happening:

Our Nesmaker games have their data stored in various banks. Some have gameplay logic code, some have graphics, some have text, etc.
At any one time, we have two of these banks loaded into memory: The static bank, and the swappable bank. The static bank contains
code that must be accessible at -all- times and as such is always loaded into memory. To access the rest of the memory, the swappable
bank is constantly being changed.

Most of the scripting you guys have been doing, with character movement and monster ai is all limited to bank 14, which contains most
of the game code. In order to have more space than just bank 14 you need to swap out of bank 14 to another bank. But here is where the
difficulty happens. Everything runs linearly on the NES. If you try and swap to another bank from bank 14, all you are going to do is
crash the game. This means that any code that moves from one bank to another one -must- be in the static bank.

Whats more, the way bank swapping is done, only the previous bank visited can be remembered. If you bank swap more than once without
returning to the original bank, you will lose the data you need to get back to where you started and the game will crash.

Almost all issues you will run into when tinkering with this is that the path back to the correct bank will be lost.

So how do you move over into the static bank from where you are in bank 14, and how do you make sure you can get back to the bank you started from?


I used a method that I called 'bank boomerangs'.
It combines using subroutines and bank switching to reach out to another bank, execute a block of code, and return right back to where you left from.


Here comes the actual tutorial:

==== Step 1 ====
Create a new script in GameEngineData>Routines>YourModule>InitializationScripts called
'bankBoomerangs.asm'
replace 'YourModule' with the actual module you are using.

Copy and paste this code into the script:

Code:
;; Boomerang template: ---------------------
;;boomerang_subroutineName:
    ;;; Change to bank BANKNUM
    ;LDA currentBank
    ;STA prevBank
    ;LDY #$BANKNUM ;; Bank to use
    ;JSR bankswitchY
    
    ;;; Call the subroutine
    ;JSR subroutineName
    
    ;;; Return to the previous bank
    ;LDY prevBank ;; Bank we came from
    ;JSR bankswitchY
    ;RTS
;; ----------------------------------------

JMP skipBoomerangs ;; Make the subroutines callable.

;; === ADD BOOMERANGS HERE ===

;; ===========================

skipBoomerangs: ;; end of script


You are done with that one for now. It won't do anything yet but it gives you a framework
to work with.

Now go to the nesmaker editor> project settings>script settings and add a new script define:
Name: Bank Boomerangs
Define: SCR_BANK_BOOMERANGS
Script: Routines\YourModule\InitializationScripts\bankBoomerangs.asm
- replace YourModule with the actual module you are using.
This will let you edit the script from within nesmaker.

==== Step 2 ====
Now open up this script:
GameEngineData>Routines>YourModule>MainASM.asm
-replace YourModule with the actual module you are using.
This script is the main game loop, and the code that is loaded in the static bank.
This is where you are going to link the new script you created.
Scroll down or search for this line: '.include "GameData\HUD_DEFINES.dat"'
add one line here:

Code:
.include ROOT\InitializationScripts\bankBoomerangs.asm

==== Step 3 ====
Ok, now you have the tools necessary to access other banks from your code bank.
Now we need to actually get some stuff in the other banks!

I recommend using bank 18, because it is completely empty! Pick wherever you want (as long as there isn't already something there), but the rest of this tutorial will be assuming you are using bank 18.

First you will need something to put there. Lets create a script that simply resets
the game.

Create a new script somewhere, I have a 'custom' folder inside my routines folder.
name it 'test.asm'

inside the script make this simple subroutine:
Code:
testSubroutine_reset:
    JMP RESET
    RTS

Now go to the nesmaker editor> project settings>script settings and add a new script define:
Name: Test Subroutine
Define: SCR_TEST_SUBROUTINE
Script: PATHTOYOURTESTSCRIPT\test.asm
- replace PATHTOYOURTESTSCRIPT with the actual address to your script.
We need the script defined in order to put it in bank 18.

Go to GameEndineData>Routines>YourModule>BankData and open the script Bank18.asm
it should be empty. If for some reason it isn't you may want to find one that is.

anyways add this line:
Code:
.include SCR_TEST_SUBROUTINE

Ok, now you actually have something in Bank 18!
Now how to actually call that code from over in bank 14?

You need to go back and edit your bankBoomerangs.asm script one more time.

Add this block of code after ;; === ADD BOOMERANGS HERE ===

Code:
boomerang_test:
    ;;; Change to bank 18
    LDA currentBank
    STA prevBank
    LDY #$18 ;; Bank to use
    JSR bankswitchY
    
    ;;; Call the subroutine
    JSR testSubroutine_reset
    
    ;;; Return to the previous bank
    LDY prevBank ;; Bank we came from
    JSR bankswitchY
    RTS

What this does is creates a link between bank 14 and bank 18 through the static bank.
Now, in bank 14 you can call:
'JSR boomerang_test'
and it will run testSubroutine_reset in bank 18.

Congratulations! You now have all the tools in place that you will need.

From here, what should you do to take advantage of this?
-You can get rid of the script for test.asm and the code to access it, it was just
to teach you how to use it.
-Add any scripts that you want to bank 18 in the same way you added test.asm
-I included the template at the top of bankBoomerangs.asm so that you can easily copy and paste it when you need access. You will need to create a separate 'boomerang' in this file for each subroutine you want to access in bank 18.
-Scripts you add in this way ABSOLUTELY MUST be designed as subroutines. This is because
it depends on using RTS to get back to the static bank when you have finished. You can
create and call new subroutines inside of bank 18 as long as they always return correctly.

If you have any further questions I will try and answer them.
 

Dirk

Member
Very cool! Thanks for sharing this with us! I'll definitely save this for future reference :)
 

n8bit

Member
Moving the PreDraw script from $14 to another empty bank screws with the scrolling platformers bad. Caused my game to have jumbled HUD and it was autoscrolling. Here I thought it was the other method I was using, similar to Dale Coop's method, but turns out scrolling platformers don't like it when you move the PreDraw.
 

Bucket Mouse

Active member
Now that we have a method for switching banks, let's try to do stuff with it. Specifically, let's see if we can use Bank 18 to store more text!

I followed the loop of programming that runs the reading of text data and was thoroughly confused. I asked Joe on Facebook for help understanding it, and he replied with this:


This is fundamental ASM addressing mode stuff (Yes, still advanced!). If you didn't follow it from what I just said, you might have a difficult time with it overall as there are probably things between here and there that you'll need to grasp. But here's another quick attempt.

Let's say I have a label for my text in bank 4 (arbitrary). Inside bank 4, I have:

MyText:
.db # _T, # _E, # _X, # _T, #$FF

The first bit of knowledge is effectively we want to read "MyText" letter by letter. Usually we'll use "y" as our offset. So CONCEPTUALLY, I'm doing a loop...

Load MyText + y
Write the result to the screen.

In ASM, that is effectively (we're not done yet) MyText,y.

But the problem is that I need to find a way to get to that particular address, which may be variable, and I need a hi and low address value to get there, which is <MyText and >MyText, effectively. Then, writing this:

LDA (MyText),y

...would get me the y offset from the label MyText.

So you could:
- Make sure you're doing this call from the static bank
- change banks to where the text is...ANY bank.
- Load your offset into y
- Load <Label into temp16
- Load >Label into temp16+1
(this could be substitute by any consecutive variables that will act as a 16 bit pointer)
- LDA (Label),y
- Store that somewhere so you can work with it.
- Return bank.

And now you have the character that is y position after the Label stored wherever you stored it (a temp variable, probably, so long as that variable isn't used in any other routines between storing it and when you have to do something with it.

Chances are now what you'll want to do is put it in some ram buffer that will be read and written to PPU data during the NMI, and then increase your "text offset", to repeat again in the next frame.

That's the logic. If any of that tripped you up, those are the things to focus on. It is a deep dive into the ASM and understanding how addressing modes work. Again, you might want to go smaller. Try doing something like:

- Make a table called MyWhatever in bank 18
MyWhatever:
.db #$07, #$09, #$00

Then, on a button press, do this:
- switch banks into bank 18.
- Load <MyWhatever into temp16
- Load >MyWhatever into temp16+1
- Load 0 into y.
- Load (MyWhatever),y
- CMP #$07
- BNE skipIt
- JMP RESET
-skipIt:

Then you can try loading 1 into y and compare it to 9. Then you can try loading 2 into y and compare it to 0. See if you can make it give you the expected results.


After some practice I was able to get the "smaller" exercise running. I put these in Bank 18:


Code:
MyWhatever:
.db #$07, #$09, #$00

.include BANKTEST


I made an ASM file mapped to the Select button that just said this:


Code:
JSR bankstuff


"Bankstuff" I put in Bank Boomerangs below the previous sobroutine:


Code:
bankstuff:
  ;;; Change to bank 18
    LDA currentBank
    STA prevBank
    LDY #$18 ;; Bank to use
    JSR bankswitchY
  
JSR banktest2

    ;;; Return to the previous bank
    LDY prevBank ;; Bank we came from
    JSR bankswitchY
    RTS


And here is banktest2, which is registered in Script Settings as "BANKTEST":


Code:
banktest2:

	LDA #<MyWhatever
	STA temp16
	LDA #>MyWhatever
	STA temp16+1
	LDY #$01
	LDA (MyWhatever),y
	CMP #$09
	BNE skipit
	JMP RESET
skipit:
RTS

And now when I push Select it resets the game. On to the heavy stuff, I guess.
 

Bucket Mouse

Active member
I may have figured this out.

I followed the trail of how the text loads itself. It starts in CheckForUpdateScreenData, right here:

Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GET TEXT STRINGS FOR THIS NEW SCREEN
LDA #SCREEN_DATA_OFFSET
	CLC
	ADC #$22
	TAY 
	LDA (collisionPointer),y
	STA stringGroupPointer
	;;;;;;;;;;;;;;;;;;;;;;
	;;;;;;;;;;;;;;;;;;;;;;;
	;;;;;;;;;;;;;;;;;;;;;;; Put text into screenText variables
	
	JSR getTextForScreen
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

The SCREEN_DATA_OFFSET is listed in Constants as #$78. No idea why it has to be 78.
22 is added to this and then it's stored into Y. No idea why that happens either.
ColisionPointer is the hi and lo data for the particular screen we are on.
CollisionPointer is combined with Y to make stringGroupPointer (for some reason) and then....

It jumps to getTextForScreen which is on LoadScreenData.asm.

Code:
getTextForScreen:
	LDA currentBank
	STA prevBank
	LDY #$16
	JSR bankswitchY
	
	;; 16 bit addressing
	;; Find the address the text groups begin at.
	LDA #<AllTextGroups
	STA temp16
	LDA #>AllTextGroups
	STA temp16+1

AllTextGroups is in bank 16 and says this:

Code:
AllTextGroups:	
	.include "ScreenData\npcTextGroup.dat"

It still isn't the text. All of this is loading the numbers that select the text GROUP. To switch the banks I need to find the precise moment where the text load happens.
This is the rest of getTextForScreen. You would think it happens here, but we aren't in Bank 17 yet.

Code:
;; Add the hi end
	LDA stringGroupPointer ;; Get the text group for this screen
	ASL ;;Shift them left twice, to make each text group equal to 4 entries.
	ASL
	CLC
	ADC temp16 ;; We need more space so we do not lose left 2 bits, store it in temp16
	STA temp16
	
	;;grab the carry flag if it exists
	LDA #$00
	ADC #$00 ;; this draws out the carry bit by not clearing it before addition.
	STA temp
	
	;; Add the lo end
	LDA stringGroupPointer ;; Get the text group for this screen
	;; get the left 2 bits if they exist
	AND #%11000000
	LSR ;; shift them to the far right side
	LSR
	LSR
	LSR
	LSR
	LSR
	CLC
	ADC temp16+1 ;; add to the lo end of temp16
	ADC temp ;; add the carry if it exists
	STA temp16+1	
	
	;;load the 4 text entries from our new address
	LDY #$00
-
    	LDA (temp16), y
    	STA (screenText), y
   	INY
    	CPY #$04
    	BNE -

	LDY prevBank
	JSR bankswitchY		
	RTS

Bank 17 isn't mentioned until HandleBoxes.asm, at this part:

Code:
notChangingAttributes:
	
	LDA currentBank
	STA prevBank
	LDY #$17
	JSR bankswitchY
	JSR HandleTextBox
	LDY prevBank
	JSR bankswitchY
	RTS

It switches to Bank 17 and it goes directly to HandleTextBox from there. Notice how heavily everything is tied to the Text Groups. The math that determines which text entries to pick up seems to be calculated FROM the text group settings, which as we covered, only go up to 256 and can't be expanded.

So if I can't get rid of the text group thing, maybe I can trick what does exist into reading from the second text thing (npcText02.dat). Meaning I could pick text entry 00 as the first entry in a group, then redirect the program to Bank 18 at the point above, and have it read entry 256 instead! I made an entry for 256, "Wiggity Wiggity," and went to work.

I quickly found out that I couldn't assign HandleTextBoxes to Banks 17 and 18 at the same time -- I would get "these names repeat" errors. That meant I had to duplicate the file as HandleTextBoxes2, and then painstakingly go into the asm and add a "2" to every single name that referred to another part in the same code. Yes, I really did this......and then I changed LDY #$17 TO #$18, changed the JSR to HandleTextBox2, compiled and hoped for the best.

I got gibberish:

game-11.png

So I took another look at what I had at Bank18.asm.

Code:
.include SCR_HANDLE_TEXTBOX2

.include "ScreenData\npcText02.dat"

stringsTableLo2:
	.db <Text256, <Text257, <Text258, <Text259, <Text260, <Text261
stringsTableHi2:
	.db <Text256, <Text257, <Text258, <Text259, <Text260, <Text261

Can you spot what's wrong here? You can if you compare it to Bank17.asm. I had copy-pasted the first .db line, and it turns out the < marks actually have to go in the opposite direction on the "Hi" part.

Code:
stringsTableLo2:
	.db <Text256, <Text257, <Text258, <Text259, <Text260, <Text261
stringsTableHi2:
	.db >Text256, >Text257, >Text258, >Text259, >Text260, >Text261

And once I compiled this....

game-13.png

SUCCESS

.....Testing to come; more later.
 

Bucket Mouse

Active member
Looks like this works, so here's how to fill Bank 18 with text....

First make the variable bank18switch in User Variables.

Then copy all this into Notepad and save it as HandleTextBox2.asm in Routines/nameofyourproject/System, next to the original HandleTextBox:

Code:
HandleTextBox2:

;; textboxHandler
 ; 7 - 6 - 5 - 4 - 3 - 2 - 1 - 0
  ;7 = Textbox is active.
   ;    6 = Black box is being created, will then create text.
    ;        5 = Attribute update to black.
     ;           4 = text is being created.
      ;             = loop to 5 if more text.
       ;             3 = Black box is being created, with then restore NT
        ;                 2 = Attributes to main NT
         ;                     1 = restore NT
          ;                          0 = check for "more" text.
			


	LDA textboxHandler
	AND #%10000000
	BNE textboxIsActive2
	RTS ;; textbox is inactive.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
textboxIsActive2:
	LDA updateNametable
	BEQ notAlreadyWritingToNT2
	RTS
notAlreadyWritingToNT2
	;HideSprites
;; DO TEXT BOX STUFF.
	;; Which text box stuff to do is determined by the textboxHandler byte.
	;; if it is active, but all other bits are inactive, that means we have just activated and we need to turn
	;; this system on.
	LDA textboxHandler
	AND #%01111111
	BNE textboxSettingsAlreadyOn2
	;;; get textbox settings
 ;; skip resetting the offset.
	LDA #$00
	STA updateNT_offset
	STA updateHUD_offset

	STA updateNT_H_offset
	STA updateNT_V_offset
	
	;;; zero out the things offsets and start creating the blackout box.
	LDA #%11000000
	STA textboxHandler ;; flow right into the next.
textboxSettingsAlreadyOn2:
	
	LdA textboxHandler
	AND #%01000000
	BEQ notCreatingBlackBox2
	;;;; CREATE THE BLACK BOX.
	;;;; The frist phase is to create the black box.
	;;;; no matter what color the text box will be, or which palette it will use
	;;;; it will always first create a box of "blanks" so it can be changed to whatever
	;;;; background attribute you'd like without noticing the attribute change.
	JSR CreateBlackBox2
	;;; now the black box has been created.
	
notCreatingBlackBox2:
	LDA textboxHandler
	AND #%00100000
	BEQ notSettingTextboxAttributes2
	JSR isWritingTextboxAttributes2
	RTS
	
	;;; set textbox attributes.
notSettingTextboxAttributes2:
	LDA textboxHandler
	AND #%00010000
	BEQ notUpdatingTextboxText2
	;; updating textbox text.
	JSR isWritingTextToTextbox2

	RTS
notUpdatingTextboxText2:
	LDA textboxHandler
	AND #%00001000
	BEQ notErasingTextboxText2
	JSR CreateBlackBox2
	RTS
notErasingTextboxText2:
	LDA textboxHandler
	AND #%00000010
	BEQ notRestoringNametables22
;	JSR CheckForEndOfTextString
;	LDA gameHandler
;	AND #%00100000
;	BEQ notRestoringNametables2 ;; because we have finished.
	JSR RestoreNametableData2
notRestoringNametables22
	RTS
	

	
	
	
	
	
	
	
	
	
	
	
	
	
getUpdateTileOffsetPosition2:
	LDA xScroll
	LSR
	LSR
	LSR
	LSR
	CLC
	ADC	#BOX_1_ORIGIN_X
	CLC
	ADC updateNT_H_offset
	STA tileX
	
	LDA #BOX_1_ORIGIN_Y
	CLC
	ADC updateNT_V_offset
	STA tileY
	JSR coordinatesToMetaNametableValue
	;;; spits out updateNT_pos as low and updateNT_pos+1 as hi of address to write.
	;;; right now, this is in terms of a single nametable, starting at $20 as the high top left corner high byte.
	
	

	
;;;; FOR HITE BYTE:
;;;; Take columnTracker and divide by 2 so you get a value 0-16
;;;; Add that number to BOX_1_ORIGIN_X.
;;;; If the sum is less than 16, this should stay in the same nametable.
;;;; Otherwise, it should cross nametables.
	Ldy columnTracker
	LDA columnTracker
	AND #%00001111
	CLC
	ADC #BOX_1_ORIGIN_X ;; plus offset of what tile you're drawing.
	CLC
	ADC updateNT_H_offset
	AND #%00010000
	BNE +
	;;; same nametable
	LDA columnTracker
	AND #%00010000
	BNE +++
	;; this started in even table,
	;; so it should stay in even table
	JMP updateIsEvenTable2
+++	
	;;; this started in odd table
	;;; so it should stay in odd table.
	JMP updateIsOddTable2
+
	;;; different nametable
	LDA columnTracker
	AND #%00010000
	BNE +++
	;; this started in even table
	;; so it should now be odd table.
	JMP updateIsOddTable2
+++
	;;; this started in odd table
	;;; so it should now be even table.
	JMP updateIsEvenTable2

	

updateIsEvenTable2:
	LDA updateNT_pos+1
	AND #%00000011 ;; 0,1,2 or 3
	clc
	adc #$20
	STA updateNT_pos+1
	JMP gotHiUpdatePos2
	
updateIsOddTable2:
	LDA updateNT_pos+1
	AND #%00000011 ;; 0,1,2 or 3
	CLC
	ADC #$24
	STA updateNT_pos+1
	JMP gotHiUpdatePos2
gotHiUpdatePos2:

	RTS
	
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;	
checkNTforNewTile2:
	LDA temp3; updateNT_pos
	AND #%00100000
	CMP #%00100000
	BEQ newTileHasCrossedThreshold2
	;; new tile has not crossed threshold.
	;; same nametable

	LDA columnTracker
	AND #%00010000
	BNE updateIsOddTable2
	JMP updateIsEvenTable2
newTileHasCrossedThreshold2:
	
	LDA columnTracker
	AND #%00010000
	BNE updateIsEvenTable2
	JMP updateIsOddTable2
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	;RTS ;; redundant, unnecessary
	
	
isWritingTextToTextbox2:
	;;; already in bank 17.
	;; we need to get positioning.

	LDA xScroll
	LSR
	LSR
	LSR
	STA temp
	LDA #BOX_1_ORIGIN_X
	ASL
	CLC
	ADC updateNT_H_offset
	CLC
	ADC temp
	STA temp3
	AND #%00011111
	STA tileX
	
	LDA #BOX_1_ORIGIN_Y
	ASL
	CLC
	ADC updateNT_V_offset
	STA tileY
	
	JSR coordinatesToNametableValue
	JSR checkNTforNewTile2
	;; temp has position value.
	;LDA temp
	;STA updateNT_pos
	
	
	
	LDY textVar ;; what string
	LDA screenText,y
	TAY
	LDA stringsTableLo2,y
	STA temp16
	LDA stringsTableHi2,y
	STA temp16+1
	LDY textboxOffsetHold
	LDA (temp16),y
	
	;; this is where we determine if this is a special character or a normal letter/number to update.
	CMP #_END
	BNE +
;;;; END:
EndText2:
	;; this is and _END value.
	;; it turns off writing to the textbox.
	
	LDA textboxHandler
	AND #%01111111
	STA textboxHandler
	;;;;; textbox handler stays on the same state
	;;;;; but deactivates.
	;;;;; it will start again if bbutton is pressed again
	;;;;; on state 00001000, which will begin the 'turn off' process.
	
	JMP doneTextUpdate2
+
	CMP #$FE ;; is it a new line?
	BNE notANewLine_text2
;;;; NEW LINE:


	LDA #$00
	STA	updateNT_H_offset
	INC updateNT_V_offset
	INC textboxOffsetHold
	
	JMP doneTextUpdate2
notANewLine_text2:

	CMP #_ENDTRIGGER
	BNE notEndTrigger2
	;; is an end trigger
	INC textboxOffsetHold ;; get the very next value.
	LDY textboxOffsetHold
	LDA (temp16),y
	STA temp
	;;;; this now has the trigger to change.
	TriggerScreen temp
	JMP EndText2
notEndTrigger2:

	CMP #_ENDITEM 
	BNE notEndItem2
	;;; Give Item triggers the warp variable.

	;; end of the tile script


	JMP EndText2
	
notEndItem2:
	CMP #_MORE
	BNE notMoreText2
	LDA #%10000000
	STA textboxHandler
	;; flip a more text flag.
	LDA #$01
	STA moreText
	INC textboxOffsetHold
	JMP EndText2
notMoreText2:
;;;; NORMAL VALUE
		;;;;; The look up was a normal letter, number, or other hud value.
	CLC 
	ADC #$C0
	STA updateHUD_fire_Tile	
	
	LDA updateNT_pos
	STA updateHUD_fire_Address_Lo
	LDA updateNT_pos+1
	STA updateHUD_fire_Address_Hi
	INC updateNT_H_offset
	INC textboxOffsetHold
doneTextUpdate2:	

	RTS
	
	
	
	
	
	
	
	
	
	
	
CreateBlackBox2	
	LDX #$00
	
;;; DRAW METATILE 1
	;; this macro SETS it to change on the next vblank update.
	;; starting at address hi-lo, with the tile in the third argument.
	;; if it sees #BLANK_TILE, it will create a meta tile of four blank tiles.
	;; if it sees any other value, it will create a metatile starting with 
	;; that index as the top left corner.
	
	;;;;;;; IF THE GAME DOES NOT SCROLL:
	;;;;;;; You can simply read the box value, do a little math, to get the offset
	;;;;;;; of the address for the top left corner, and skip the whole business of
	;;;;;;; finding the offset based on the scroll.
	
	;;;;;;; IF THE GAME DOES SCROLL:
	;;;;;;; YOu will need to get the offset value so it is at the box value position
	;;;;;;; compared to the CAMERA rather than zero.
	
	;;;;;;; Fortunately, we already have tables set up for columnTableLo and columnTableHi
	;;;;;;; which we use to handle the column for scrolling.  
	;;;;;;; we can use the columnTracker value to help determine the proper offset.
	
	;;;;;;; There can only be 32 columns.
	
	;;;;;;; And we will use updateNT_offset to continue this over frames, 
	;;;;;;; and to handle tile to offset.
	;;;;;;; this will be set to zero wherever HandleTextBox is activated.
	;;;;;;; It needs to, in some way, be boolean (like a key press, or using a boolean var).
	; LDA #$00
	; STA updateNT_H_offset
	; STA updateNT_V_offset
	;; THESE ALSO MUST BE SET IN THE BOOL VAR PLACE
	;; Whatever code is calling this must also set these to zero.

	
	
	
	LDA #$00
	STA tilesToWrite
	LDA #$04
	STA dummyVar2
DoLoopThing2:
	JSR getUpdateTileOffsetPosition2
	SetMetaTileToChange updateNT_pos+1, updateNT_pos, #BLANK_TILE

 	INC updateNT_H_offset
	LDA updateNT_H_offset
	CMP #BOX_1_WIDTH
	BEQ ++
	DEC dummyVar2
	LDA dummyVar2
	BEQ +
	JMP DoLoopThing2
++ ;; width has been reached.
	;; now to test the height, and to move h pos back to the left.

	
	BEQ dontEndCreatingBlackBox2
	JMP EndCreatingBlackBox2
dontEndCreatingBlackBox2:
	
	LDA #$00
	STA updateNT_offset
	STA updateHUD_offset
	STA updateNT_H_offset
	INC updateNT_V_offset


+	

	LDA #$01
	STA updateNametable  ;; turn on write.
	
	LDA updateNT_V_offset
	CMP #BOX_1_HEIGHT
	BCS turnOffCreatingTextBox2
	
	LDA updateNT_H_offset
	CMP #BOX_1_WIDTH
	BEQ turnOffCreatingTextBox2
	JMP EndCreatingBlackBox2
turnOffCreatingTextBox2:
	
	;;;;;;;;;;;;;;;;;;;;;;;;;;
	;;; Setup text specifics
	LDA #$00
	STA stringGroupOffset
	STA updateNT_H_offset
	STA updateNT_V_offset
	
	LDA xScroll
	LSR
	LSR
	LSR
	STA temp
	LDA #BOX_1_ORIGIN_X
	ASL
	CLC
	ADC temp
	CLC
	ADC #H_PAD_TEXTBOX
	STA tileX
	LDA #BOX_1_ORIGIN_Y
	ASL
	CLC
	ADC #V_PAD_TEXTBOX
	STA tileY
	
;	LDA #$01
;	STA writingText ;; turn on writing text.
	LDA moreText
	BEQ +
	LDA #$00
	STA moreText
	JMP ++
+
	LDA #$00
	STA textboxOffsetHold
++
	STA updateNT_H_offset
	STA updateNT_V_offset

	LDA textboxHandler
	AND #%00001000
	BEQ continueToUpdatingAttributes2
	;;; is turning textbox OFF, so no writing to text.
	;;; will have to allot for changing attributes back with the
	;;; bit 00000100
	LDA #%10101000 ;; turn on attribute update
	STA textboxHandler
	LDA #$00
	STA updateNT_H_offset
	STA updateNT_V_offset
	STA updateNT_offset
	STA updateNT_compensation

;	LDA xScroll_hi

;	AND #%00000001
;	BEQ oddToEven2
;	LDA #$27
;	JMP gotNtDeets2
;oddToEven2:
;	LDA #$23
;gotNtDeets2:
;	STA updateNT_tableLeft
;	STA updateNT_details

	;LDA columnTracker
	LDA #BOX_1_ORIGIN_X
	LSR
	TAY
	LDA attrColumnTableHi,y
	STA updateNT_tableLeft
	STA updateNT_details

	RTS
continueToUpdatingAttributes2:
	LDA #%10001000
	STA textboxHandler
	LDA #$00
	STA updateNT_H_offset
	STA updateNT_V_offset
	STA updateNT_offset
	STA updateNT_compensation
	
	
;	LDA xScroll_hi

;	AND #%00000001
;	BEQ oddToEven
;	LDA #$27
;	JMP gotNtDeets:
;oddToEven:
;	LDA #$23
;gotNtDeets:
;	STA updateNT_tableLeft
;	STA updateNT_details
	
	;LDA columnTracker
	LDA updateNT_H_offset
	CLC
	ADC updateNT_offset
	ASL
	CLC
	ADC	columnTracker
	AND #%00011111
	LSR 
	TAY
	LDA attrColumnTableHi,y
	STA updateNT_details
	
	LDA #%10100000
	STA textboxHandler
	;;;;;;;;;;;;;;;;;;;;;;;;;;;
	RTS
EndCreatingBlackBox2:	
	LDA #$01
	RTS
	
	
	
	
RestoreNametableData2:

	

	;;; FIRST we need to find the metaNametale value from the ROM.
	;;;;;;;;;;LESSER PRIORITY;;;;;;;;;;;;;;
	;;; THEN we need to check it againt collision type to / screen state to see if it should be 
	;;; change (for instance, a tile that changes at night / saved / changes if no monsters, if there are no monsters, etc)
	;;; THEN, we should probably always just restore the hud at the end.
	;;; the problem is, the data we need to fetch is in bank 16, then screen bank, 
	;;; while we are currently in bank 17 with this routine.
	;;; so restoration of nametable, or at least analysis of whate tiles to write, will have to happen OUTSIDE of this routine
	;;; We handle it in HandleBoxes, which will populate updateTile00-03, and respect paths.
	;;; And we handle POSITION to update here.

	LDX #$00
	LDA #$00
	STA tilesToWrite
	
	JSR getUpdateTileOffsetPosition2
	
	

	LDA updateNT_pos
	STA temp
	LDA updateNT_pos+1
	STA temp1
	

	SetTileToChange temp1, temp, updateTile_00
	LDA temp
	CLC
	ADC #$01
	STA temp2
	LDA temp1
	ADC #$00
	STA temp3
	
	
	SetTileToChange temp3, temp2, updateTile_01
	
	LDA temp
	CLC
	ADC #$20
	STA temp2
	LDA temp1
	ADC #$00
	STA temp3
	
	
	SetTileToChange temp3, temp2, updateTile_02
	
	LDA temp
	CLC
	ADC #$21
	STA temp2
	LDA temp1
	ADC #$00
	STA temp3
	
	SetTileToChange temp3, temp2, updateTile_03
	INC tilesToWrite
	LDA #$01
	STA updateNametable
	
	INC updateNT_H_offset
	LDA updateNT_H_offset
	CMP #BOX_1_WIDTH
	BNE dontReturnToGame2
	LDA #$00
	STA updateNT_H_offset
	INC updateNT_V_offset
	LDA updateNT_V_offset
	CMP #BOX_1_HEIGHT
	BNE dontReturnToGame2
	LDA #$00
	STA textboxHandler
	STA updateHUD_offset
	LDA gameHandler
	AND #%11011111
	STA gameHandler
	;ShowSprites

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; check to see if there is behavior after a text box.

;;;; THIS WOULD WARP YOU TO A SCREEN AFTER TEXTBOX.
	; LDA #$01
	; STA activateWarpFlag
	; PlaySound #SND_ENTER
	; LDX player1_object
	; LDA Object_x_hi,x
	; STA mapPosX
	; LDA Object_y_hi,x
	; STA mapPosY
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;	
	
	
dontReturnToGame2:

	RTS
	
	
	
	
	
	
	
	
isWritingTextboxAttributes2:
	;; handle all four quadrants of the attribute.
	
	
	RTS

Go back to Project Settings > Script Settings and add a new Script Define:

Name: Handle Text Box 2
Define: SCR_HANDLE_TEXTBOX2
Click OK and then, while your new Script Define is highlighted, use the Visible ASM Files window next to it to navigate to the place you stored HandleTextBox2, and double click. Now it's registered there.

Now find the BankData folder, open up Bank18.asm and add these things:

Code:
.include SCR_HANDLE_TEXTBOX2

.include "ScreenData\npcText02.dat"

stringsTableLo2:
	.db <Text256, <Text257, <Text258, <Text259, <Text260, <Text261
stringsTableHi2:
	.db >Text256, >Text257, >Text258, >Text259, >Text260, >Text261

Those are the first five entries in npcText2.dat. I suggest you add more as you're writing the dialogue you're using it for, because it would be tedious to type it all out at once (but if you want to try, be my guest).

Almost done. Look in the System folder for HandleBoxes.asm. Find the bit of code that starts with "NotChangingAttributes." Directly below it and above LDA currentBank, paste this in:

Code:
	LDA bank18switch
	CMP #$01
	BNE bank17switch
	
	LDA currentBank
	STA prevBank
	LDY #$18
	JSR bankswitchY
	JSR HandleTextBox2
	LDY prevBank
	JSR bankswitchY
	RTS

	bank17switch:

You're ready. Now here's how to use it. npcText2 starts with text entry 256, but you're fooling the script into thinking it's text entry 0. It will likewise think text entry 257 is 1, 258 is 2, and on. To keep this from becoming confusing, take the text entries you want, subtract 256 from 'em, and select those numbers in Text Groups on any screen in which you're using the bank switch.

As for how to trip the switch, that's the tricky part. You cannot trip it on a screen that is already loaded. Tiles, monster AI and mapping it to a button will not work! The only place you can shift text banks is during the screen loading process....specifically, HandleScreenLoads.asm.

Foertunately, you really only need to use this once, if ever. If you find yourself at a point where you've run out of text, take a look at the overworld grid and make a note of the specific screen on which you want to shift banks. It should be in hex (0 through 9 and then A through F), so for example, the rightmost screen on the top row would be #$0F.

Then find System > HandleScreenLoads.asm and modify the top to look like this:

Code:
HandleScreenLoads:
LDA #$ ;;;; insert screen number here
    CMP currentScreen
	BNE HandleScreenLoads2
	LDA #$01
STA bank18switch
HandleScreenLoads2:

And that should do it.
 

dale_coop

Moderator
Staff member
Bucket Mouse, you should start your own topic for that. This is NOT related to the bank boomerang technic (Bank swtiching is not new, there is bank switching everywhere in NM code engine).
This post is about a technic to optimize space in the bank 14, by moving the routines to another bank.

Please, only feedbacks or question on and about the boomerang topic... else in the future it will difficult to follow.
 
Top Bottom