Add Item Description loading
Basic Dialog macro expansion added Handle rstripped tsv lines safely
This commit is contained in:
parent
83f8c76fe5
commit
fae527ec01
|
@ -5,6 +5,7 @@ character_names 5 0x115500 6
|
|||
dialogue 0x900 0x2013F0 0x082220 3 2 0x000000 0x0A0000 True
|
||||
enemy_names 384 0x200050 0x105C00 10 8
|
||||
items 0x100 0x111380 9
|
||||
item_descriptions 128 0x114000 2 0x110000 0x110000 True True
|
||||
job_names 22 0x115600 8
|
||||
job_and_ability_descs 133 0x117140 2 0x110000 0x110000 True
|
||||
magics 87 0x111C80 6
|
||||
|
|
Can't render this file because it has a wrong number of fields in line 5.
|
|
@ -1,4 +1,5 @@
|
|||
extends Node
|
||||
const ff5_dialog := preload('res://scripts/loaders/snes/ff5_dialog.gd')
|
||||
|
||||
var SNES_block_addresses := Common.load_tsv('res://data/string_blocks.tsv')
|
||||
var tables_raw = {}
|
||||
|
@ -28,68 +29,76 @@ func decode_array(array, glyph_table, trim_trailing_whitespace: bool = true) ->
|
|||
output.append(decode_string(s, glyph_table, trim_trailing_whitespace))
|
||||
return output
|
||||
|
||||
func _load_block(block_name: String, buffer: StreamPeerBuffer, is_RPGe: bool = false) -> void:
|
||||
var block: Dictionary = SNES_block_addresses[block_name]
|
||||
var glyph_table_small: PoolStringArray = glyph_tables.RPGe_small if is_RPGe else glyph_tables.SNES_small
|
||||
var glyph_table_dialog: PoolStringArray = glyph_tables.RPGe_dialog if is_RPGe else glyph_tables.SNES_dialog
|
||||
var glyph_table_kanji: PoolStringArray = glyph_tables.SNES_kanji
|
||||
var raw_strings := []
|
||||
var strings := PoolStringArray()
|
||||
var num_entries: int = block.num_entries
|
||||
var l1_width: int = block.bytes
|
||||
if (not is_RPGe) and block.snes_bytes:
|
||||
l1_width = block.snes_bytes
|
||||
var l1_address: int = block.address
|
||||
if (not is_RPGe) and block.snes_address:
|
||||
l1_address = block.snes_address
|
||||
buffer.seek(l1_address)
|
||||
|
||||
var ptr_offset = block.rpge_ptr_offset if is_RPGe else block.snes_ptr_offset
|
||||
if ptr_offset is int:
|
||||
var ptrs = PoolIntArray()
|
||||
match l1_width:
|
||||
1:
|
||||
for i in num_entries:
|
||||
ptrs.append(ptr_offset + buffer.get_u8())
|
||||
2:
|
||||
for i in num_entries:
|
||||
ptrs.append(ptr_offset + buffer.get_u16()) # Bank wrapping
|
||||
3:
|
||||
for i in num_entries:
|
||||
ptrs.append(ptr_offset + buffer.get_u16() + (buffer.get_u8() << 16)) # Bank wrapping
|
||||
_:
|
||||
assert(false, 'Indirect l1_width of %d is not possible' % l1_width)
|
||||
if block.null_terminated:
|
||||
for i in num_entries:
|
||||
buffer.seek(ptrs[i] & 0x3FFFFF) # Bank wrapping
|
||||
var bytes = PoolByteArray()
|
||||
while true:
|
||||
var b = buffer.get_u8()
|
||||
if b == 0:
|
||||
break
|
||||
bytes.append(b)
|
||||
raw_strings.append(bytes)
|
||||
else:
|
||||
for i in num_entries-1:
|
||||
var size: int = ptrs[i+1] - ptrs[i]
|
||||
if size > 0:
|
||||
buffer.seek(ptrs[i] & 0x3FFFFF) # Bank wrapping
|
||||
raw_strings.append(buffer.get_data(size)[1])
|
||||
elif size == 0:
|
||||
raw_strings.append(PoolByteArray())
|
||||
else:
|
||||
print_debug('String pointer mismatch: "%s" index %d: 0x%06X:0x%06X, effective size of %d bytes' % [block_name, i, ptrs[i], ptrs[i+1], size])
|
||||
break
|
||||
else:
|
||||
# Get first level of data
|
||||
for i in num_entries:
|
||||
raw_strings.append(buffer.get_data(l1_width)[1])
|
||||
|
||||
# Decode
|
||||
if block.dialog:
|
||||
for raw in raw_strings:
|
||||
strings.append(ff5_dialog.decode_string_dialog(raw, glyph_table_dialog, glyph_table_kanji))
|
||||
else:
|
||||
for raw in raw_strings:
|
||||
strings.append(decode_string(raw, glyph_table_small))
|
||||
self.tables_raw[block_name] = raw_strings
|
||||
self.tables[block_name] = strings
|
||||
|
||||
func load_snes_rom(buffer: StreamPeerBuffer, is_RPGe: bool = false) -> void:
|
||||
for block_name in SNES_block_addresses:
|
||||
var block: Dictionary = SNES_block_addresses[block_name]
|
||||
var glyph_table_small: PoolStringArray = glyph_tables.RPGe_small if is_RPGe else glyph_tables.SNES_small
|
||||
var raw_strings := []
|
||||
var strings := PoolStringArray()
|
||||
var num_entries: int = block.num_entries
|
||||
var l1_width: int = block.bytes
|
||||
if (not is_RPGe) and block.snes_bytes:
|
||||
l1_width = block.snes_bytes
|
||||
var l1_address: int = block.address
|
||||
if (not is_RPGe) and block.snes_address:
|
||||
l1_address = block.snes_address
|
||||
buffer.seek(l1_address)
|
||||
|
||||
var ptr_offset = block.rpge_ptr_offset if is_RPGe else block.snes_ptr_offset
|
||||
if ptr_offset is int:
|
||||
var ptrs = PoolIntArray()
|
||||
match l1_width:
|
||||
1:
|
||||
for i in num_entries:
|
||||
ptrs.append((ptr_offset + buffer.get_u8()) & 0x3FFFFF) # Bank wrapping
|
||||
2:
|
||||
for i in num_entries:
|
||||
ptrs.append((ptr_offset + buffer.get_u16()) & 0x3FFFFF) # Bank wrapping
|
||||
3:
|
||||
for i in num_entries:
|
||||
ptrs.append((ptr_offset + buffer.get_u16() + (buffer.get_u8() << 16)) & 0x3FFFFF) # Bank wrapping
|
||||
_:
|
||||
assert(false, 'Indirect l1_width of %d is not possible' % l1_width)
|
||||
if block.null_terminated:
|
||||
for i in num_entries:
|
||||
buffer.seek(ptrs[i])
|
||||
var bytes = PoolByteArray()
|
||||
while true:
|
||||
var b = buffer.get_u8()
|
||||
if b == 0:
|
||||
break
|
||||
bytes.append(b)
|
||||
raw_strings.append(bytes)
|
||||
else:
|
||||
for i in num_entries-1:
|
||||
buffer.seek(ptrs[i])
|
||||
var size: int = ptrs[i+1] - ptrs[i]
|
||||
if size > 0:
|
||||
raw_strings.append(buffer.get_data(size)[1])
|
||||
else:
|
||||
print_debug('String pointer mismatch: "%s" index %d: 0x%06X:0x%06X, effective size of %d bytes' % [block_name, i, ptrs[i], ptrs[i+1], size])
|
||||
raw_strings.append(PoolByteArray())
|
||||
else:
|
||||
# Get first level of data
|
||||
for i in num_entries:
|
||||
raw_strings.append(buffer.get_data(l1_width)[1])
|
||||
|
||||
# Decode
|
||||
if block.dialog:
|
||||
pass # TODO
|
||||
else:
|
||||
for raw in raw_strings:
|
||||
strings.append(decode_string(raw, glyph_table_small))
|
||||
self.tables_raw[block_name] = raw_strings
|
||||
self.tables[block_name] = strings
|
||||
self._load_block(block_name, buffer, is_RPGe)
|
||||
|
||||
func get_ability_name(id: int) -> String:
|
||||
if id < 128:
|
||||
|
@ -107,6 +116,16 @@ func get_ability_desc(id: int) -> String:
|
|||
assert(false, 'ability id %d out of description ranges' % id)
|
||||
return 'ability id %d out of description ranges' % id
|
||||
|
||||
func get_inventory_item_desc(id: int) -> String:
|
||||
var desc_id := 0
|
||||
if id < 0x80:
|
||||
desc_id = RomLoader.snes_data.tbl_weapons[id].description
|
||||
elif id < 0xE0:
|
||||
desc_id = RomLoader.snes_data.tbl_armors[id-0x80].description
|
||||
else:
|
||||
desc_id = RomLoader.snes_data.tbl_items[id-0xE0].description
|
||||
return self.tables.item_descriptions[desc_id]
|
||||
|
||||
func get_job_name(id: int) -> String:
|
||||
return self.tables.job_names[id]
|
||||
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
const LITERAL_MACROS := {
|
||||
# Is 0x00 a wait for input marker?
|
||||
# 0x01 is linebreak
|
||||
#0x02: [0x20, 0xBC, 0x82], # 0x02 expands to Bartz's name バッツ. Used for his dialogue in EN, only used for other chars in JP.
|
||||
0x03: [0x6E, 0xA8, 0x78, 0x7E, 0xAA], # 0x03 is クリスタル
|
||||
0x04: [0x7E, 0x8C, 0x6E, 0xC5, 0xB8], # expands to タイクーン
|
||||
0x06: [0x37, 0xBF], # expands to じゃ
|
||||
0x07: [0x8D, 0xAB], # expands to いる
|
||||
0x08: [0xFF, 0xFF, 0xFF, 0xFF], # 4 spaces
|
||||
0x09: [0xFF, 0xFF, 0xFF], # 3 spaces
|
||||
0x0A: [0xFF, 0xFF], # 2 spaces
|
||||
0x0B: [0x1E12, 0x1E13], # expands to 魔物
|
||||
# 0x0C appears to be a pause in delivery - affects previous char
|
||||
0x0D: [0x1E24, 0x9B, 0x1E52, 0x1E57], # expands to 風の神殿
|
||||
0x0E: [0x1E04, 0x1E0A], # expands to 飛竜
|
||||
# 0x0F - unknown (invisible control char)
|
||||
# 0x10 is a gil substitution
|
||||
# 0x11 and 0x12 appear to be item (obtained) substitutions
|
||||
0x13: [0x1E07, 0x1E0D], # expands to 封印
|
||||
0x14: [0x76, 0x46, 0xD0], # Cid speaking - シド「
|
||||
0x15: [0x9E, 0x46, 0xD0], # Mid speaking - ミド「
|
||||
0x16: [0x1E05, 0x1E06], # expands to 世界
|
||||
# 0x17 uses the next byte for pause duration (seconds?)
|
||||
0x18: [0x8E, 0x6E, 0x78, 0x44, 0x78], # expands to エクスデス
|
||||
0x19: [0xAC, 0x92, 0xD0], # Lenna speaking - レナ「
|
||||
0x1A: [0x2A, 0xA6, 0x64, 0xD0], # Galuf speaking - ガラフ「
|
||||
0x1B: [0x64, 0xC4, 0xA8, 0x78, 0xD0], # Faris speaking - ファリス「
|
||||
0x1C: [0x6E, 0xAA, 0xAA, 0xD0], # Krile/Kara speaking - クルル「
|
||||
0x1D: [0x91, 0x37, 0x8D, 0x81, 0xBF, 0xB9], # expands to おじいちゃん
|
||||
# 0x1E-0x1F form kanji with the next byte
|
||||
# 0x20-0xCC are standard character set
|
||||
0xCD: [0xC9, 0xC9], # % (0xCD) to !!
|
||||
# 0xCE is /
|
||||
0xCF: [0xBD, 0x85], # : (0xCF) appears to expand to って
|
||||
# 0xD0-0xD4 are 「」。AB
|
||||
0xD5: [0x1E1B, 0x95, 0x1E08, 0xAD], # expands to 手に入れ
|
||||
# 0xD6, 0xD7, 0xD8 are YLR
|
||||
0xD9: [0x93, 0x8D], # expands to ない
|
||||
# 0xDA-0xDC are HMP
|
||||
0xDD: [0xC7, 0xC7], # S (0xDD) to ……
|
||||
0xDE: [0x3F, 0x8D, 0x37, 0xC3, 0x89, 0x25], # C (0xDE) to だいじょうぶ
|
||||
0xDF: [0x61, 0xE3], # T (0xDF) to は、
|
||||
0xE0: [0xB9, 0x3F], # expands to んだ
|
||||
0xE1: [0x85, 0x8D], # expands to てい
|
||||
0xE2: [0x77, 0x7F], # expands to した
|
||||
# 0xE3 is 、
|
||||
0xE4: [0x77, 0x85], # ◯ (0xE4) appears to expand to して
|
||||
# 0xE5 is used for Bartz speaking in JP. This only appears as 『
|
||||
0xE6: [0x91, 0x1E0F, 0x1E03], # F (0xE6) appears to expand to otousan (お父様)
|
||||
0xE7: [0xC9, 0xCB], # °C (0xE7) to !? - yes this is the wrong order interrobang
|
||||
0xE8: [0x45, 0x79], # ・ (0xE8) appears to expand to です
|
||||
# 0xE9, 0xEA are ()
|
||||
0xEB: [0x73, 0x9B], # expands to この
|
||||
0xEC: [0x9B, 0x1E02], # expands to の力
|
||||
0xED: [0x70, 0xAA, 0x2A, 0xC5], # expands to ケルガー
|
||||
0xEE: [0x1E86, 0x1ED7, 0x1E87, 0x1E62, 0x1EA7], # expands to 古代図書舘 (ancient library?)
|
||||
0xEF: [0x1E1C, 0xBD, 0x85], # expands to 言って
|
||||
0xF0: [0x1E2B, 0x1E0B, 0xD0], # soldier speaking - 兵士「
|
||||
0xF1: [0x6B, 0xA7], # expands to から
|
||||
0xF2: [0x1E2C, 0x6A, 0x1E0C], # expands to 火カ船
|
||||
0xF3: [0x1E0E, 0x3D, 0x6F], # expands to 海ぞく
|
||||
0xF4: [0x8D, 0x37, 0xC3, 0x89], # expands to いじょう
|
||||
0xF5: [0x2B, 0xE3], # expands to が、
|
||||
0xF6: [0x7F, 0x81], # expands to たち
|
||||
0xF7: [0x7F, 0x9B], # expands to たの
|
||||
0xF8: [0x9D, 0x79], # expands to ます
|
||||
0xF9: [0x6F, 0x3F, 0x75, 0x8D], # expands to ください
|
||||
0xFA: [0x6B, 0xBD, 0x7F], # expands to かった
|
||||
0xFB: [0x7F, 0xC9], # expands to た!
|
||||
0xFC: [0x95, 0xE3], # expands to に、
|
||||
0xFD: [0x8D, 0x93, 0x8D, 0x6B, 0xA7, 0x93, 0xB9, 0x3F], # expands to いないからなんだ
|
||||
0xFE: [0x1F20, 0x1F38, 0x9B, 0x61, 0x35, 0x9D], # expands to 次元のはざま
|
||||
# 0xFF is space
|
||||
}
|
||||
|
||||
static func decode_string_dialog(bytes, glyph_table: PoolStringArray, glyph_table_kanji: PoolStringArray, variables:={}, trim_trailing_whitespace: bool = true) -> String:
|
||||
# Dialog requires multibyte handling including kanji and other macros
|
||||
var expanded_bytes = PoolIntArray() # Kanji are 16bit codes
|
||||
var output = ''
|
||||
var i := 0
|
||||
var end := len(bytes)
|
||||
while i < end:
|
||||
var c: int = bytes[i]
|
||||
match c:
|
||||
# 0x17 uses the next byte for pause duration (seconds?)
|
||||
0x17, 0x1E, 0x1F: # Kanji
|
||||
i += 1
|
||||
expanded_bytes.append((c << 8) + bytes[i])
|
||||
_:
|
||||
expanded_bytes.append_array(LITERAL_MACROS.get(c, [c]))
|
||||
i += 1
|
||||
for c in expanded_bytes:
|
||||
match c:
|
||||
0x02:
|
||||
output += variables.get('bartz_name', 'Bartz')
|
||||
0x0C: # 0x0C appears to be a pause in delivery - affects previous char
|
||||
pass
|
||||
0x0F: # 0x0F - unknown (invisible control char)
|
||||
pass
|
||||
0x10: # 0x10 is a gil substitution
|
||||
output += '%d' % variables.get('gil_amount', 0)
|
||||
0x11, 0x12: # 0x11 and 0x12 appear to be item (obtained) substitutions
|
||||
output += variables.get('item_name', 'unk_item')
|
||||
_:
|
||||
if c < 0x1700:
|
||||
output += glyph_table[c]
|
||||
elif c < 0x1E00:
|
||||
var delay = c - 0x1700
|
||||
pass # TODO: work out a way to signal dialog delivery pauses
|
||||
else:
|
||||
output += glyph_table_kanji[c - 0x1E00]
|
||||
return output.trim_suffix(' ') if trim_trailing_whitespace else output
|
|
@ -80,7 +80,7 @@ static func load_glyph_table(filename: String) -> PoolStringArray:
|
|||
OK:
|
||||
var l: int = file.get_len()
|
||||
while file.get_position() < l:
|
||||
output.append(file.get_line())
|
||||
output.append(file.get_line().replace('\\n', '\n').replace('’', "'").replace('”', '"')) # TODO: Fix font to accept the special quote marks
|
||||
var error:
|
||||
print_debug('Failed to open glyph table "%s" - %d' % [filename, error])
|
||||
return output
|
||||
|
@ -145,6 +145,9 @@ static func limited_eval(token: String):
|
|||
var hex := token.hex_to_int()
|
||||
if hex > 0:
|
||||
return hex
|
||||
elif token.substr(2).is_valid_integer():
|
||||
# Special case for 0x000000...
|
||||
return 0
|
||||
# Try int literal
|
||||
if token.is_valid_integer():
|
||||
return int(token)
|
||||
|
@ -174,6 +177,8 @@ static func load_tsv(filename: String, delimiter: String = '\t') -> Dictionary:
|
|||
for i in range(1, n):
|
||||
if line.size() > i:
|
||||
entry[headers[i]] = limited_eval(line[i])
|
||||
else:
|
||||
entry[headers[i]] = limited_eval('')
|
||||
output[line[0]] = entry
|
||||
return output
|
||||
print_debug(error)
|
||||
|
|
|
@ -3,9 +3,12 @@ extends Panel
|
|||
onready var sidebar_classic := $'%sidebar_classic'
|
||||
onready var sbc_menu_item_containers := [$'%menu_items_1', $'%menu_items_2']
|
||||
|
||||
onready var lbl_time := $'%lbl_time'
|
||||
onready var lbl_gilcount := $'%lbl_gilcount'
|
||||
onready var characters_container := $'%characters_container'
|
||||
onready var items_menu := $'%items_menu'
|
||||
onready var items_container := $'%items_container'
|
||||
onready var lbl_item_description := $'%lbl_item_description'
|
||||
|
||||
enum Submenu {PARTY, ITEMS}
|
||||
const str2submenu := {
|
||||
|
@ -26,12 +29,12 @@ func update_labels(data: Dictionary):
|
|||
p.visible = not characters[i].is_absent
|
||||
p.update_labels(data, i)
|
||||
|
||||
$'%lbl_time'.text = Common.game_time_frames_to_hhmm(data.game_time_frames)
|
||||
$'%lbl_gilcount'.text = '%d' % data.current_gil
|
||||
lbl_time.text = Common.game_time_frames_to_hhmm(data.game_time_frames)
|
||||
lbl_gilcount.text = '%d' % data.current_gil
|
||||
|
||||
# Populate inventory
|
||||
for child in items_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
for i in 256:
|
||||
var item_id = data.inventory_item_ids[i]
|
||||
var item_qty = data.inventory_item_qtys[i]
|
||||
|
@ -42,6 +45,10 @@ func update_labels(data: Dictionary):
|
|||
if item_qty > 0:
|
||||
lbl_item_name.text = item_name
|
||||
lbl_item_qty.text = 'x%d'%item_qty
|
||||
var desc = StringLoader.get_inventory_item_desc(item_id)
|
||||
if desc:
|
||||
lbl_item_name.mouse_filter = MOUSE_FILTER_PASS
|
||||
lbl_item_name.hint_tooltip = desc
|
||||
items_container.add_child(lbl_item_name)
|
||||
items_container.add_child(lbl_item_qty)
|
||||
|
||||
|
|
|
@ -295,7 +295,6 @@ rect_min_size = Vector2( 320, 240 )
|
|||
|
||||
[node name="items_menu" type="Control" parent="submenus"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
|
@ -355,7 +354,8 @@ anchor_right = 1.0
|
|||
margin_top = 22.0
|
||||
margin_bottom = 66.0
|
||||
|
||||
[node name="Label" type="Label" parent="submenus/items_menu/itemdesc"]
|
||||
[node name="lbl_item_description" type="Label" parent="submenus/items_menu/itemdesc"]
|
||||
unique_name_in_owner = true
|
||||
margin_left = 4.0
|
||||
margin_top = 4.0
|
||||
margin_right = 316.0
|
||||
|
@ -381,6 +381,7 @@ columns = 4
|
|||
|
||||
[node name="characters_container" type="VBoxContainer" parent="submenus"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
margin_left = 8.0
|
||||
|
|
Loading…
Reference in New Issue