[4.5.x] How To Make Text-Heavy Games By Using TEXT_FREE

Bucket Mouse

Active member
In the tutorial videos, Joe shows how to use a command in the ASM called TEXT_FREE that reads a text entry from any bank. He writes a HELLO WORLD message in Bank 1A and then writes more code to display it.

This feature is new to 4.5.9, and like most new features, is not fully hooked up yet. As it is, all text entries written in the NESMaker GUI are stored in npcText01.dat, which is compiled into Bank 19. Look in GameEngineData/ScreenData for npcText01 and open it with Notepad+, as we'll be needing it later.

It's possible to rig up NESMaker to store text in two banks, like what was done with the multiple banks music tutorial. But that's a lot of work, and why do it when a workaround already exists? We're going to use TEXT_FREE to cheat the system and store extra text messages anywhere we want.

STEP ONE: USERSCREENBYTES

The first step is to make sure userScreenBytes is active (see THIS tutorial for how to do that). You really need to get the userScreenBytes working as most new tutorials will use them for something.

STEP TWO: DRAWBOX2

The macro that calls the text is called DrawBox. We're going to make a second macro called DrawBox2, which we'll use for TEXT_FREE. Copy-paste this into a Notepad+ file and save it in System/Macros as DrawBox2.asm.

Code:
MACRO DrawBox2 arg0, arg1, arg2, arg3, arg4
    TXA
    PHA
    TYA
    PHA
    ; arg0 = X value, in metatiles
    ; arg1 = y value, in metatiles
    ; arg2 = width, in metatiles
    ; arg3 = height, in metatiles
    ; arg4 = must always be #TEXT_FREE

    LDA arg0

    STA Box_x_origin;arg0_hold
    STA temp_boxX
 
    LDA arg1

    STA Box_y_origin;arg1_hold
    STA Box_y_hold
    STA temp_boxY
 
    LDA arg2
    STA Box_width; arg2_hold
    STA temp_boxWidth
 
    LDA arg3
    STA Box_height
    STA Box_height_hold
    STA temp_boxHeight
    ;arg3_hold
    LDA arg4
    ;STA ;arg4_hold
    STA endDrawBoxAction ;; also serves a text mode.
    ; CMP #TEXT_FREE
    ; BNE notTextFree
 
 
            LDA textQueued
            AND #%00000001
            BNE skipSettingNewPointer3
LDX userScreenByte0
; CLC
; ADC #$01
LDA freetextTable_lo,x
STA textPointer
LDA freetextTable_hi,x
STA textPointer+1
            skipSettingNewPointer3:
            JMP activateTextNow
    ; notTextFree:
        ;;; check if it is NPC text, if there are any other types.
        ; LDA arg5
        ; STA textToWrite
activateTextNow:

    LDA Box_x_origin
    STA temp_boxX

    LDA Box_y_origin
    STA Box_y_hold
    STA temp_boxY
 
    LDA Box_width
    STA temp_boxWidth
 
    LDA Box_height
    STA Box_height_hold
    STA temp_boxHeight

 
    LDA queueFlags
    ORA #%10000000
    STA queueFlags

    JSR doDrawBox
 
    PLA
    TAY
    PLA
    TAX
 
    ENDM

This new macro is much like the original, except it loads text from a different set of tables, which we'll create in a bit.

STEP THREE: ALTER DRAWNPCTEXT

Find the drawNPCtext.asm input script in the Inputs folder. Open it with Notepad+, NOT NESMaker's built-in input script editor. Then save it under a new name, like drawNPCtext-1 or something.
We're going to tell the text button to use the alternate macro if userScreenByte00 is any number other than zero.

Scroll down to near the bottom where the line LDA textHandler is. We're gonna alter that portion so it looks like this:

Code:
            LDA textHandler
    BNE +skipNpcText
        LDA userScreenByte0
BNE +alternate
    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, npc_text
    ;;; x
    ;;; y
    ;;; width
    ;;; height
    ;;; text mode
        ;#TEXT_NPC pulls from the index in the following argument.
        ;#TEXT_FREE allows you to just write a label definition in the following argument
        ; and as long as you're in the right bank, will draw it.
    ;;; string or index.
+skipNpcText
   RTS

      
          +alternate: ; userScreenByte00 loads from Bank 1A

        LDA userScreenByte0
        ADC Object_id,x
        STA userScreenByte0 ; temporarily adds sprite ID to screenbyte number

        DrawBox2 #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH, #BOX_1_HEIGHT, #TEXT_FREE
        LDA userScreenByte0
        SBC Object_id,x ; restores sprite ID to normal for next sprite
        STA userScreenByte0
        RTS

In NESMaker, replace the old drawNPCtext input script with the new one. First, the new code checks if userScreenByte00 is any number other than zero. If it is, it uses the DrawBox2 macro, but first it adds the Object ID of the NPC you're talking to (1, 2, 3 or 4). After the macro runs, the Object ID number is subtracted to reset things.

STEP FOUR: REDIRECT THE BANK

The part where the bankswitch happens is in another script. Find doDrawText.asm in the Subroutines folder, and search for the line alreadyDrawingText. Then alter that portion like so:

Code:
alreadyDrawingText:
    LDA #$00
    STA scrollOffsetCounter
    ;;;;
    ;;;; EDITED PORTION TO CHANGE BANK WHEN USING TEXTFREE
    LDA userScreenByte0
    BEQ +
    SwitchBank #$1A
    JMP ++
    +
    SwitchBank textBank ;; original line = keep
    ++
    ;;;;;; END OF EDITED PORTION
    ;;;;;;
        LDY #$00

STEP FIVE: BUILD YOUR TEXT LIBRARY

In the tutorial, Joe stored a line of text that said "HELLO WORLD" in Bank 1A. It should actually still be there, and if it's not you can add it:

Code:
MyDumbText:
    .db _H,_E,_L,_L,_O, _SPACE, _BREAK
    .db _W, _O, _R, _L, _D, _END

Time to build the table. Below MyDumbText, put this stuff:

Code:
freetextTable_lo:
    .db <MyDumbText, <MyDumbText, <MyDumbText

freetextTable_hi:
    .db >MyDumbText, >MyDumbText, >MyDumbText

Pay close attention to the way this is written. Always use the < sign in the Lo table and the > sign in the Hi table.

So why'd we enter it three times? It's due to the way the script works. It will ignore userScreenByte00 if it is set to zero. But there has to be a "zero" entry by default, so we have to put something there. The second repeat is due to the addition of the Object ID number, which means it will never read Entry 1 either -- only 2 and onward. After this point you can add more text names without the need to repeat them. Write them in NESMaker's text entry GUI, compile the game, scoop the raw data out of npcText01.dat (which you opened earlier) and paste it into Bank 1A.

text1.jpg
Copy the text from npcText01

text2.jpg
Paste it into Bank 1A. RENAME the label.

text3.jpg
Make it one short word that easily identifies what line of text it is.
Now we don't need the original text entry anymore, so go back to NESMaker's text GUI and delete what you wrote there. Space successfully saved.

Make as many text entries this way as desired. Store them neatly in easily countable rows of 8, like this:

Code:
freetextTable_lo:
    .db <MyDumbText, <MyDumbText, <MyDumbText, <quitit, <kilroy, <tiny, <owl2, <owl3 ; 0-7
    .db <owl4, <owl5, <owl7, <owl6, <owlslumlord, <owl8, <cheeto, <chicken ; 8-15
    .db <cotton, <barman, <fish, <store, <civilization, <meatsign, <meatball1, <meatball2 ; 16-23
    .db <artrat, <sewer, <balloon, <artsign, <cavesign, <switch1, <switch2, <switch3 ; 24-31
    .db <batty, <cliffs, <headsign, <head1, <head2, <mansnow

freetextTable_hi:
    .db >MyDumbText, >MyDumbText, >MyDumbText, >quitit, >kilroy, >tiny, >owl2, >owl3
    .db >owl4, >owl5, >owl7, >owl6, >owlslumlord, >owl8, >cheeto, >chicken
    .db >cotton, >barman, >fish, >store, >civilization, >meatsign, >meatball1, >meatball2
    .db >artrat, >sewer, >balloon, >artsign, >cavesign, >switch1, >switch2, >switch3
    .db >batty, >cliffs, >headsign, >head1, >head2, >mansnow

STEP SIX: HOW TO USE IT

Open your overworld and find the screen you want. Once you're seeing that screen, open up Screen Info and observe the User Defined Bytes at the bottom. Now we can use them, specifically the first one:

text4.jpg

Make the number of UserScreenByte00 ONE NUMBER LESS THAN the text entry in your custom table that you want NPC #1 to say.
IMPORTANT: any other NPC on the screen will say the next three text entries in the order you have listed them, so list them appropriately.

And that's how to store extra NPC text in a separate bank. With a little modification, this method can be used for Autotext tiles or any other use of text dialogue boxes in NESMaker. The crucial element is the redirect of DrawBox to DrawBox 2. Anywhere you see DrawBox, you can probably use this:

Code:
LDA userScreenByte0
BNE +alternate
 
        DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH, #BOX_1_HEIGHT, #TEXT_NPC, screenText
        RTS
     
    +alternate:
        DrawBox2 #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH, #BOX_1_HEIGHT, #TEXT_FREE
        RTS
 
Last edited:

SHLIM

Active member
is there an updated version of this? my drawNPCtext file doesnt have a line that says LDA texthandler. Theres a line that says LDA npctrigger close to the rest of the code...i added everything after that but when im trying to load the text im just getting a blank box. The problem is im not sure if its to do with where ive placed the code in the drawnpc file, or if its a fault ive caused somewhere else?
 

dale_coop

Moderator
Staff member
Difficult to guess without seing your scripts...
Can you share your drawNPCtext input script?
 

SHLIM

Active member
Difficult to guess without seing your scripts...
Can you share your drawNPCtext input script?

Here you go, the changes start from line 76. (where it says LDA npctrigger)

Code:
NPC_WIDTH = #$10
NPC_HEIGHT = #$10

   LDX player1_object
    LDA Object_direction,x
    AND #%00000111
    TAY
 
    LDA NpcFocusTableX,y
    STA tempA
    LDA NpcFocusTableY,y
    STA tempB
 
    LDA Object_x_hi,x
    CLC
    ADC tempA
    STA tempC
 
    LDA Object_y_hi,x
    CLC
    ADC tempB
    STA tempD
 
    LDX #$00
    doCheckForNpcLoop:
    LDA Object_status,x
    AND #%10000000
    BNE +keepChecking
        JMP +noNPCcollision
    +keepChecking
        ;; we have confirmed it is active.
        LDA Object_flags,x
        AND #%10000000
        BNE +keepChecking
     
            JMP +noNPCcollision
        +keepChecking
            ;; we have confirmed it is an NPC type.
            LDA tempC
            CMP Object_x_hi,x
            BCC +noNPCcollision
                ;; still possible.
                LDA Object_x_hi,x
                CLC
                ADC #NPC_WIDTH
                CMP tempC
                BCC +noNPCcollision
                    ;;; still possible
                    LDA tempD
                    CMP Object_y_hi,x
                    BCC +noNPCcollision
                        LDA Object_y_hi,x
                        CLC
                        ADC #NPC_HEIGHT
                        CMP tempD
                        BCC +noNPCcollision
                                LDA Object_id,x
                                TXA
                                PHA
                                    LDA stringGroupPointer,x
                                    STA npc_text
                                PLA
                                TAX
                                LDA npcTrigger
                                ORA #%00000001
                                STA npcTrigger
                            JMP +doNpc
               
            +noNPCcollision
                INX
                CPX #TOTAL_MAX_OBJECTS
                BNE doCheckForNpcLoop
                    RTS
    +doNpc
    LDA npcTrigger
;;    AND #%00000001
;;    BNE dontSkipNPCtext
;;    JMP +skipNpcText
;; dontSkipNPCtext:
;; eadams text free
    BNE +skipNpcText
        LDA userScreenByte0
BNE +alternate
    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, npc_text
    ;;; x
    ;;; y
    ;;; width
    ;;; height
    ;;; text mode
        ;#TEXT_NPC pulls from the index in the following argument.
        ;#TEXT_FREE allows you to just write a label definition in the following argument
        ; and as long as you're in the right bank, will draw it.
    ;;; string or index.
+skipNpcText
   RTS
 

      ;; e adams text free
          +alternate: ; userScreenByte00 loads from Bank 1A

        LDA userScreenByte0
        ADC Object_id,x
        STA userScreenByte0 ; temporarily adds sprite ID to screenbyte number

        DrawBox2 #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH, #BOX_1_HEIGHT, #TEXT_FREE
        LDA userScreenByte0
        SBC Object_id,x ; restores sprite ID to normal for next sprite
        STA userScreenByte0
        RTS

 
NpcFocusTableX:
    ;; DOWN  DR    RIGHT   UR  UP    UL    LEFT  DL
    .db #$08, #$00, #$18, #$00, #$08, #$00, #$F8, #$00

NpcFocusTableY:
    .db #$18, #$00, #$08, #$00, #$F8, #$00, #$08, #$00
 
Last edited:

dale_coop

Moderator
Staff member
Here you go, the changes start from line 76. (where it says LDA npctrigger)

Code:
NPC_WIDTH = #$10
NPC_HEIGHT = #$10

   LDX player1_object
    LDA Object_direction,x
    AND #%00000111
    TAY
 
    LDA NpcFocusTableX,y
    STA tempA
    LDA NpcFocusTableY,y
    STA tempB
 
    LDA Object_x_hi,x
    CLC
    ADC tempA
    STA tempC
 
    LDA Object_y_hi,x
    CLC
    ADC tempB
    STA tempD
 
    LDX #$00
    doCheckForNpcLoop:
    LDA Object_status,x
    AND #%10000000
    BNE +keepChecking
        JMP +noNPCcollision
    +keepChecking
        ;; we have confirmed it is active.
        LDA Object_flags,x
        AND #%10000000
        BNE +keepChecking
    
            JMP +noNPCcollision
        +keepChecking
            ;; we have confirmed it is an NPC type.
            LDA tempC
            CMP Object_x_hi,x
            BCC +noNPCcollision
                ;; still possible.
                LDA Object_x_hi,x
                CLC
                ADC #NPC_WIDTH
                CMP tempC
                BCC +noNPCcollision
                    ;;; still possible
                    LDA tempD
                    CMP Object_y_hi,x
                    BCC +noNPCcollision
                        LDA Object_y_hi,x
                        CLC
                        ADC #NPC_HEIGHT
                        CMP tempD
                        BCC +noNPCcollision
                                LDA Object_id,x
                                TXA
                                PHA
                                    LDA stringGroupPointer,x
                                    STA npc_text
                                PLA
                                TAX
                                LDA npcTrigger
                                ORA #%00000001
                                STA npcTrigger
                            JMP +doNpc
              
            +noNPCcollision
                INX
                CPX #TOTAL_MAX_OBJECTS
                BNE doCheckForNpcLoop
                    RTS
    +doNpc
    LDA npcTrigger
;;    AND #%00000001
;;    BNE dontSkipNPCtext
;;    JMP +skipNpcText
;; dontSkipNPCtext:
;; eadams text free
    BNE +skipNpcText
        LDA userScreenByte0
BNE +alternate
    DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, npc_text
    ;;; x
    ;;; y
    ;;; width
    ;;; height
    ;;; text mode
        ;#TEXT_NPC pulls from the index in the following argument.
        ;#TEXT_FREE allows you to just write a label definition in the following argument
        ; and as long as you're in the right bank, will draw it.
    ;;; string or index.
+skipNpcText
   RTS
 

      ;; e adams text free
          +alternate: ; userScreenByte00 loads from Bank 1A

        LDA userScreenByte0
        ADC Object_id,x
        STA userScreenByte0 ; temporarily adds sprite ID to screenbyte number

        DrawBox2 #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH, #BOX_1_HEIGHT, #TEXT_FREE
        LDA userScreenByte0
        SBC Object_id,x ; restores sprite ID to normal for next sprite
        STA userScreenByte0
        RTS

 
NpcFocusTableX:
    ;; DOWN  DR    RIGHT   UR  UP    UL    LEFT  DL
    .db #$08, #$00, #$18, #$00, #$08, #$00, #$F8, #$00

NpcFocusTableY:
    .db #$18, #$00, #$08, #$00, #$F8, #$00, #$08, #$00

I think the error is around line 74... in order to work properly, it should be:
Code:
+doNpc:
    LDA npcTrigger
    AND #%00000001
    BNE dontSkipNPCtext
        JMP +skipNpcText
 dontSkipNPCtext:
    LDA userScreenByte0
    BNE +alternate
      DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, npc_text
 

SHLIM

Active member
I think the error is around line 74... in order to work properly, it should be:
Code:
+doNpc:
    LDA npcTrigger
    AND #%00000001
    BNE dontSkipNPCtext
        JMP +skipNpcText
 dontSkipNPCtext:
    LDA userScreenByte0
    BNE +alternate
      DrawBox #BOX_1_ORIGIN_X, #BOX_1_ORIGIN_Y, #BOX_1_WIDTH , #BOX_1_HEIGHT, #TEXT_NPC, npc_text

still getting nothing sadly :(
does it matter if my bank1a is used for inputs aswel? theres a couple of lines of code at the top of the file, would this effect the text arrangements placed below it?
 

dale_coop

Moderator
Staff member
No, it should not matter.
I'd suggest to follow again all steps... you might have missed something
 

SHLIM

Active member
No, it should not matter.
I'd suggest to follow again all steps... you might have missed something
weird, ive gone through everything again, i even made sure my bytes code is working (by testing the proxy script which uses byte01 & 02).
do i need to do anything specific with the drawbox2 script other than just add it to the macro folder? do i need to define it in the scripts settings?

i dont get any errors or anything, and the textbox draws, just no text appears and it wont let me end the textbox.
 

SHLIM

Active member
Solved it :)
the problem was that id replaced the alreadyDrawingText function in dodrawtext.asm with the code shown in the tutorial.... but instead you need to leave the rest of the code in after the LDY #$00 part and not replace it all!

As always thank you for your assistance <3
 
Top Bottom