News: 11 March 2016 - Forum Rules, Mobile Version
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia

Author Topic: FF1 - Printing text from a LUT  (Read 670 times)

Jiggers

  • Jr. Member
  • **
  • Posts: 59
    • View Profile
FF1 - Printing text from a LUT
« on: March 18, 2018, 10:39:17 pm »
I've run into another road block while fiddling around with FF1. I wanted to make a sound test screen... so I did. The problem is the part where I want to print the name of the song. Either the way it picks the pointers for the text doesn't work, or it doesn't work AND there's some PPU thing I'm not doing right.

So here's the code--its not commented properly, because its mainly cobbled together from other routines that were already commented on. But I hope it makes sense.

Code: [Select]
SoundTestMenu:         ; prior to this call, Bank E is swapped in, so we can use some menu routines.
    LDA #0
    STA soundtest
    STA $2001           ; turn off the PPU (we need to do some drawing)     
    STA $4015           ; and silence the APU.  Music sill start next time MusicPlay is called.
    STA $5015           ; and silence the MMC5 APU.
    STA cursor                      ; flush cursor, joypad, and prev joy directions
    STA joy
    STA joy_prevdir
    STA menustall
   
    JSR LoadMenuCHRPal        ; load menu related CHR and palettes
    JSR ClearNT               ; clear the nametable
   
    LDA #$03                       
    STA box_x                       
    LDA #$0A
    STA box_y
    LDA #$1A
    STA box_wd
    LDA #$05
    STA box_ht
    JSR DrawBox     
    JSR TurnMenuScreenOn_ClearOAM   ; then clear OAM and turn the screen on
   
    LDA #BANK_Z
    JSR SwapPRG
    JSR SoundTestZ
    RTS 

---(BANK_Z)---

  SoundTestZ:
    LDA #BANK_Z
    STA cur_bank                 
  SoundTestMenuLoop:   
    JSR ClearOAM                  ; clear OAM (erasing all existing sprites)
    JSR DrawSoundTestCursor       ; draw the cursor
    JSR SoundTest_DrawSongName
    JSR SoundTestFrame            ; Do a frame
   
    LDA joy_a                     ; check to see if A has been pressed
    BNE @A_Pressed
    LDA joy_b                     ; then see if B has been pressed
    BNE @B_Pressed
    JSR SoundTestSelect
    JMP SoundTestMenuLoop         ;  rinse, repeat

  @B_Pressed:
    LDA #0            ; turn PPU off
    STA $2001
    STA joy_a         ; flush A, B, and Start joypad recordings
    STA joy_b
    STA joy_start
    RTS               ; and exit the main menu (by RTSing out of its loop)

  @A_Pressed:
    LDA soundtest
    CLC
    ADC #$41
    STA music_track 
    JMP SoundTestMenuLoop          ; then return to main menu loop

   
SoundTestFrame:
    JSR WaitForVBlank_L    ; wait for VBlank
    LDA #>oam              ; Do sprite DMA (update the 'real' OAM)
    STA $4014
   
    LDA soft2000           ; reset scroll and PPU data
    STA $2000
    LDA #0
    STA $2005
    STA $2005

    LDA #BANK_THIS           ; record current bank and CallMusicPlay
    STA cur_bank
    JSR CallMusicPlay

    INC framecounter       ; increment the frame counter to count this frame

    LDA #0                 ; zero joy_a and joy_b so that an increment will bring to a
    STA joy_a              ;   nonzero state
    STA joy_b
    JMP UpdateJoy          ; update joypad info, then exit
 
SoundTestSelect:
    LDA joy           ; get joypad data
    AND #$0C          ;  isolate up/down buttons
    CMP joy_prevdir   ;  compare it to previously checked button states
    BEQ @Exit         ; if they equal, do nothing (button has already been pressed and is currently just held)

    STA joy_prevdir   ; otherwise, button state has changed, so record new button state in prevdir
    CMP #$00          ;  and check to see if a button is being pressed or released (nonzero=pressed)
    BEQ @Exit         ;  if zero, button is being released, so do nothing and just exit

    CMP #$04          ; see if the user pressed down or up
    BNE @Up

  @Down:              ; moving down...
    DEC soundtest
    LDA soundtest     
    CMP #$FF
    BNE :+
      LDA #23
      STA soundtest
      CLC
 :  JMP @Exit

  @Up:                ; up is the same deal...
    INC soundtest
    LDA soundtest
    CMP #24
    BNE @Exit
      LDA #00
      STA soundtest     
     
 @Exit:
    RTS

DrawSoundTestCursor:
    LDA #$1F
    STA spr_x              ; set cursor X coord to $58
    LDA #$60
    STA spr_y              ; and that's the cursor Y coord
    JMP DrawCursor         ; draw the cursor and exit   
 
SoundTest_DrawSongName:
    LDA #$06
    STA dest_x
    LDA #$0C
    STA dest_y   
    LDA soundtest      ; 0-23
    LDX #$0A           ; each song name is 10 bytes across
    JSR MultiplyXA     ; output of A is low byte of multiplication
    TAX
    STA soundtesthelper
    ;LDA lut_SongNames, X
    LDA #<(lut_SongNames + soundtesthelper)
    STA text_ptr
    LDA #>(lut_SongNames + soundtesthelper)
    ;LDA lut_SongNames+1, X
    STA text_ptr+1
    LDA #1
    STA menustall         ; enable to write while PPU is on?
    JSR DrawSongName
    RTS   

DrawSongName:
   JSR CoordToNTAddr
   JSR MenuCondStall
   
   @Draw:
    LDY #0            ; zero Y -- we don't want to use it as an index.  Rather, the pointer is updated
    LDA (text_ptr), Y ;   after each fetch
    BEQ @DrawSongName_Exit   ; if the character is 0  (null terminator), exit the routine

    INC text_ptr      ; otherwise, inc source pointer
    BNE :+
      INC text_ptr+1  ;   inc high byte if low byte wrapped

:   LDX $2002         ; reset PPU toggle
    LDX ppu_dest+1    ;  load and set desired PPU address
    STX $2006         ;  do this with X, as to not disturb A, which is still our character
    LDX ppu_dest
    STX $2006

    STA $2007         ; draw the character as-is
    INC ppu_dest      ; increment dest PPU address
    JMP @Draw         ; and repeat the process until terminated
 
   @DrawSongName_Exit:
    LDA #$00          ; reset scroll to 0
    STA $2005
    STA $2005
    STA menustall     ; and disable menu stalling again
    RTS

https://drive.google.com/open?id=1cJwUIMmb16Cb8urIpmOOFS1rKIwpUso_-- the project file as I have it now. Above code is in Bank F and Z. When using PRGSwap, Z is actually #$0F though.

You can see there that I was trying to use "LDA lut_SongNames, X", where X is the song number times ten. In the debugger, I could see it actually grabbing the right bytes from the table at first load, but it never printed on screen. If I used "LDA #<(lut_SongNames)" with or without ( ), it would print "Prelude" just fine. Fiddling with it further just causes garbage to be printed everywhere.

Ideally, I want to have song names longer than 9 characters. I can't think of a good way to make a LUT that contains longer strings, though. With my current attempt, I could add 1 more song (25*10), but not 1 more byte per song (24*11). The longest song name I have in mind is 24 bytes. There's a second .bin in the zip file, where every song name is 24 bytes long. I have space for it, I just don't know how I'd use it.

I feel pretty proud about the rest, though! It prints the box, the cursor, and the music plays when you scroll up or down... It's accessed by pressing B+select from any map not the overworld, and exits without breaking the game!

Lenophis

  • IRC Staff
  • Hero Member
  • *****
  • Posts: 950
  • The return of the sombrero!
    • View Profile
    • Slick Productions
Re: FF1 - Printing text from a LUT
« Reply #1 on: March 19, 2018, 01:30:00 am »
Why not do dynamic lengths? Use the Song ID as a LUT, then you can load the pointer to the string which can be null-terminated. Something like this:
Code: [Select]
LDA $00  ; load song ID
TAX  ; transfer song ID to X. alternatively, LDX $00 for song ID?
LDA songpointerLUT,X  ; somewhere in the ROM that has the data for the pointers to the song names
STA $01
LDA songpointerLUT+1,X  ; high byte for pointer
STA $02  ; this is now your text pointer
Then use your DrawSongName routine to output the text.

As for why your code isn't working? No clue. I don't know if MMC5 is faulty here, or if there's a problem with the code's logic.


https://ff6randomizer.codeplex.com/ - Randomize your FF6 experience!

Disch

  • Hero Member
  • *****
  • Posts: 2582
  • NES Junkie
    • View Profile
Re: FF1 - Printing text from a LUT
« Reply #2 on: March 19, 2018, 11:46:29 am »
Code: [Select]
    LDA #<(lut_SongNames + soundtesthelper)
The '+' operator is an assembler tool to add together values AT ASSEMBLE TIME.  This is adding the address of lut_SongNames with the address of soundtesthelper.  It is not taking the contents of either variable.  The assembler can't possibly know what the contents are at assemble time because they will be changing during runtime.

If you want to add the contents of soundtesthelper (or really, the contents of the result of your multiplication) to a pointer, you have to do it the old fashioned way... with ADC.


Or... since you're effectively wanting soundtesthelper to be an index, you can just load it into your indexing reg (Y) when reading the string.

Two possible solutions:

SOLUTION 1:  Indexing

Code: [Select]
; when loading pointer
;  no muss, no fuss
JSR MultiplyXA       ; <- multiply by 10
STA soundtesthelper  ; <- store result (don't really need the TAX here)

LDA #<lut_SongNames
STA text_ptr
LDA #>lut_SongNames
STA text_ptr+1

; ...
; when drawing the string
LDY soundtesthelper  ; <- start indexing at your index, rather than at zero
LDA (text_ptr),Y
; ...

This is the easier solution, but is limited because it means the table can't be larger than 256 bytes because Y is only 8-bits wide.  I don't think this is a problem since I don't think there are more than 25 songs.


SOLUTION 2:  Add the index to the pointer with ADC

This gets around the 256 byte limit by adding the result of the multiplication directly to the pointer:

Code: [Select]
JSR MultiplyXA    ; *10  , low byte in A, high byte in X

; add low byte of pointer
CLC
ADC #<lut_SongNames
STA text_ptr

; add high byte of pointer
TXA
ADC #>lut_SongNames   ; note:  no CLC here, we want the carry from the low byte
STA text_ptr+1


;...
; when reading
LDY #0   ; <- index at zero
LDA (text_ptr),Y

Jiggers

  • Jr. Member
  • **
  • Posts: 59
    • View Profile
Re: FF1 - Printing text from a LUT
« Reply #3 on: March 19, 2018, 04:46:58 pm »
Dynamic lengths is a good idea, but I'm not sure how to erase written text. I just now had to convert my song name table to have a bunch of C1s and only a 00 at the very end of each name to draw spaces over the previous song names. The other problem is that one of the name strings was causing a compiling error somehow, so that's why I put the data into an INCBIN in the first place... and with the bank always getting new stuff added to it, having a LUT point to the pointers would have to be flexible, as the addresses would change...! What a mess.

Quote
The '+' operator is an assembler tool to add together values AT ASSEMBLE TIME.
Hah, I was wondering to myself about that. I saw one LUT use a * and was like, why do I have to do all this calculating if the code can do math like that?? So that was the last thing I tried before giving up and posting. XD Didn't really think about it too much.

I did try using ADC, but without the #> stuff!

So Solution 2 is the one I picked, so I can use longer song names. It works. :D Thank you again!

Seeing all the different ways I can do it should help me better understand what's going on with indexing in the future.

Lenophis

  • IRC Staff
  • Hero Member
  • *****
  • Posts: 950
  • The return of the sombrero!
    • View Profile
    • Slick Productions
Re: FF1 - Printing text from a LUT
« Reply #4 on: March 20, 2018, 01:33:49 am »
Dynamic lengths is a good idea, but I'm not sure how to erase written text.
The same way you draw text here. Just load a pointer that is all spaces so it erases the text for you. Give it the length of the highest character count for a song title. Eg, if the song "For What Must a Song Play" is your longest title, it is 25 letters. Throw in a string that is 25 blank spaces, or use assembly to draw the blank space 25 times followed by a null-terminator before loading the next song title.


https://ff6randomizer.codeplex.com/ - Randomize your FF6 experience!

Jiggers

  • Jr. Member
  • **
  • Posts: 59
    • View Profile
Re: FF1 - Printing text from a LUT
« Reply #5 on: March 21, 2018, 11:32:37 pm »
Oohhh, right! :D I mean, I just used that "write over with blank spaces" thing in my main LUT... Silly me.

https://www.youtube.com/watch?v=xkOSThd9L08&feature=youtu.be

So apart from some small issues with noise from not silencing things properly before/after playing songs, this mini-project is basically done!

Jiggers

  • Jr. Member
  • **
  • Posts: 59
    • View Profile
Re: FF1 - Printing text from a LUT
« Reply #6 on: April 03, 2018, 05:42:55 pm »
So here's something weird... Trying to change some menu text. Item descriptions, menu options, things like that.

;;  LUT for menu text  [$8500 :: 0x38510]
;;    This is a table of complex strings used in menus.

First: moving this LUT from $8500 messes it up. I haven't tried to align it anywhere else though; I just moved the copyright code from above it and it went wonky, so I filled it back up with the weapon permissions LUT and took out 6 unused bytes that were being used for padding.

Second: Adding extra letters to a line messes higher-numbered lines up. It really seems like it shouldn't! These are some of the codes used for the LUT:

Code: [Select]
LDA #$01 ; draw menu string ID=$01  (current GP, followed by " G")
LDA #$02 ; Draw Menu String ID=$02 (the option text)
LDA #$03 ; draw the "ITEM" title box
LDA #$04 ; draw the "You have nothing" description text
LDA #$05 ; get relevent description text
LDA #$06 ; if you can't use the lute here, just load up generic description text
LDA #$07 ; select description text "The stolen CROWN"
UseItem_Crystal:  LDABRA $08, UseItem_SetDesc
UseItem_Herb:     LDABRA $09, UseItem_SetDesc
UseItem_Key:      LDABRA $0A, UseItem_SetDesc
UseItem_TNT:      LDABRA $0B, UseItem_SetDesc
UseItem_Adamant:  LDABRA $0C, UseItem_SetDesc
UseItem_Slab:     LDABRA $0D, UseItem_SetDesc
UseItem_Ruby:     LDABRA $0E, UseItem_SetDesc
UseItem_Chime:    LDABRA $13, UseItem_SetDesc
UseItem_Tail:     LDABRA $14, UseItem_SetDesc
UseItem_Cube:     LDABRA $15, UseItem_SetDesc
UseItem_Oxyale:   LDABRA $18, UseItem_SetDesc
UseItem_Canoe:    LDABRA $19, UseItem_SetDesc
LDA #$32 ; print "you don't have enough MP" or whatever message (description text ID=$32)
LDA #$33 ; print description text ("can't cast that here")
LDA #$34 ; menu string $34 ("WEAPON")
LDA #$35 ; and its contained text ("EQUIP   TRADE   DROP")
LDA #$36 ; Character 0's name
LDA #$38 ; Character 1's name
LDA #$3A ; Character 2's name
LDA #$3C ; Character 3's name
LDA #$3E ; if not the weapon screen, change menu string ID to $3E ("ARMOR")

And this is how it accesses the lines to print...

Code: [Select]
DrawMenuString:
    ASL A                   ; double A (pointers are 2 bytes)
    TAX                     ; put in X to index menu string pointer table
    LDA lut_MenuText, X
    STA text_ptr
    LDA lut_MenuText+1, X   ; load pointer from table, store to text_ptr  (source pointer for DrawComplexString)
    STA text_ptr+1

And THIS is the start of lut_MenuText itself!
(I did change some things already, somehow; ITEMS instead of just ITEM, for example. I think I just got lucky since there's room for 5 characters on those lines and it was only using 4.)

Code: [Select]
lut_MenuText:
;  .INCBIN "bin/0E_8500_menutext.bin"

.byte $80,$85,$81,$85,$85,$85,$A7,$85,$AD,$85,$BA,$85,$D9,$85,$F3,$85
.byte $01,$86,$15,$86,$30,$86,$3E,$86,$48,$86,$56,$86,$71,$86,$7E,$86
.byte $9E,$86,$BD,$86,$DF,$86,$ED,$86,$0E,$87,$38,$87,$53,$87,$70,$87
.byte $7B,$87,$94,$87,$A6,$87,$CC,$87,$DD,$87,$03,$88,$14,$88,$49,$88
.byte $66,$88,$78,$88,$8B,$88,$A7,$88,$AB,$88,$B2,$88,$B9,$88,$DC,$88
.byte $18,$89,$4B,$89,$4F,$89,$07,$8A,$19,$8A,$4D,$8A,$65,$8A,$77,$8A
.byte $97,$8A,$B3,$8A,$DE,$8A,$FB,$8A,$18,$8B,$1E,$8B,$33,$8B,$37,$8B
.byte $38,$8B,$3C,$8B,$3D,$8B,$41,$8B,$42,$8B,$46,$8B,$47,$8B,$4D,$8B
.byte $00 ;; first terminator
.byte $04,$FF,$90,$00 ; _G - for gold on menu
.byte $92,$9D,$8E,$96,$9C,$01 ; ITEMS
.BYTE $96,$8A,$90,$92,$8C,$01 ; MAGIC
.byte $CF,$D0,$D1,$D2,$D3,$01 ; WEAPON (special sprites to squeeze 6 letters in)
.BYTE $8A,$9B,$96,$98,$9B,$01 ; ARMOR
.byte $CA,$CB,$CC,$CD,$CE,$00,; STATUS (special sprites to squeeze 6 letters in)
.byte $00,$00,$00,$00 ;; ??
.BYTE $92,$9D,$8E,$96,$9C,$00 ; ITEMS - For top of the ITEMS menu instead of the main menu options
;; Item descriptions begin here
.byte $A2,$B2,$64,$41,$B9,$1A,$B1,$B2,$1C,$1F,$AA,$C0,$00 ; You have nothing.
.byte $9D,$AB,$1A,$B7,$B8,$B1,$1A,$B3,$AF,$A4,$BC,$B6,$BF,$05,$B5,$A8,$B9,$2B,$AF,$1F,$AA,$20,$24,$B7,$A4,$AC,$B5,$5D,$BC,$C0,$00 ; The tune plays,[enter]revealing a stairway.
.byte $8B,$A8,$A4,$B8,$57,$A9,$B8,$AF,$42,$B8,$B6,$AC,$A6,$43,$AC,$4E,$B6,$05,$B7,$AB,$1A,$A4,$AC,$B5,$C0,$00 ; Beautiful music fills[enter]the air.
.byte $9D,$AB,$1A,$B6,$28,$45,$29,$8C,$9B,$98,$A0,$97,$C0,$00 ; The stolen CROWN.

What am I missing? Something, somewhere, knows how long each line is. Is X jumping to the first non-zero byte after passing however many its set to? Does the Menu Options string NEED 5 $00 terminators? What's all that nonsense at the start? Why can't I change how many bytes are being used by this table, or where its placed in the rom?

What I was trying to change:
.byte $95,$8E,$9F,$FF,$10,$03,$00 ; LEV #
 to
.byte $95,$8E,$9F,$8E,$95,$FF,$FF,$FF,$10,$03,$00 ; LEVEL ___ #

This caused the rest of the Status screen to go crazy. Its mostly blank with a few numbers on it now.

Disch

  • Hero Member
  • *****
  • Posts: 2582
  • NES Junkie
    • View Profile
Re: FF1 - Printing text from a LUT
« Reply #7 on: April 03, 2018, 06:28:32 pm »
First: moving this LUT from $8500 messes it up.

Hmm.. LUT is a bit of a misnomer.  It's not really a lookup table as much as it is a block of string data.  The data itself contains a pointer table to the strings contained in it.  So if you're going to move the entire block, you'll need to adjust all the pointers contained in it.

It probably would have been better to give all the strings labels and then make a pointer table out of those labels in the assembly.  Whoops.

Quote
What am I missing? Something, somewhere, knows how long each line is. Is X jumping to the first non-zero byte after passing however many its set to? [snip] What's all that nonsense at the start? Why can't I change how many bytes are being used by this table, or where its placed in the rom?

The nonsense at the start is the pointer table.  Note the pattern:
$8580
$8581
$8585
$85A7
..etc

Overlooking this seems to be the source of most of your troubles.  But I kind of misled you by calling it a LUT when it really isn't.  =x  Sorry.

Quote
Does the Menu Options string NEED 5 $00 terminators?

No.  Text printing stops at the first null.  I don't know why there are back-to-back nulls like that.  Probably the same reason why the first pointer points to a null character:  likely the result of a automated program to generate the pointer table from string data that gave each empty string its own null character instead of having all of them point to any old null character.

Jiggers

  • Jr. Member
  • **
  • Posts: 59
    • View Profile
Re: FF1 - Printing text from a LUT
« Reply #8 on: April 03, 2018, 07:00:26 pm »
OH!! Damn, I was hoping that block was nonsense that I could discard!
(Edit: Turns out everything after $0658 in the menutext.bin file is pretty much useless. Its just spell descriptions but without DTE! Then some Equip Menu stuff over and over again... And I think the last little bit is the bugged armor stat codes.)
(But I have a bug when buying weapons and armor--other characters randomly get pieces of equipment--so I can't confirm 100% that this is not somehow necessary.)

Okay, so that explains what X is doing.

So I should make a pointer table...

.word GOLD, OPTIONS, ITEMS, ItemDescrip_01, etc?

Then put those labels in front of the strings? I can do that! Yay!
« Last Edit: April 04, 2018, 05:17:45 pm by Jiggers »