+  RHDN Forum Archive
|-+  Romhacking
| |-+  ROM Hacking Discussion
| | |-+  How do you handle character-line limits?
Pages: [1]
Author Topic: How do you handle character-line limits?  (Read 1 times)
DarknessSavior
Guest
« on: July 16, 2011, 02:39:47 pm »

I decided to finally get into the script for FFIV. I set up Cartographer properly (with a little help), dumped the script. I started translating it.

I added Atlas commands to the script so that it'll insert properly. Haven't tested it yet.

Why? Because I have a problem to overcome first. FFIV's character-per-line limit is something like...24. Dragonsbrethren once sent me some files to use with TheCheat in order to throw my script in so I could see if it overflowed or not.

For one, I can't get TheCheat to even work properly. This is the first time I've tried using it on Win7, and it has a ton of errors, saying it cannot read certain files (even the Secret of Mana stuff included by default!).

For two, even if I did get that to work? FFIV has an auto-linebreak system built into the game. If you go past a certain limitation, it'll automatically shove the next character down to the next line.

Example:



In my hex-editor? It looks like this:

[Cecil]: If you value yourlives, hand over the[LB]Crystal quietly.

In ActRaiser 2, I handled the character limit by using some plugin in Notepad++. TextFX -> TextFX Edit -> ReWrap text to (Clipboard or 72) width.

This means if you type 24, and copy it, then click that option, it'll set up auto-linebreaks at 24 characters per line.

This is far less tedious than doing it by hand, to be sure. But doing this for an RPG will take forever. I suppose I could just delete the Japanese out of my script, throw it into Notepad++, and do this. But even then, I wouldn't be taking advantage of the auto-linebreak system FFIV has (as Notepad++ doesn't tell me what amount of characters each line has, it just auto-adjusts it for me so that it doesn't go over 24).

Is there some easier way to handle this stuff? I really want to hit FFIV hard.

~DS
Gemini
Guest
« Reply #1 on: July 16, 2011, 03:36:02 pm »

Being a FFIV specific issue, I changed the dialog parser to automatically reformat lines, like this:

This is what the lines appear like in my script:
Quote
Re Baron
"<Cain>, andrai anche tu insieme a <Cecil>, dato che la sua sorte sembra starti così tanto a cuore!"<New>
<Cecil>
"Maestà!"<New>
Re Baron
"Non abbiamo più nulla di cui discutere!
Prendete quell'anello e sparite!"<End>
It's slightly different from what you got there in the picture, but it's just to give you an idea of how it works.

Back to us, the only thing the code does in the original is wrapping stuff right to the next line as soon as the caret hits the 24 char-per-line limit, so instead of ruling words it just wraps whatever characters get parsed near the window borders. This behavior can be easily changed by processing all spaces and counting the next word's length. The only problem you might have is with the 4 lines-per-page limit going on there, but you can suppress it like I did and just forget about it.

PS: Why do you need an [LB] tag to carry to the next line? Can't you just enter a simple line return? I will never understand why some Atlas-formatted scripts need to follow this weird rule. In my opinion it's just redundant and really useless, but whatever you like's in the middle fiddle.
Gideon Zhi
Guest
« Reply #2 on: July 16, 2011, 04:21:02 pm »

I'm slowly moving towards in-engine automatic formatting along the lines of what Gemini's showing. It's really not too difficult, unless the first project you're doing it on pulls some crazy recursive bullshit with the text engine, ahem ahem ancient magic -_-;

As far as having explicitly-defined linebreaks in Atlas, the whole point of Atlas is to give the hacker absolute maximum control over their scripts. Pretty much *everything* needs to be explicitly defined; having linebreaks NOT be explicitly defined could cause problems in some situations. I prefer to have them, because if something goes wrong then the onus for the problem lies entirely on the hacker and not a potential quirk with the inserter program doing something unexpected.
DarknessSavior
Guest
« Reply #3 on: July 16, 2011, 04:35:56 pm »

Can either of you give me an example in code, or even pseudocode?

Also, as far as [LB] goes, I found that you had to have those things defined anyway, so I almost always just insert that as the text for my linebreak control codes in all of my tables anyway, just to be safe.

~DS
Gideon Zhi
Guest
« Reply #4 on: July 16, 2011, 04:42:34 pm »

Essentially how I do it...
-Detect a whitespace character in the script
-Read the next word, calculate how many pixels it is
-If the length of the word + the length of everything before the word > max pixels per line, replace the whitespace with a linebreak, zero out current pixel count, and continue
-Otherwise, add the length of the word to the current pixel count and continue 'til you hit another whitespace.

Of course, once you start displaying variables in the script (numbers, item names, etc) it starts getting a bit more complicated, but that's the general gist of it.
Gemini
Guest
« Reply #5 on: July 16, 2011, 04:50:14 pm »

This is the entire code used in FFIV, but it's MIPS R3000, so it's only for reference:
Code:
PrintString16:\t\t\t; void PrintString16(int x, int y, int char, int color)
\taddiu sp, -28
\tsw ra, 0(sp)
\tsw s0, 4(sp)
\tsw s1, 8(sp)
\tsw s2, 12(sp)
\tsw s3, 16(sp)
\tsw s4, 20(sp)
\tsw s5, 24(sp)
\t; ---
\tmove s0, a0\t\t\t\t; x
\tmove s1, a1\t\t\t\t; y
\tmove s2, a2\t\t\t\t; str
\tmove s3, a3\t\t\t\t; color
\tmove s4, a0\t\t\t\t; x base
\tli s5, 0\t\t\t\t; center flag
@@read:
\tlbu a2, 0(s2)\t\t\t; *str
\taddiu s2, 1\t\t\t\t; str++
\tsltiu v0, a2, 3\t\t\t; if(c<2)
\tbeqz v0, @@char
\tnop
\t; -
\tsll a2, 2\t\t\t\t; setup switch
\tla v0, @@switch_jtbl\t; --
\taddu v0, a2\t\t\t\t; --
\tlw v0, 0(v0)\t\t\t; --
\tnop
\tjr v0\t\t\t\t\t; jump to case
\tnop
@@line:\t\t\t\t\t; case 0x01
\tbeqz s5, @@align_off
\tmove s0, s4\t\t\t\t; reset x
\t; ---
\tjal GetLineWidth
\tmove a0, s2
\t; ---
\tli t0, 256\t\t\t\t; screen width/2
\tsubu s0, t0, v0\t\t\t; x=(screen_w-line_w)/2
\tsrl s0, 1\t\t\t\t; --
@@align_off:
\tj @@read
\taddiu s1, 14\t\t\t; y+=14
 ; --------------
@@center:\t\t\t\t; case 0x02
\tli s5, 1\t\t\t\t; turn on alignment flag
\tjal GetLineWidth
\tmove a0, s2
\t; ---
\tli t0, 256\t\t\t\t; screen width/2
\tsubu s0, t0, v0\t\t\t; x=(screen_w-line_w)/2
\tj @@read
\tsrl s0, 1\t\t\t\t; --
 ; --------------
@@char:\t\t\t\t\t; default
\tli t8, 0xFF
\tbeq\ta2, t8, @@skip_draw\t; if(c==0xFF) continue;
\tli t8, 0x40\t\t\t\t; ' '
\t; ---
\tbne a2, t8, @@not_space
\tnop
\t; ---
\tjal GetWordWidth
\tmove a0, s2
\t; ---
\taddu t0, s0, v0
\tsltiu t0, 233\t\t\t; if(x+GetWordWidth(&str[1])<232)
\tbnez t0, @@not_space
\tli a2, 0x40\t\t\t\t; set a sprite if no overflow happens
\t; ---
\tj @@line
\tnop
\t; ---
@@not_space:
\taddiu a2, -0x40\t\t\t; set to 0x40-0xFF range
\tla t0, font_width16\t\t; get character width
\taddu t0, a2\t\t\t\t; ---
\tlbu t0, 0(t0)\t\t\t; ---
\tmove a0, s0\t\t\t\t; x
\tmove a1, s1\t\t\t\t; y
\tmove a3, s3\t\t\t\t; color
\tjal PrintChar16\t\t\t; print character
\taddu s0, t0\t\t\t\t; x+=width[c]
@@skip_draw:
\tj @@read
\tnop
\t; ---------
@@end:\t\t\t\t\t; case 0x00
\tlw s0, gfx_pool
\tli v0, 1
\tsb v0, 3(s0)
\tli v0, 0xE100000E
\tsw v0, 4(s0)
\tlw a0, 0x1F800008\t\t; ot
\tmove a1, s0\t\t\t\t; primitive
\tjal AddPrim
\taddiu s0, 8
\t; --
\tsw s0, gfx_pool
\tlw ra, 0(sp)
\tlw s0, 4(sp)
\tlw s1, 8(sp)
\tlw s2, 12(sp)
\tlw s3, 16(sp)
\tlw s4, 20(sp)
\tlw s5, 24(sp)
\tjr ra
\taddiu sp, 28

@@switch_jtbl:
.dw @@end, @@line, @@center

;------------------------------------
GetWordWidth:\t\t\t; int GetWordWidth(u8* str)
\tla t0, font_width16
\tli v0, 0\t\t\t\t; reset width counter
@@read:
\tlbu v1, 0(a0)\t\t\t; ch=*str++
\taddiu a0, 1\t\t\t\t; ---
\tsltiu t1, v1, 0x41\t\t; if(ch<0x40)
\tbnez t1, @@return
\taddu v1, t0\t\t\t\t; w=width_tbl16[ch-0x40]
\t; ---
\tlbu v1, -0x40(v1)
\tnop
\tj @@read
\taddu v0, v1\t\t\t\t; width+=w
@@return:
\tjr ra
\tnop

;------------------------------------
GetLineWidth:\t\t\t; int GetWordWidth(u8* str)
\tla t0, font_width16
\tli v0, 0\t\t\t\t; reset width counter
@@read:
\tlbu v1, 0(a0)\t\t\t; ch=*str++
\taddiu a0, 1\t\t\t\t; ---
\tsltiu t1, v1, 0x02\t\t; if(ch==0 || ch==1)
\tbnez t1, @@return
\taddu v1, t0\t\t\t\t; w=width_tbl16[ch-0x40]
\t; ---
\tlbu v1, -0x40(v1)
\tnop
\tj @@read
\taddu v0, v1\t\t\t\t; width+=w
@@return:
\tjr ra
\tnop

;------------------------------------
PrintChar16:\t\t\t; void PrintChar16(int x, int y, int char, int color)
\taddiu sp, -8
\tsw ra, 0(sp)
\tsw s0, 4(sp)
\t; ---
\tlw s0, gfx_pool
\tsll a3, 6\t\t\t\t; color to clut_y
\t; set sprite
\tandi v0, a2, 0xF\t; u=((*str-0x40)%16*8)+88
\tsll v0, 3\t\t\t\t; --
\taddiu v0, 88
\tsb v0, 0xC(s0)\t\t\t; --
\tsrl v0, a2, 4\t\t; v=((*str-0x40)/16*16)+96
\tsll v0, 4\t\t\t\t; --
\taddiu v0, 96
\tsb v0, 0xD(s0)\t\t\t; --
\tli v0, 0x80\t\t\t; r=g=b=128
\tsb v0, 4(s0)\t\t\t; --
\tsb v0, 5(s0)\t\t\t; --
\tsb v0, 6(s0)\t\t\t; --
\tli v0, 4\t\t\t; setSprt
\tsb v0, 3(s0)\t\t\t; ---
\tli v0, 0x64\t\t\t\t; ---
\tsb v0, 7(s0)\t\t\t; ---
\tsh a0, 8(s0)\t\t; x
\tsh a1, 0xA(s0)\t\t; y
\tli v0, (880>>4)|(252<<6)\t; clut
\tor v0, a3\t\t\t\t; ---
\tsh v0, 0xE(s0)\t\t\t; ---
\tli v0, 8\t\t\t; w
\tsh v0, 0x10(s0)\t\t\t; ---
\tli v0, 16\t\t\t; h
\tsh v0, 0x12(s0)\t\t\t; ---
\t; send to ot
\tlw a0, 0x1F800008\t\t; ot
\tmove a1, s0\t\t\t\t; sprt
\tjal AddPrim
\taddiu s0, 5*4\t\t\t; sprt++
\t; ---
\tsw s0, gfx_pool
\tlw ra, 0(sp)
\tlw s0, 4(sp)
\tjr ra
\taddiu sp, 8

;------------------------------------
;------------------------------------
;------------------------------------
.org 0x80108448
.area 4760
ParseDialog:\t\t; void ParseDialog()
\taddiu sp, -12
\tsw ra, 0(sp)
\tsw s0, 4(sp)
\tsw s1, 8(sp)
\t; ---
\tla s0, _dlg_buffer\t\t; parsed dialog destination
\tli s1, 0\t\t\t\t; line counter
@@read:
\tjal ReadDialog\t\t\t; get character
\tnop
\t; ---
\tsltiu t0, v0, 0xB\t\t; check for special codes
\tbeqz t0, @@char\t\t\t; jump if it's a regular character
\tnop
\t; ---
\tla t0, @@switch_jtbl\t; setup switch
\tsll v0, 2\t\t\t\t; --
\taddu t0, v0\t\t\t\t; --
\tlw t0, 0(t0)\t\t\t; --
\tnop
\tjr t0\t\t\t\t\t; jump to case
\tnop
 ; ----------
@@end:\t\t\t\t\t; case 0x00
\tli t0, 1
\tsb t0, _dlg_end\t\t\t; _dlg_end=TRUE, avoid further parsing
\tj @@parse_end
\tsb r0, 0(s0)\t\t\t; *str=NULL
 ; ----------
@@line:\t\t\t\t\t; case 0x01
\taddiu s1, 1\t\t\t\t; y_cnt++
\tslti t0, s1, 4\t\t\t; if(y_cnt<4)
\tbnez t0, @@no_overflow
\tnop
\t; ---
\tsb r0, _dlg_end
\tj @@parse_end
\tsb r0, 0(s0)\t\t\t; *str=NULL
\t; ---
@@no_overflow:
\tli t0, 1\t\t\t\t; line break code
\tsb t0, 0(s0)\t\t\t; *str=1
\tj @@read
\taddiu s0, 1\t\t\t\t; str++
 ; ----------
@@spacing:\t\t\t\t; case 0x02
\tjal ReadDialog\t\t\t; get size
\tnop
\t; ---
\tbeqz v0, @@skip_space\t; check invalid values
\tli t0, 0x40\t\t\t\t; ' '
@@copy_space:
\tsb t0, 0(s0)\t\t\t; *str=' '
\taddiu v0, -1\t\t\t; counter--
\tbgez v0, @@copy_space
\taddiu s0, 1\t\t\t\t; str++
\t; ---
@@skip_space:
\tj @@read
\tnop
 ; ----------
@@music:\t\t\t\t; case 0x03
\tjal ReadDialog\t\t\t; get song index
\tnop
\t; ---
\tli v1, 1
\tla t0, 0x800D1E00
\tsb v1, 0(t0)\t\t\t; 'set song' parameter
\tjal 0x80169158\t\t\t; SetMusic
\tsb v0, 1(t0)\t\t\t; song value
\t; ---
\tj @@read
\tnop
 ; ----------
@@name:\t\t\t\t\t; case 0x04
\tjal ReadDialog\t\t\t; get name index
\tnop
\t; ---
\tla a1, 0x800D1500
\tsll v1, v0, 2\t\t\t; *6
\tsll v0, 1\t\t\t\t; --
\taddu v0, v1\t\t\t\t; --
\tla a0, str_buffer\t\t; destination
\tjal DecodeName
\taddu a1, v0\t\t\t\t; source
\t; ---
\tla a0, str_buffer
@@copy_name:
\tlbu t0, 0(a0)
\taddiu a0, 1
\tbeqz t0, @@name_end
\tnop
\tsb t0, 0(s0)
\tj @@copy_name
\taddiu s0, 1
@@name_end:
\tj @@read
\tnop
 ; ----------
@@delay:\t\t\t\t; case 0x05
\tjal ReadDialog\t\t\t; get delay value
\tnop
\t; ---
\tsll v0, 3
\tlui v1, 0x800D
\tsh v0, 0x08F4(v1)
\tj @@read
\tsh r0, 0x08F6(v1)
 ; ----------
@@autoclose:\t\t\t; case 0x06
\tli t0, 2
\tsb t0, _dlg_end\t\t\t; _dlg_end=2, no idea
\tj @@parse_end
\tsb r0, 0(s0)\t\t\t; *str=NULL
 ; ----------
@@item:\t\t\t\t\t; case 0x07
\tlbu a1, 0x800D08FB
\tjal GetKernelStr
\tli a0, enum_kstr_item
\t; ---
\taddiu v0, 1\t\t\t\t; skip icon
@@copy_item:
\tlbu t0, 0(v0)
\taddiu v0, 1
\tbeqz t0, @@item_end
\tnop
\tsb t0, 0(s0)
\tj @@copy_item
\taddiu s0, 1
@@item_end:
\tj @@read
\tnop
 ; ----------
@@value:\t\t\t\t; case 0x08
\tla v0, 0x800D08F8
\tlbu t1, 2(v0)\t\t\t; read 24 bit value
\tlhu t0, 0(v0)\t\t\t; --
\tsll t1, 16\t\t\t\t; --
\tor a0, t0, t1\t\t\t; value
\tjal __itoa
\tmove a1, s0\t\t\t\t; dest
\t; ---
\tj @@read
\tmove s0, v0\t\t\t\t; returned *str
 ; ----------
@@page:\t\t\t\t\t; case 0x09
\tsb r0, _dlg_end
\tj @@parse_end
\tsb r0, 0(s0)\t\t\t; *str=NULL
 ; ----------
@@center:\t\t\t\t; case 0x0A
\tli t0, 2
\tsb t0, 0(s0)\t\t\t; *str=2
\tj @@read
\taddiu s0, 1\t\t\t\t; str++
 ; ----------
@@char:\t\t\t\t\t; default
\tsb v0, 0(s0)\t\t\t; *str=ch
\tj @@read
\taddiu s0, 1\t\t\t\t; str++
\t; ---
@@parse_end:
\tli v0, 1
\tsb v0, 0x800D06ED
\tlw ra, 0(sp)
\tlw s0, 4(sp)
\tlw s1, 8(sp)
\tjr ra
\taddiu sp, 12

@@switch_jtbl:
.dw @@end, @@line, @@spacing, @@music, @@name, @@delay, @@autoclose, @@item, @@value, @@page, @@center

;-----------------------------------------
ReadDialog:\t\t\t; u8 ReadDialog()
\tlhu v0, _dlg_ptr\t\t; pointer to current character to load
\tla v1, DialogPool\t\t; decompression buffer
\taddu v1, v0\t\t\t\t; buff[ptr]
\tlbu v1, 0(v1)\t\t\t; --
\taddiu v0, 1\t\t\t\t; _dlg_ptr++
\tsh v0, _dlg_ptr\t\t\t; --
\tjr ra
\tmove v0, v1

;-----------------------------------------
__itoa:\t\t\t\t; void __itoa(int value, u8* dest)
\tmove t2, r0
\tbnez a0, @@loc_800113FC
\tmove t1, r0
\tli v0, 0x80
\tsb v0, 0(a1)
\tj @@return
\tli t1, 1
@@loc_800113FC:
\tli a2, 1000000000
\tli t0, 0x66666667
@@loc_8001140C:
\tdiv a0, a2
\tmflo v0
\tbnez a2, @@loc_80011420
\tnop
\tbreak 7
@@loc_80011420:
\tbnez t2, @@loc_80011430
\tmove a3, v0
\tblez a3, @@loc_80011448
\tmult r0, a3, a2
@@loc_80011430:
\taddu v1, a1, t1
\taddiu v0, a3, -0x80
\tsb v0, 0(v1)
\taddiu t1, 1
\tli t2, 1
\tmult r0, a3, a2
@@loc_80011448:
\tmflo v0
\tnop
\tnop
\tmult r0, a2, t0
\tsubu a0, v0
\tsra v0, a2, 31
\tmfhi v1
\tsra v1, 2
\tsubu a2, v1, v0
\tbgtz a2, @@loc_8001140C
\tnop
@@return:
\tjr ra
\taddu v0, a1, t1
DarknessSavior
Guest
« Reply #6 on: July 16, 2011, 05:00:09 pm »

Quote from: Gideon Zhi on July 16, 2011, 04:42:34 pm
Essentially how I do it...
-Detect a whitespace character in the script
-Read the next word, calculate how many pixels it is
-If the length of the word + the length of everything before the word > max pixels per line, replace the whitespace with a linebreak, zero out current pixel count, and continue
-Otherwise, add the length of the word to the current pixel count and continue 'til you hit another whitespace.

Of course, once you start displaying variables in the script (numbers, item names, etc) it starts getting a bit more complicated, but that's the general gist of it.
How are you doing these calculations?

Also, the game has a dictionary system set in place, for character names. Would that make it more complicated?

Gemini, the code was for me to be able to see the ideas in action. While I appreciate your help, since I can't read that code, it basically makes it useless to me. Pseudocode would be best, so that I don't necessarily have to know any particular language to see how it's working.

~DS
Gemini
Guest
« Reply #7 on: July 16, 2011, 05:46:30 pm »

Quote from: DarknessSavior on July 16, 2011, 05:00:09 pm
Also, the game has a dictionary system set in place, for character names. Would that make it more complicated?
Preformatted strings, that's the easiest solution. Use a buffer to store your messages and then do all the line wrap processing. FFIV already uses a buffered system, so you've got to add your line breaks where the text->tile parser comes into action. As for the pseudo-code to apply the wrapping, something like this would work:
Code:
if(str[i]==' ' && cur_w+GetWordWidth(&str[i+1])>24) {str[i]='\\n'; goto @@read_char;}
This way whenever a space is found it counts the next word width and if it exceeds your limit (say 24 tiles) the code replaces the space with a line break, then it goes back to the parsing snippet and does its regular stuff.
DarknessSavior
Guest
« Reply #8 on: July 17, 2011, 01:24:33 pm »

Well, I doubt I'll be writing any new code for FFIV, as far as that goes. It's really not as hard as I thought.

I ran through (meaning translated, edited a bit, and then formatted for insertion) the intro text up until Cecil and Kain leave Baron in about an hour.

~DS
MathOnNapkins
Guest
« Reply #9 on: July 18, 2011, 11:22:18 am »

Consider that by having the dialogue parser implictly do the line breaking for you, you no longer have need for the line break command, saving a decent amount of space in the text. Nothing earth shattering, but if there's on average 20 characters for each line break, that's a redunction in size of a little under 5%. Of course, the added code subtracts from that savings, but I've done this type of code before and I don't recall it taking gobs and gobs of code to pull off.
Gideon Zhi
Guest
« Reply #10 on: July 18, 2011, 11:55:59 am »

Quote from: MathOnNapkins on July 18, 2011, 11:22:18 am
Consider that by having the dialogue parser implictly do the line breaking for you, you no longer have need for the line break command, saving a decent amount of space in the text. Nothing earth shattering, but if there's on average 20 characters for each line break, that's a redunction in size of a little under 5%. Of course, the added code subtracts from that savings, but I've done this type of code before and I don't recall it taking gobs and gobs of code to pull off.

False. Typically, the linebreak command is a single byte, and whitespace characters are a single byte as well. The linebreak usually just replaces a whilespace character in the script; size-wise, having it removed and done automatically is a 1:1 change. You still save time and probably headaches figuring out bad formatting, but you don't save yourself any space.
MathOnNapkins
Guest
« Reply #11 on: July 18, 2011, 01:26:00 pm »

I'll get you next time, Gadget! Next time....
Pages: [1]  


Powered by SMF 1.1.4 | SMF © 2006-2007, Simple Machines LLC