diff --git a/data/SNES_PSX_addresses.tsv b/data/SNES_PSX_addresses.tsv index fbe21a7..4da9a7a 100644 --- a/data/SNES_PSX_addresses.tsv +++ b/data/SNES_PSX_addresses.tsv @@ -10,13 +10,13 @@ character_battle_sprite_layouts 0x14B997 /btl/ff5_btl.bin 0x028997 11 of 6 of u8 character_battle_sprite_disabled_palette 0x00F867 /mnu/memsave.bin 0x000034 Palette16Of555 character_battle_sprite_stone_palette 0x00F807 N/A N/A Palette16Of555 Also 0x199835 tiles_fist 0x11D710 /btl/ff5_btl.bin 0x021D10 TileSNES3bpp Also /mnu/men_bin.eng:0x00D910 -tbl_weapons 0x110000 128 of 12 of u8 length 0x600 -tbl_armors 0x110600 96 of 12 of u8 length 0x480 -tbl_items 0x110A80 32 of 8 of u8 length 0x100 -tbl_magic 0x110B80 256 of 8 of u8 length 0x800 +tbl_weapons 0x110000 128 of WeaponData length 0x600 +tbl_armors 0x110600 96 of ArmorData length 0x480 +tbl_items 0x110A80 32 of ItemData length 0x100 +tbl_magic 0x110B80 256 of MagicData length 0x800 tbl_equip_types 0x112480 64 of 4 of u8 length 0x100 - Item Equipment type definitions (64x4 bytes, 2B weapon, 2B armor) -tbl_armors_elem_def 0x112580 64 of 5 of u8 length 0x140 - Item Armor Element defense 64x(5B - absorb, evade, immunity, half, weakness) -tbl_armors_status 0x1126C0 64 of 7 of u8 length 0x1C0 - Item Armor Status defense 64x(7B - 4B Initial, 3B Immune) +tbl_armors_elem_def 0x112580 64 of ElemDef length 0x140 - Item Armor Element defense 64x(5B - absorb, evade, immunity, half, weakness) +tbl_armors_status 0x1126C0 64 of StatusEffect length 0x1C0 - Item Armor Status defense 64x(7B - 4B Initial, 3B Immune) tbl_prices_items 0x112A00 256 of ItemCost length 0x200 tbl_prices_magic 0x112C00 128 of ItemCost length 0x100 tbl_charlevels_exp 0x115000 99 of u24 diff --git a/data/SNES_other.tsv b/data/SNES_other.tsv new file mode 100644 index 0000000..3bb4381 --- /dev/null +++ b/data/SNES_other.tsv @@ -0,0 +1,117 @@ +struct AttackType +u1 is_blue_magic +u1 is_white_magic +u1 is_black_magic +u1 is_dimension_magic +u1 is_summon +u1 is_sound +u1 is_longrange +u1 is_physical + +struct AttackProperties +u2 action_delay +u1 roulette +u1 target_enemy_by_default +u1 can_target_either_side +u1 can_select_target +u1 always_target_all +u1 can_target_all + +struct WeaponData +AttackProperties attack_properties +AttackType attack_type +u6 item_type +u1 is_unthrowable +u1 byte_2_leftover +EquipStatBonus bonus +u6 description +u1 can_double_grip +u1 only_double_grip +u1 is_wonder_rod +u1 is_action_on_use +u1 can_magic_sword +u1 is_action_on_hit +u1 is_5.4 +u1 is_initiative +u1 is_knife_parry +u1 is_sword_parry +u7 spell_cast +u1 is_break_after_use +u8 attack_power +u7 attack_effect +u1 byte_8_leftover +u8 param0 # accuracy_percent for sub-100 weapons, crit rate on katanas, element on Flametongue/Icebrand/Excalibur/AirKnife/Trident/WindSpear +u8 param1 # on_hit_effect_percent for weapons that cast actions on hit, also Rune weapons bonus attack power +u8 param2 # action_on_hit for weapons that cast actions on hit, Also Rune weapons 5 mana cost on attack, also element of attack for rods + +struct ArmorData +u8 slot # b0 - 0x01 acc, 0x02 body, 0x04 head, 0x08 shield, 0xF0 unused +u8 weight # b1 +u6 item_type +u1 is_unthrowable +u1 byte_2_leftover +EquipStatBonus bonus # b3 +u6 description # b4 +u2 byte_4_leftovers +u1 is_improve_catch # b5 +u1 is_become_undead +u1 is_improve_dance +u1 is_halve_mp_cost +u1 is_improve_steal +u1 is_improve_brawl +u1 is_elf_cape_dodge +u1 is_block_all_magic # b5 +u8 evasion # b6 +u8 defense +u8 m_evasion +u8 m_def # b9 +u8 elem_effect # b10 - 01 ½ lightning, 02 ½ all, 03 immune poison damage, 04 absorb fire+immune ice+weak water, 05 absorb water+immune fire+weak lightning, 06 Bone Mail (absorb poison, halve ice, weak fire/holy), 07 absorb fire, 08 absorb ice +u8 status_effect # b11 - 01 auto-regen, 02 auto-doom, 03 auto-haste + immune slow/stop/paralyse/sleep, 04 immune petrify, 05 Ribbon, 06 immune confuse, 07 immune silence, 08 immune blind, 09 immune poison, 0A bone mail, 0B auto-reflect, 0C immune mini, 0D immune old/zombie, 0E auto-sap + immune sleep, 0F auto-blink, 10 immune mini/paralyse, 11 immune confuse/mini, 12 immune confuse/toad, 13 immune toad/paralyse + + +struct ItemData +AttackProperties attack_properties +u8 attack_type +u1 unk0 +u1 is_unmixable +u1 unk1 +u1 is_magic_lamp +u1 is_undrinkable +u1 is_unusable_in_battle +u1 is_unthrowable +u1 unk2 +u6 description # b3 +u2 byte_3_leftovers +u7 attack_formula +u1 is_unavoidable +u8 param_0 +u8 param_1 +u8 param_2 + + +struct MagicData +AttackProperties attack_properties +u8 attack_type +u4 meteo_extra_hits +u2 unk +u1 is_learnable +u1 is_monster_bit +u7 mp_cost +u1 is_unreflectable +u7 attack_formula +u1 is_unavoidable +u8 param_0 +u8 param_1 +u8 param_2 + + +struct ElemDef +u8 absorb +u8 evade +u8 immune +u8 half +u8 weak + +struct StatusEffect +4 of u8 initial +3 of u8 immune diff --git a/scripts/loaders/RomLoader.gd b/scripts/loaders/RomLoader.gd index 6471766..c823f02 100644 --- a/scripts/loaders/RomLoader.gd +++ b/scripts/loaders/RomLoader.gd @@ -31,7 +31,7 @@ func load_snes_rom(filename: String): MapLoader.load_snes_rom(rom_snes) StringLoader.load_snes_rom(rom_snes, true) # Don't do this concurrently, avoid file pointer conflicts - var _thread_error = thread.start(SoundLoader, 'parse_rom', rom_snes) + #var _thread_error = thread.start(SoundLoader, 'parse_rom', rom_snes) # Can concurrently work with the preloaded StreamPeerBuffer though for key in Common.SNES_PSX_addresses: var d = Common.SNES_PSX_addresses[key] @@ -69,8 +69,29 @@ func _ready(): structdefs.merge(STRUCT.get_base_structarraytypes()) structdefs.merge(STRUCT_SNES.get_structtypes()) STRUCT.parse_struct_definitions_from_tsv_filename('res://data/SNES_save.tsv', structdefs) + STRUCT.parse_struct_definitions_from_tsv_filename('res://data/SNES_other.tsv', structdefs) var _error := psx_productcode_regex.compile('(S[A-Z]{3}_\\d{3}\\.\\d{2});(\\d)') load_snes_rom(ROM_filename) + # Debugging breakpoint + for i in 128: + var weapon = snes_data.tbl_weapons[i] + if weapon.byte_2_leftover: + print('Weapon %d ($%02X) has byte_2_leftover set' % [i, i]) + if weapon.byte_8_leftover: + print('Weapon %d ($%02X) has byte_8_leftover set' % [i, i]) + if weapon['is_5.4']: + print('Weapon %d ($%02X) has 5.4 set' % [i, i]) + for i in 96: + var armor = snes_data.tbl_armors[i] + if armor.byte_0_leftover: + print('Armor %d ($%02X) "%s" has byte_0_leftover set' % [i, i, StringLoader.tables.items[i+0x80]]) + if armor.byte_4_leftovers: + print('Armor %d ($%02X) "%s" has byte_4_leftovers set' % [i, i, StringLoader.tables.items[i+0x80]]) + if armor.byte_2_leftovers: + print('Armor %d ($%02X) "%s" has byte_2_leftovers set to %d ($%02X)' % [i, i, StringLoader.tables.items[i+0x80], armor.byte_2_leftovers, armor.byte_2_leftovers]) + if armor.gear_special: + print('Armor %d ($%02X) "%s" has gear_special set to %d ($%02X)' % [i, i, StringLoader.tables.items[i+0x80], armor.gear_special, armor.gear_special]) + pass func _exit_tree() -> void: thread.wait_to_finish() diff --git a/scripts/loaders/snes/structs.gd b/scripts/loaders/snes/structs.gd index fe1d46c..fa8c2fc 100644 --- a/scripts/loaders/snes/structs.gd +++ b/scripts/loaders/snes/structs.gd @@ -53,6 +53,47 @@ class ItemCost extends STRUCT.StructType: var sell_price := -1 if not is_sellable else (5 if is_sellprice_5gil else buy_price/2) return {'buy_price': buy_price, 'sell_price': sell_price} +# Unions aren't covered by my DSL yet +class EquipStatBonus extends STRUCT.StructType: + static func get_stat_bonuses(affected_stats: int, bonus: int, base: int = 0) -> Dictionary: + var stats := {Common.STAT_STR: base, Common.STAT_SPD: base, Common.STAT_VIT: base, Common.STAT_MAG: base} + if affected_stats & 0x1: + stats[Common.STAT_MAG] += bonus + if affected_stats & 0x2: + stats[Common.STAT_VIT] += bonus + if affected_stats & 0x4: + stats[Common.STAT_SPD] += bonus + if affected_stats & 0x8: + stats[Common.STAT_STR] += bonus + return stats + + func get_value(buffer: StreamPeer, leftover_bits: Array): + var b0 := buffer.get_u8() + var is_stat_bonus = b0 >> 7 + if is_stat_bonus: + var affected_stats := (b0 >> 3) & 0x0F # "0" maryoku, "1": tairyoku, "2": subayasa, "3": chikara + var stats := [{}, {}] + var bonus := b0 & 0x07 + match bonus: + 0: + return get_stat_bonuses(affected_stats, 1) + 1: + return get_stat_bonuses(affected_stats, 2) + 2: + return get_stat_bonuses(affected_stats, 3) + 3: # unused? "+1/-1/+1/-1" + return {Common.STAT_STR: 1, Common.STAT_SPD: -1, Common.STAT_VIT: 1, Common.STAT_MAG: -1} + 4: # unused? "-1 (to unselected stats)" + return get_stat_bonuses(affected_stats, 1, -1) + 5: # Giant's Glove: +5 -5 +5 -5 + return {Common.STAT_STR: 5, Common.STAT_SPD: -5, Common.STAT_VIT: 5, Common.STAT_MAG: -5} + 6: # Bone Mail: -5 to unselected stats + return get_stat_bonuses(affected_stats, 5, -5) + 7: + return get_stat_bonuses(affected_stats, 5) + else: # is Element bitfield + return {'elemental_bonus': b0 & 0x7F} + static func get_structtypes() -> Dictionary: return { 'TileSNESMode7': TileSNESMode7.new(), @@ -65,4 +106,5 @@ static func get_structtypes() -> Dictionary: 'Palette16Of555': PaletteOf555.new(16), 'Palette128Of555': PaletteOf555.new(128), 'ItemCost': ItemCost.new(), + 'EquipStatBonus': EquipStatBonus.new(), } diff --git a/scripts/managers/Common.gd b/scripts/managers/Common.gd index f4ddc00..53261ca 100644 --- a/scripts/managers/Common.gd +++ b/scripts/managers/Common.gd @@ -6,6 +6,20 @@ var last_resolution_pre_shrink := Vector2.ZERO var SNES_PSX_addresses := load_tsv('res://data/SNES_PSX_addresses.tsv') +# Canonicalize english stat names within the codebase +# This is based off of FF8, and is more or less RPGe but with Speed instead of Agility +# I don't like Vigor or Stamina, Agility could go either way but I side with FF8 and FF9 rather than FF10 +enum { + STAT_STRENGTH = 0, + STAT_STR = 0, + STAT_SPEED = 1, + STAT_SPD = 1, + STAT_VITALITY = 2, + STAT_VIT = 2, + STAT_MAGIC = 3, + STAT_MAG = 3, +} + static func eval(expression: String): var ex = Expression.new() match ex.parse(expression):