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

Author Topic: How do you handle character-line limits?  (Read 5383 times)

DarknessSavior

  • Hero Member
  • *****
  • Posts: 5031
  • Darkness.
    • View Profile
    • DS: No, not the Nintendo one.
How do you handle character-line limits?
« on: July 16, 2011, 03: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
Red Comet: :'( Poor DS. Nobody loves him like RC does. :'(
Sliver-X: LET ME INFRINGE UPON IT WITH MY MOUTH
DSRH - Currently working on: Demon's Blazon, Romancing SaGa, FFIV EasyType.
http://www.youtube.com/user/DarknessSavior

Gemini

  • Hero Member
  • *****
  • Posts: 2015
  • 時を越えよう、そして彼女の元に戻ろう
    • View Profile
    • Apple of Eden
Re: How do you handle character-line limits?
« Reply #1 on: July 16, 2011, 04: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.
I am the lord, you all know my name, now. I got it all: cash, money, and fame.

Gideon Zhi

  • IRC Staff
  • Hero Member
  • *****
  • Posts: 3505
    • View Profile
    • Aeon Genesis
Re: How do you handle character-line limits?
« Reply #2 on: July 16, 2011, 05: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

  • Hero Member
  • *****
  • Posts: 5031
  • Darkness.
    • View Profile
    • DS: No, not the Nintendo one.
Re: How do you handle character-line limits?
« Reply #3 on: July 16, 2011, 05: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
Red Comet: :'( Poor DS. Nobody loves him like RC does. :'(
Sliver-X: LET ME INFRINGE UPON IT WITH MY MOUTH
DSRH - Currently working on: Demon's Blazon, Romancing SaGa, FFIV EasyType.
http://www.youtube.com/user/DarknessSavior

Gideon Zhi

  • IRC Staff
  • Hero Member
  • *****
  • Posts: 3505
    • View Profile
    • Aeon Genesis
Re: How do you handle character-line limits?
« Reply #4 on: July 16, 2011, 05: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

  • Hero Member
  • *****
  • Posts: 2015
  • 時を越えよう、そして彼女の元に戻ろう
    • View Profile
    • Apple of Eden
Re: How do you handle character-line limits?
« Reply #5 on: July 16, 2011, 05:50:14 pm »
This is the entire code used in FFIV, but it's MIPS R3000, so it's only for reference:
Code: [Select]
PrintString16: ; void PrintString16(int x, int y, int char, int color)
addiu sp, -28
sw ra, 0(sp)
sw s0, 4(sp)
sw s1, 8(sp)
sw s2, 12(sp)
sw s3, 16(sp)
sw s4, 20(sp)
sw s5, 24(sp)
; ---
move s0, a0 ; x
move s1, a1 ; y
move s2, a2 ; str
move s3, a3 ; color
move s4, a0 ; x base
li s5, 0 ; center flag
@@read:
lbu a2, 0(s2) ; *str
addiu s2, 1 ; str++
sltiu v0, a2, 3 ; if(c<2)
beqz v0, @@char
nop
; -
sll a2, 2 ; setup switch
la v0, @@switch_jtbl ; --
addu v0, a2 ; --
lw v0, 0(v0) ; --
nop
jr v0 ; jump to case
nop
@@line: ; case 0x01
beqz s5, @@align_off
move s0, s4 ; reset x
; ---
jal GetLineWidth
move a0, s2
; ---
li t0, 256 ; screen width/2
subu s0, t0, v0 ; x=(screen_w-line_w)/2
srl s0, 1 ; --
@@align_off:
j @@read
addiu s1, 14 ; y+=14
 ; --------------
@@center: ; case 0x02
li s5, 1 ; turn on alignment flag
jal GetLineWidth
move a0, s2
; ---
li t0, 256 ; screen width/2
subu s0, t0, v0 ; x=(screen_w-line_w)/2
j @@read
srl s0, 1 ; --
 ; --------------
@@char: ; default
li t8, 0xFF
beq a2, t8, @@skip_draw ; if(c==0xFF) continue;
li t8, 0x40 ; ' '
; ---
bne a2, t8, @@not_space
nop
; ---
jal GetWordWidth
move a0, s2
; ---
addu t0, s0, v0
sltiu t0, 233 ; if(x+GetWordWidth(&str[1])<232)
bnez t0, @@not_space
li a2, 0x40 ; set a sprite if no overflow happens
; ---
j @@line
nop
; ---
@@not_space:
addiu a2, -0x40 ; set to 0x40-0xFF range
la t0, font_width16 ; get character width
addu t0, a2 ; ---
lbu t0, 0(t0) ; ---
move a0, s0 ; x
move a1, s1 ; y
move a3, s3 ; color
jal PrintChar16 ; print character
addu s0, t0 ; x+=width[c]
@@skip_draw:
j @@read
nop
; ---------
@@end: ; case 0x00
lw s0, gfx_pool
li v0, 1
sb v0, 3(s0)
li v0, 0xE100000E
sw v0, 4(s0)
lw a0, 0x1F800008 ; ot
move a1, s0 ; primitive
jal AddPrim
addiu s0, 8
; --
sw s0, gfx_pool
lw ra, 0(sp)
lw s0, 4(sp)
lw s1, 8(sp)
lw s2, 12(sp)
lw s3, 16(sp)
lw s4, 20(sp)
lw s5, 24(sp)
jr ra
addiu sp, 28

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

;------------------------------------
GetWordWidth: ; int GetWordWidth(u8* str)
la t0, font_width16
li v0, 0 ; reset width counter
@@read:
lbu v1, 0(a0) ; ch=*str++
addiu a0, 1 ; ---
sltiu t1, v1, 0x41 ; if(ch<0x40)
bnez t1, @@return
addu v1, t0 ; w=width_tbl16[ch-0x40]
; ---
lbu v1, -0x40(v1)
nop
j @@read
addu v0, v1 ; width+=w
@@return:
jr ra
nop

;------------------------------------
GetLineWidth: ; int GetWordWidth(u8* str)
la t0, font_width16
li v0, 0 ; reset width counter
@@read:
lbu v1, 0(a0) ; ch=*str++
addiu a0, 1 ; ---
sltiu t1, v1, 0x02 ; if(ch==0 || ch==1)
bnez t1, @@return
addu v1, t0 ; w=width_tbl16[ch-0x40]
; ---
lbu v1, -0x40(v1)
nop
j @@read
addu v0, v1 ; width+=w
@@return:
jr ra
nop

;------------------------------------
PrintChar16: ; void PrintChar16(int x, int y, int char, int color)
addiu sp, -8
sw ra, 0(sp)
sw s0, 4(sp)
; ---
lw s0, gfx_pool
sll a3, 6 ; color to clut_y
; set sprite
andi v0, a2, 0xF ; u=((*str-0x40)%16*8)+88
sll v0, 3 ; --
addiu v0, 88
sb v0, 0xC(s0) ; --
srl v0, a2, 4 ; v=((*str-0x40)/16*16)+96
sll v0, 4 ; --
addiu v0, 96
sb v0, 0xD(s0) ; --
li v0, 0x80 ; r=g=b=128
sb v0, 4(s0) ; --
sb v0, 5(s0) ; --
sb v0, 6(s0) ; --
li v0, 4 ; setSprt
sb v0, 3(s0) ; ---
li v0, 0x64 ; ---
sb v0, 7(s0) ; ---
sh a0, 8(s0) ; x
sh a1, 0xA(s0) ; y
li v0, (880>>4)|(252<<6) ; clut
or v0, a3 ; ---
sh v0, 0xE(s0) ; ---
li v0, 8 ; w
sh v0, 0x10(s0) ; ---
li v0, 16 ; h
sh v0, 0x12(s0) ; ---
; send to ot
lw a0, 0x1F800008 ; ot
move a1, s0 ; sprt
jal AddPrim
addiu s0, 5*4 ; sprt++
; ---
sw s0, gfx_pool
lw ra, 0(sp)
lw s0, 4(sp)
jr ra
addiu sp, 8

;------------------------------------
;------------------------------------
;------------------------------------
.org 0x80108448
.area 4760
ParseDialog: ; void ParseDialog()
addiu sp, -12
sw ra, 0(sp)
sw s0, 4(sp)
sw s1, 8(sp)
; ---
la s0, _dlg_buffer ; parsed dialog destination
li s1, 0 ; line counter
@@read:
jal ReadDialog ; get character
nop
; ---
sltiu t0, v0, 0xB ; check for special codes
beqz t0, @@char ; jump if it's a regular character
nop
; ---
la t0, @@switch_jtbl ; setup switch
sll v0, 2 ; --
addu t0, v0 ; --
lw t0, 0(t0) ; --
nop
jr t0 ; jump to case
nop
 ; ----------
@@end: ; case 0x00
li t0, 1
sb t0, _dlg_end ; _dlg_end=TRUE, avoid further parsing
j @@parse_end
sb r0, 0(s0) ; *str=NULL
 ; ----------
@@line: ; case 0x01
addiu s1, 1 ; y_cnt++
slti t0, s1, 4 ; if(y_cnt<4)
bnez t0, @@no_overflow
nop
; ---
sb r0, _dlg_end
j @@parse_end
sb r0, 0(s0) ; *str=NULL
; ---
@@no_overflow:
li t0, 1 ; line break code
sb t0, 0(s0) ; *str=1
j @@read
addiu s0, 1 ; str++
 ; ----------
@@spacing: ; case 0x02
jal ReadDialog ; get size
nop
; ---
beqz v0, @@skip_space ; check invalid values
li t0, 0x40 ; ' '
@@copy_space:
sb t0, 0(s0) ; *str=' '
addiu v0, -1 ; counter--
bgez v0, @@copy_space
addiu s0, 1 ; str++
; ---
@@skip_space:
j @@read
nop
 ; ----------
@@music: ; case 0x03
jal ReadDialog ; get song index
nop
; ---
li v1, 1
la t0, 0x800D1E00
sb v1, 0(t0) ; 'set song' parameter
jal 0x80169158 ; SetMusic
sb v0, 1(t0) ; song value
; ---
j @@read
nop
 ; ----------
@@name: ; case 0x04
jal ReadDialog ; get name index
nop
; ---
la a1, 0x800D1500
sll v1, v0, 2 ; *6
sll v0, 1 ; --
addu v0, v1 ; --
la a0, str_buffer ; destination
jal DecodeName
addu a1, v0 ; source
; ---
la a0, str_buffer
@@copy_name:
lbu t0, 0(a0)
addiu a0, 1
beqz t0, @@name_end
nop
sb t0, 0(s0)
j @@copy_name
addiu s0, 1
@@name_end:
j @@read
nop
 ; ----------
@@delay: ; case 0x05
jal ReadDialog ; get delay value
nop
; ---
sll v0, 3
lui v1, 0x800D
sh v0, 0x08F4(v1)
j @@read
sh r0, 0x08F6(v1)
 ; ----------
@@autoclose: ; case 0x06
li t0, 2
sb t0, _dlg_end ; _dlg_end=2, no idea
j @@parse_end
sb r0, 0(s0) ; *str=NULL
 ; ----------
@@item: ; case 0x07
lbu a1, 0x800D08FB
jal GetKernelStr
li a0, enum_kstr_item
; ---
addiu v0, 1 ; skip icon
@@copy_item:
lbu t0, 0(v0)
addiu v0, 1
beqz t0, @@item_end
nop
sb t0, 0(s0)
j @@copy_item
addiu s0, 1
@@item_end:
j @@read
nop
 ; ----------
@@value: ; case 0x08
la v0, 0x800D08F8
lbu t1, 2(v0) ; read 24 bit value
lhu t0, 0(v0) ; --
sll t1, 16 ; --
or a0, t0, t1 ; value
jal __itoa
move a1, s0 ; dest
; ---
j @@read
move s0, v0 ; returned *str
 ; ----------
@@page: ; case 0x09
sb r0, _dlg_end
j @@parse_end
sb r0, 0(s0) ; *str=NULL
 ; ----------
@@center: ; case 0x0A
li t0, 2
sb t0, 0(s0) ; *str=2
j @@read
addiu s0, 1 ; str++
 ; ----------
@@char: ; default
sb v0, 0(s0) ; *str=ch
j @@read
addiu s0, 1 ; str++
; ---
@@parse_end:
li v0, 1
sb v0, 0x800D06ED
lw ra, 0(sp)
lw s0, 4(sp)
lw s1, 8(sp)
jr ra
addiu sp, 12

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

;-----------------------------------------
ReadDialog: ; u8 ReadDialog()
lhu v0, _dlg_ptr ; pointer to current character to load
la v1, DialogPool ; decompression buffer
addu v1, v0 ; buff[ptr]
lbu v1, 0(v1) ; --
addiu v0, 1 ; _dlg_ptr++
sh v0, _dlg_ptr ; --
jr ra
move v0, v1

;-----------------------------------------
__itoa: ; void __itoa(int value, u8* dest)
move t2, r0
bnez a0, @@loc_800113FC
move t1, r0
li v0, 0x80
sb v0, 0(a1)
j @@return
li t1, 1
@@loc_800113FC:
li a2, 1000000000
li t0, 0x66666667
@@loc_8001140C:
div a0, a2
mflo v0
bnez a2, @@loc_80011420
nop
break 7
@@loc_80011420:
bnez t2, @@loc_80011430
move a3, v0
blez a3, @@loc_80011448
mult r0, a3, a2
@@loc_80011430:
addu v1, a1, t1
addiu v0, a3, -0x80
sb v0, 0(v1)
addiu t1, 1
li t2, 1
mult r0, a3, a2
@@loc_80011448:
mflo v0
nop
nop
mult r0, a2, t0
subu a0, v0
sra v0, a2, 31
mfhi v1
sra v1, 2
subu a2, v1, v0
bgtz a2, @@loc_8001140C
nop
@@return:
jr ra
addu v0, a1, t1
I am the lord, you all know my name, now. I got it all: cash, money, and fame.

DarknessSavior

  • Hero Member
  • *****
  • Posts: 5031
  • Darkness.
    • View Profile
    • DS: No, not the Nintendo one.
Re: How do you handle character-line limits?
« Reply #6 on: July 16, 2011, 06:00:09 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
Red Comet: :'( Poor DS. Nobody loves him like RC does. :'(
Sliver-X: LET ME INFRINGE UPON IT WITH MY MOUTH
DSRH - Currently working on: Demon's Blazon, Romancing SaGa, FFIV EasyType.
http://www.youtube.com/user/DarknessSavior

Gemini

  • Hero Member
  • *****
  • Posts: 2015
  • 時を越えよう、そして彼女の元に戻ろう
    • View Profile
    • Apple of Eden
Re: How do you handle character-line limits?
« Reply #7 on: July 16, 2011, 06:46:30 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: [Select]
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.
I am the lord, you all know my name, now. I got it all: cash, money, and fame.

DarknessSavior

  • Hero Member
  • *****
  • Posts: 5031
  • Darkness.
    • View Profile
    • DS: No, not the Nintendo one.
Re: How do you handle character-line limits?
« Reply #8 on: July 17, 2011, 02: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
Red Comet: :'( Poor DS. Nobody loves him like RC does. :'(
Sliver-X: LET ME INFRINGE UPON IT WITH MY MOUTH
DSRH - Currently working on: Demon's Blazon, Romancing SaGa, FFIV EasyType.
http://www.youtube.com/user/DarknessSavior

MathOnNapkins

  • Forum Moderator
  • Hero Member
  • *****
  • Posts: 636
  • Who ya gonna call
    • View Profile
    • Arc-Nova - Rohmackin' and Chiptunin'
Re: How do you handle character-line limits?
« Reply #9 on: July 18, 2011, 12:22:18 pm »
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

  • IRC Staff
  • Hero Member
  • *****
  • Posts: 3505
    • View Profile
    • Aeon Genesis
Re: How do you handle character-line limits?
« Reply #10 on: July 18, 2011, 12:55:59 pm »
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

  • Forum Moderator
  • Hero Member
  • *****
  • Posts: 636
  • Who ya gonna call
    • View Profile
    • Arc-Nova - Rohmackin' and Chiptunin'
Re: How do you handle character-line limits?
« Reply #11 on: July 18, 2011, 02:26:00 pm »
I'll get you next time, Gadget! Next time....