This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Text routines and scripting

Text routines and scripting
by on (#75233)
So I'm expanding the text capability in my top-down adventure game to be able to give the player a choice, and have different things happen based on the choice. So for example, this is how a string would appear in rom:

Code:
   .db "Would you rather have a beer or a water?", CLRSCR
   .db "Well?", NEWLINE
   .db MENU2
   .db "Beer", ENDPROMPT, <script1, >script1
   .db "Water", ENDPROMPT, <script2, >script2


The control codes:
Code:
CLRSC      = clears the textbox and resets the cursor to the top
NEWLINE    = a newline
MENU2      = a text "menu" with 2 options is about to be defined
             -build a menu coordinate struct in ram (for the arrow) based
              on where the textbox appears on screen
             -save the event handler pointers
             -change the state of the player to "menu handling mode"
             -print the prompts
ENDPROMPT  = end of a prompt string


An option definition follows this format: [text prompt], ENDPROMPT, [script lsb], [script msb]

MENU2 could also be other things, like MENU3, a counter for a value (a "How many?" type question), or a text input ("Enter your name:").


Anyone ever done something like this or dissassembled a game that does? This solution works pretty well for me, but you never know.

by on (#75237)
Most of the Final Fantasy series, at least 1 and 2 (Japanese) on the NES, operate like this. It's pretty standard.

There are two things you need to keep in mind, and neither may matter to you depending on how intricate your game is:

1) Using a byte (0-255) for your strings limits the number of "control" numbers you have available to you. The "commonly used ASCII set" ranges from 0x20 to 0x7e, leaving you with a little more than half of the range a byte offers.

I know that in commercial games a hacky (but understandable) workaround was to use a single byte for a control byte indicator, followed by 16-bit values that indicated what you wanted to do. For example:

.db "Would you rather have a beer or a water?", $FF, $9AA2, $741F, $0000

In this context, $FF tells the string parsing engine that there are special control codes that trail the $FF character. The string parsing engine, up until this point, is reading a byte at a time (obviously). The engine makes the assumption the codes that follow are 16-bit values (0-65535).

It continues reading them, one value at a time, and possibly running code of your choice, until it finds one that matches $0000, which acts as an end-of-operation marker (think of it as end-of-string).

The values themselves ($9AA2 and $741F) are just codes; you can make them do whatever you want. $9AA2, for example, could be MENU2, etc... You could also try turning them into pointers to subroutines that the string parsing engine would just JSR to (to execute some code, do some on-screen stuff, etc.). Your choice.

I would recommend keeping certain single-byte values for common things as well, like $0A could be newline, etc... You get the idea. Just don't use them all up, given the below:

2) Keep in mind that if you expect your game to be available in other languages, it's very likely that you'll need more than 0x20-0x7e as indexes within CHR to represent a language (Japanese is a good example).

If your game turns out to be highly interesting to romhackers, they almost certainly will appreciate a more "slimlined" design. Otherwise, work with them in advance and have your game done in multiple languages from the get-go.

by on (#75238)
I have RE'ed the script systems for Ultima 4 and 5. They use a more limited scripting system than what you have described, so that's probably not going to be of much use to you :D

My opinion is that if you don't care about internationalization then one-byte codes should be fine. This gives you 128 possible op-codes for your script system. If you make these op-codes parameterizable that should be plenty.

An example of a parameterized code might be with your menus:

Code:
.byte MENU, 3 ; A menu definition follows with 3 entries
.byte "My first choice" ; Menu choices
.byte "My second choice"
.byte "My third choice"
.word first_response, second_response, third_response ; Response vectors


If on the other hand you do care about internationalization, then try the following:

Codes 0-127 have their normal ASCII meaning.
Codes 128 - 253 are reserved for an extended character set.
Code 254 means the next byte (or word) is an index into another extended character set table.
Code 255 means the next byte is one of 256 possible opcodes.



The script system I wrote for my C64 game (that I never finished) used a C program to parse a script and do various craziness to make my script data binary. I like your approach better using macros and aliases.



One thing that has not been touched on yet is word compression. Many RPGs (even in the SNES era) had special op-codes for particular words. For instance, if you use the word internationalization a lot in your game that's going to take up an awful lot of space. You could have an opcode that indexes a dictionary of commonly-used long words and inline's that into the output stream.

That's where the script pre-processor comes in real handy. You can lay out your text in a regular text file, then run it through the compiler / compressor. The compressor portion would calculate how many bytes in the output each word uses, then take the top 256 words and generate a dictionary for them, then replace all occurrences of these words in the script output with the dictionary op-codes.

Many games even include spaces and punctuation along with their compressed words. I can't remember what game it was I was slogging through but I found both " that" and " that." in their dictionary.

by on (#75239)
qbradq wrote:
One thing that has not been touched on yet is word compression. Many RPGs (even in the SNES era) had special op-codes for particular words. For instance, if you use the word internationalization a lot in your game that's going to take up an awful lot of space. You could have an opcode that indexes a dictionary of commonly-used long words and inline's that into the output stream.

Yeah, I was planning on building an e-book reader based on this principle, which is called "Huffword" in Managing Gigabytes. Counting the dictionary and actual text, I managed to squeeze a short novel to one-third its size. I stopped when I realized that some portable DVD players that advertise NES support have really inaccurate emulators. (See previous topic.)

Quote:
That's where the script pre-processor comes in real handy. You can lay out your text in a regular text file, then run it through the compiler / compressor.

And if you use a makefile to manage your build process, you can have the text compressor run automatically whenever you change the text file and rebuild the ROM.

by on (#75241)
Don't bother with esoteric stuff like DMC interrupts, inaccurate emulators usually can still do Sprite 0 hit fine.

by on (#75242)
Dwedit wrote:
Don't bother with esoteric stuff like DMC interrupts, inaccurate emulators usually can still do Sprite 0 hit fine.

Even the demo that used only sprite 0 screwed up on this DVD player.

by on (#75255)
tepples wrote:
Dwedit wrote:
Don't bother with esoteric stuff like DMC interrupts, inaccurate emulators usually can still do Sprite 0 hit fine.

Even the demo that used only sprite 0 screwed up on this DVD player.

Image

by on (#75267)
Image

Back on topic: I used a very basic scripting system for the attract-mode instruction screen in LJ65. I used $01-$03 for various waiting amounts, $0A for newline, $0C to clear the text box, and $10-$17 and $18-$1F to press specific buttons for amounts of time (half or double autorepeat delay respectively), as well as of course $20-$5F to draw a character.
Code:
  .byt "BUT YOU",$0A
  .byt "CAN'T TURN",$0A
  .byt "THE YELLOW",$0A
  .byt "SQUARE ONES.",$01,$10,$01,$10,$10,$10,$02,$14,$0C

by on (#75312)
I think I'm gonna go with what koitsu described, where I'll use $F8 to $FF as control codes.

$F8xx where xx=designates what action to perform
$F9 to $FF will be common functions like newline, etc.

Text compression is something I'm putting off until I get the story fleshed out. I'm gonna go with some sort of variable length dictionary scheme, what qbradq was talking about.