From 681ce135f8c4c4c5cbd97c1e100c74d63d28d823 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 3 May 2026 20:58:34 -0700 Subject: [PATCH] add JSON encoding for ItemPMT --- README.md | 5 +- src/ItemData.cc | 2 +- src/ItemNameIndex.cc | 206 ++++- src/ItemParameterTable.cc | 1821 ++++++++++++++++++++++++++++++++----- src/ItemParameterTable.hh | 269 ++++-- src/Main.cc | 12 + src/ServerState.cc | 2 +- src/StaticGameData.cc | 6 +- 8 files changed, 2024 insertions(+), 299 deletions(-) diff --git a/README.md b/README.md index c11917dd..dcd7e2d7 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,16 @@ If you want to use parts of newserv in your project, there are two easy ways to * If you're only using a few files from newserv, you can copy and paste the contents of the LICENSE file into a comment at the beginning of each copied file. Some of the more likely useful files are: +* **src/BattleParamsIndex.hh**: Format of BattleParamEntry files * **src/CommandFormats.hh**: Complete listing of all network commands used in all known versions of the game, and their formats * **src/CommonItemSet.hh/cc**: Format of ItemPT files, shop definition files, and tekker adjustment tables +* **src/Compression.hh/cc**: PRS and BC0 compression and decompression algorithms * **src/DCSerialNumbers.hh/cc**: PSO DC serial number validation algorithm and serial number generator * **src/ItemData.hh**: Item format reference * **src/ItemCreator.hh/cc**: Reverse-engineered item generator from Episodes 1&2 (used for all versions) -* **src/ItemParameterTable.hh**: Format of many structures in ItemPMT.prs +* **src/ItemParameterTable.cc**: Format of many structures in ItemPMT.prs (see BinaryItemParameterTableT) * **src/Map.hh/cc**: Map file (.dat/.evt) structure, listing of object/enemy types and parameters, and reverse-engineered Challenge Mode random enemy generation algorithm +* **src/MagEvolutionTable.cc**: Format of ItemMagEdit.prs * **src/QuestScript.cc**: Complete listing of all quest opcodes on all versions, along with their arguments and behavior * **src/RareItemSet.hh/cc**: Format of ItemRT files (rare item drop tables) * **src/SaveFileFormats.hh**: Definitions of save file structures for all versions diff --git a/src/ItemData.cc b/src/ItemData.cc index c4ac5b99..d7c8b371 100644 --- a/src/ItemData.cc +++ b/src/ItemData.cc @@ -486,7 +486,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptrdata1[1] > 0x26)) { if (this->data1[1] < 0x89) { this->data1[5] = this->data1[1]; - this->data1[1] = item_parameter_table->get_weapon_class(this->data1[1]); + this->data1[1] = item_parameter_table->get_weapon_kind(this->data1[1]); if (this->data1[1] == 0x00) { this->data1[1] = 0x0F; } diff --git a/src/ItemNameIndex.cc b/src/ItemNameIndex.cc index cf555c03..ad3d66bf 100644 --- a/src/ItemNameIndex.cc +++ b/src/ItemNameIndex.cc @@ -1,5 +1,7 @@ #include "ItemNameIndex.hh" +#include + #include "StaticGameData.hh" using namespace std; @@ -787,9 +789,9 @@ void ItemNameIndex::print_table(FILE* stream) const { auto pmt = this->item_parameter_table; phosg::fwrite_fmt(stream, "WEAPONS\n"); - phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB(S1:AMT1,S2:AMT2) PJ 1X 1Y 2X 2Y CR --A1-- A4 A5 TB BF CL ST* USL ---DIVISOR--- NAME\n"); + phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB(S1:AMT1,S2:AMT2) PJ 1X 1Y 2X 2Y CR --A1-- A4 A5 TB(TN:FL:AMOUNT, ... ) BF CL ST* USL ---DIVISOR--- NAME\n"); for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes(); data1_1++) { - uint8_t weapon_class = pmt->get_weapon_class(data1_1); + uint8_t weapon_class = pmt->get_weapon_kind(data1_1); float sale_divisor = pmt->get_sale_divisor(0x00, data1_1); string divisor_str = std::format("{:g}", sale_divisor); divisor_str.resize(13, ' '); @@ -806,8 +808,24 @@ void ItemNameIndex::print_table(FILE* stream) const { item.data1[2] = data1_2; string name = this->describe_item(item); - auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index); - phosg::fwrite_fmt(stream, " 00{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:5} {:5} {:5} {:5} {:5} {:3} {:02X} {:02X} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}{:02X}{:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:2}* {} {} {}\n", + const auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index); + + string tech_boost_str; + if (w.tech_boost_entry_index < pmt->num_tech_boosts()) { + const auto& tech_boost = pmt->get_tech_boost(w.tech_boost_entry_index); + tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})", + tech_boost.tech_num1, tech_boost.flags1, tech_boost.amount1, tech_boost.tech_num2, tech_boost.flags2, + tech_boost.amount2, tech_boost.tech_num3, tech_boost.flags3, tech_boost.amount3); + } + tech_boost_str.resize(40, ' '); + + phosg::fwrite_fmt(stream, + // CODE => ID TYPE SKIN PTS FLAG ATP- ATP+ ATPR MSTR ATAR MST GND PH SP ATA + " 00{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:5} {:5} {:5} {:5} {:5} {:3} {:02X} {:02X} {:3} " + // SB( S1:AMT1 , S2:AMT2 ) PJ 1X 1Y 2X 2Y CR --------A1-------- A4 + "{:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}{:02X}{:02X} {:02X} " + // A5 TB( TN: FL: AMT, TN: FL: AMT, TN: FL: AMT) BF CL ST* US DV NAME\n" + "{:02X} {:02X}{} {:02X} {:02X} {:2}* {} {} {}\n", data1_1, data1_2, w.id, @@ -841,7 +859,8 @@ void ItemNameIndex::print_table(FILE* stream) const { w.unknown_a1[2], w.unknown_a4, w.unknown_a5, - w.tech_boost, + w.tech_boost_entry_index, + tech_boost_str, w.behavior_flags, weapon_class, stars, @@ -852,7 +871,7 @@ void ItemNameIndex::print_table(FILE* stream) const { } phosg::fwrite_fmt(stream, "ARMORS\n"); - phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB(S1:AMT1,S2:AMT2) TB FT A4 ST* ---DIVISOR--- NAME\n"); + phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB(S1:AMT1,S2:AMT2) TB(TN:FL:AMOUNT, ... ) FT A4 ST* ---DIVISOR--- NAME\n"); for (size_t data1_1 = 1; data1_1 < 3; data1_1++) { float sale_divisor = pmt->get_sale_divisor(0x01, data1_1); string divisor_str = std::format("{:g}", sale_divisor); @@ -870,7 +889,17 @@ void ItemNameIndex::print_table(FILE* stream) const { string name = this->describe_item(item); auto& stat_boost = pmt->get_stat_boost(a.stat_boost_entry_index); - phosg::fwrite_fmt(stream, " 01{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:5} {:02X} {:02X} {:04X} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X} {:02X} {:02X} {:2}* {} {}\n", + + string tech_boost_str; + if (a.tech_boost_entry_index < pmt->num_tech_boosts()) { + const auto& tech_boost = pmt->get_tech_boost(a.tech_boost_entry_index); + tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})", + tech_boost.tech_num1, tech_boost.flags1, tech_boost.amount1, tech_boost.tech_num2, tech_boost.flags2, + tech_boost.amount2, tech_boost.tech_num3, tech_boost.flags3, tech_boost.amount3); + } + tech_boost_str.resize(40, ' '); + + phosg::fwrite_fmt(stream, " 01{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:5} {:02X} {:02X} {:04X} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:02X}({:02X}:{:04X},{:02X}:{:04X}) {:02X}{} {:02X} {:02X} {:2}* {} {}\n", data1_1, data1_2, a.id, @@ -895,7 +924,8 @@ void ItemNameIndex::print_table(FILE* stream) const { stat_boost.amount1, stat_boost.stat2, stat_boost.amount2, - a.tech_boost, + a.tech_boost_entry_index, + tech_boost_str, a.flags_type, a.unknown_a4, stars, @@ -1040,7 +1070,7 @@ void ItemNameIndex::print_table(FILE* stream) const { } phosg::fwrite_fmt(stream, "SPECIAL DEFINITIONS\n"); - phosg::fwrite_fmt(stream, " SPECIAL => TYPE COUNT ST* NAME\n"); + phosg::fwrite_fmt(stream, " SP => TYPE COUNT ST* NAME\n"); for (size_t index = 0; index < pmt->num_specials(); index++) { const auto& sp = pmt->get_special(index); uint8_t stars = pmt->get_special_stars(index); @@ -1051,12 +1081,12 @@ void ItemNameIndex::print_table(FILE* stream) const { } catch (const out_of_range&) { } } - phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}* {}\n", index, sp.type, sp.amount, stars, name); + phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}* {}\n", index, sp.type, sp.amount, stars, name); } phosg::fwrite_fmt(stream, "ITEM COMBINATIONS\n"); phosg::fwrite_fmt(stream, " ---USE + -EQUIP => RESULT MLV GND LVL CLS\n"); - for (const auto& combo_list_it : pmt->get_all_item_combinations()) { + for (const auto& combo_list_it : pmt->all_item_combinations()) { for (const auto& combo : combo_list_it.second) { phosg::fwrite_fmt(stream, " {:02X}{:02X}{:02X} + {:02X}{:02X}{:02X} => {:02X}{:02X}{:02X}", combo.used_item[0], combo.used_item[1], combo.used_item[2], @@ -1096,4 +1126,158 @@ void ItemNameIndex::print_table(FILE* stream) const { event_item.item[0], event_item.item[1], event_item.item[2], event_item.probability); } } + + phosg::fwrite_fmt(stream, "PHOTON COLORS\n"); + phosg::fwrite_fmt(stream, " ## => ---A1--- (A2) (A3)\n"); + for (size_t z = 0; z < pmt->num_photon_colors(); z++) { + const auto& pc = pmt->get_photon_color(z); + phosg::fwrite_fmt(stream, " {:02X} => {:08X} ({:g}, {:g}, {:g}, {:g}) ({:g}, {:g}, {:g}, {:g})\n", + z, pc.unknown_a1, pc.unknown_a2.x, pc.unknown_a2.y, pc.unknown_a2.z, pc.unknown_a2.t, + pc.unknown_a3.x, pc.unknown_a3.y, pc.unknown_a3.z, pc.unknown_a3.t); + } + + phosg::fwrite_fmt(stream, "WEAPON RANGES\n"); + phosg::fwrite_fmt(stream, " ## => ---A3--- ---A4--- ---A5--- (A1) (A2)\n"); + for (size_t z = 0; z < pmt->num_weapon_ranges(); z++) { + const auto& wr = pmt->get_weapon_range(z); + phosg::fwrite_fmt(stream, " {:02X} => {:08X} {:08X} {:08X} ({:g}) ({:g})\n", + z, wr.unknown_a3, wr.unknown_a4, wr.unknown_a5, wr.unknown_a1, wr.unknown_a2); + } + + phosg::fwrite_fmt(stream, "SALE DIVISORS\n"); + phosg::fwrite_fmt(stream, " ARMOR = {:g}\n", pmt->get_sale_divisor(1, 1)); + phosg::fwrite_fmt(stream, " SHIELD = {:g}\n", pmt->get_sale_divisor(1, 2)); + phosg::fwrite_fmt(stream, " UNIT = {:g}\n", pmt->get_sale_divisor(1, 3)); + phosg::fwrite_fmt(stream, " MAG = {:g}\n", pmt->get_sale_divisor(2, 0)); + + auto write_data_string = [&](const std::string& data, size_t addr = 0) -> void { + if (data.empty()) { + phosg::fwrite_fmt(stream, " (no data)\n"); + } else { + auto data_str = phosg::format_data(data, addr); + phosg::strip_trailing_whitespace(data_str); + phosg::fwrite_fmt(stream, " {}\n", phosg::str_replace_all(data_str, "\n", "\n ")); + } + }; + + phosg::fwrite_fmt(stream, "STAR VALUES\n"); + write_data_string(pmt->get_star_value_table(), pmt->get_star_value_index_range().first); + + phosg::fwrite_fmt(stream, "UNKNOWN_A1\n"); + write_data_string(pmt->get_unknown_a1()); + + phosg::fwrite_fmt(stream, "WEAPON EFFECTS\n"); + phosg::fwrite_fmt(stream, " ## => -SOUND1- -VALUE1- -SOUND2- -VALUE2- ----------------A5---------------\n"); + for (size_t z = 0; z < pmt->num_weapon_effects(); z++) { + const auto& we = pmt->get_weapon_effect(z); + auto a5_str = phosg::format_data_string(we.unknown_a5.data(), we.unknown_a5.size()); + phosg::fwrite_fmt(stream, " {:02X} => {:08X} {:08X} {:08X} {:08X} {}\n", + z, we.sound_id1, we.eff_value1, we.sound_id2, we.eff_value2, a5_str); + } + + phosg::fwrite_fmt(stream, "WEAPON STAT BOOST INDEX TABLE\n"); + write_data_string(pmt->get_weapon_stat_boost_index_table()); + + phosg::fwrite_fmt(stream, "ARMOR STAT BOOST INDEX TABLE\n"); + write_data_string(pmt->get_armor_stat_boost_index_table()); + + phosg::fwrite_fmt(stream, "SHIELD STAT BOOST INDEX TABLE\n"); + write_data_string(pmt->get_shield_stat_boost_index_table()); + + phosg::fwrite_fmt(stream, "STAT BOOSTS\n"); + phosg::fwrite_fmt(stream, " ## => BOOSTS\n"); + for (size_t z = 0; z < pmt->num_stat_boosts(); z++) { + const auto& sb = pmt->get_stat_boost(z); + static constexpr std::array stat_names{ + "ATP+", "ATA+", "EVP+", "DFP+", "MST+", "HP+", "LCK+", "ALL+", + "ATP-", "ATA-", "EVP-", "DFP-", "MST-", "HP-", "LCK-", "ALL-"}; + string s; + if (sb.stat1 > 0x10) { + s = std::format("[{:02X}:{:04X}]", sb.stat1, sb.amount1); + } else if (sb.stat1 > 0) { + s = std::format("{}{}", stat_names[sb.stat1 - 1], sb.amount1); + } + if (sb.stat2) { + if (!s.empty()) { + s += ", "; + } + if (sb.stat2 > 0x10) { + s += std::format("[{:02X}:{:04X}]", sb.stat2, sb.amount2); + } else if (sb.stat2 > 0) { + s += std::format("{}{}", stat_names[sb.stat2 - 1], sb.amount2); + } + } + if (s.empty()) { + s = "(none)"; + } + phosg::fwrite_fmt(stream, " {:02X} => {}\n", z, s); + } + + phosg::fwrite_fmt(stream, "SHIELD EFFECTS\n"); + phosg::fwrite_fmt(stream, " ## => -SOUND1- ---A1---\n"); + for (size_t z = 0; z < pmt->num_shield_effects(); z++) { + const auto& se = pmt->get_shield_effect(z); + phosg::fwrite_fmt(stream, " {:02X} => {:08X} {:08X}\n", z, se.sound_id, se.unknown_a1); + } + + phosg::fwrite_fmt(stream, "SOUND REMAPS\n"); + phosg::fwrite_fmt(stream, " -SOUND1- => RT:[...] CC:[...]\n"); + for (const auto& [sound_id, remaps] : pmt->get_all_sound_remaps()) { + std::string rt_str; + for (uint32_t rt_sound_id : remaps.by_rt_index) { + if (!rt_str.empty()) { + rt_str += ","; + } + rt_str += std::format("{:08X}", rt_sound_id); + } + std::string cc_str; + for (uint32_t cc_sound_id : remaps.by_char_class) { + if (!cc_str.empty()) { + cc_str += ","; + } + cc_str += std::format("{:08X}", cc_sound_id); + } + phosg::fwrite_fmt(stream, " {:08X} => RT:[{}] CC:[{}]\n", sound_id, rt_str, cc_str); + } + + phosg::fwrite_fmt(stream, "TECH BOOSTS\n"); + phosg::fwrite_fmt(stream, " ## => BOOSTS\n"); + for (size_t z = 0; z < pmt->num_tech_boosts(); z++) { + const auto& tb = pmt->get_tech_boost(z); + string s; + if (tb.amount1) { + s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num1, tb.flags1, tb.amount1); + } + if (tb.amount2) { + s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num2, tb.flags2, tb.amount2); + } + if (tb.amount3) { + s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num3, tb.flags3, tb.amount3); + } + phosg::fwrite_fmt(stream, " {:02X} => {}\n", z, s); + } + + phosg::fwrite_fmt(stream, "UNSEALABLE ITEMS\n"); + phosg::fwrite_fmt(stream, " -ITEM- NAME\n"); + std::vector unsealable_items; + for (uint32_t item_code : pmt->all_unsealable_items()) { + unsealable_items.emplace_back(item_code); + } + std::sort(unsealable_items.begin(), unsealable_items.end()); + for (uint32_t item_code : unsealable_items) { + ItemData item; + item.data1[0] = item_code >> 16; + item.data1[1] = item_code >> 8; + item.data1[2] = item_code; + string name = this->describe_item(item); + phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, name); + } + + phosg::fwrite_fmt(stream, "RANGED SPECIALS\n"); + phosg::fwrite_fmt(stream, " ## => 11 12 WR A1\n"); + for (size_t z = 0; z < pmt->num_ranged_specials(); z++) { + const auto& rs = pmt->get_ranged_special(z); + phosg::fwrite_fmt(stream, " {:02X} => {:02X} {:02X} {:02X} {:02X}\n", + z, rs.data1_1, rs.data1_2, rs.weapon_range_index, rs.unknown_a1); + } } diff --git a/src/ItemParameterTable.cc b/src/ItemParameterTable.cc index 05356337..6bcad298 100644 --- a/src/ItemParameterTable.cc +++ b/src/ItemParameterTable.cc @@ -4,6 +4,9 @@ using namespace std; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Utilities + template <> ServerDropMode phosg::enum_for_name(const char* name) { if (!strcmp(name, "DISABLED")) { @@ -39,6 +42,1057 @@ const char* phosg::name_for_enum(ServerDropMode value) { } } +static void u32_to_item_code(parray& decoded, uint32_t item) { + decoded[0] = item >> 16; + decoded[1] = item >> 8; + decoded[2] = item; +} + +static uint32_t item_code_to_u32(const parray& item) { + return (item[0] << 16) | (item[1] << 8) | item[2]; +} +static uint32_t item_code_to_u32(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) { + return (data1_0 << 16) | (data1_1 << 8) | data1_2; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Parsed structures + +void ItemParameterTable::ItemBase::parse_base_from_json(const phosg::JSON& json) { + this->id = json.get_int("ID"); + this->type = json.get_int("Type"); + this->skin = json.get_int("Skin"); + this->team_points = json.get_int("TeamPoints"); +} +phosg::JSON ItemParameterTable::ItemBase::json() const { + return phosg::JSON::dict( + {{"ID", this->id}, {"Type", this->type}, {"Skin", this->skin}, {"TeamPoints", this->team_points}}); +} + +ItemParameterTable::Weapon ItemParameterTable::Weapon::from_json(const phosg::JSON& json) { + ItemParameterTable::Weapon ret; + ret.parse_base_from_json(json); + ret.class_flags = json.get_int("ClassFlags"); + ret.atp_min = json.get_int("ATPMin"); + ret.atp_max = json.get_int("ATPMax"); + ret.atp_required = json.get_int("ATPRequired"); + ret.mst_required = json.get_int("MSTRequired"); + ret.ata_required = json.get_int("ATARequired"); + ret.mst = json.get_int("MST"); + ret.max_grind = json.get_int("MaxGrind"); + ret.photon = json.get_int("Photon"); + ret.special = json.get_int("Special"); + ret.ata = json.get_int("ATA"); + ret.stat_boost_entry_index = json.get_int("StatBoostEntryIndex"); + ret.projectile = json.get_int("Projectile"); + ret.trail1_x = json.get_int("Trail1X"); + ret.trail1_y = json.get_int("Trail1Y"); + ret.trail2_x = json.get_int("Trail2X"); + ret.trail2_y = json.get_int("Trail2Y"); + ret.color = json.get_int("Color"); + const auto& unknown_a1 = json.get_list("UnknownA1"); + ret.unknown_a1[0] = unknown_a1[0]->as_int(); + ret.unknown_a1[1] = unknown_a1[1]->as_int(); + ret.unknown_a1[2] = unknown_a1[2]->as_int(); + ret.unknown_a4 = json.get_int("UnknownA4"); + ret.unknown_a5 = json.get_int("UnknownA5"); + ret.tech_boost_entry_index = json.get_int("TechBoostEntryIndex"); + ret.behavior_flags = json.get_int("BehaviorFlags"); + return ret; +} +phosg::JSON ItemParameterTable::Weapon::json() const { + phosg::JSON ret = this->ItemBase::json(); + ret.emplace("ClassFlags", this->class_flags); + ret.emplace("ATPMin", this->atp_min); + ret.emplace("ATPMax", this->atp_max); + ret.emplace("ATPRequired", this->atp_required); + ret.emplace("MSTRequired", this->mst_required); + ret.emplace("ATARequired", this->ata_required); + ret.emplace("MST", this->mst); + ret.emplace("MaxGrind", this->max_grind); + ret.emplace("Photon", this->photon); + ret.emplace("Special", this->special); + ret.emplace("ATA", this->ata); + ret.emplace("StatBoostEntryIndex", this->stat_boost_entry_index); + ret.emplace("Projectile", this->projectile); + ret.emplace("Trail1X", this->trail1_x); + ret.emplace("Trail1Y", this->trail1_y); + ret.emplace("Trail2X", this->trail2_x); + ret.emplace("Trail2Y", this->trail2_y); + ret.emplace("Color", this->color); + ret.emplace("UnknownA1", phosg::JSON::list({this->unknown_a1[0], this->unknown_a1[1], this->unknown_a1[2]})); + ret.emplace("UnknownA4", this->unknown_a4); + ret.emplace("UnknownA5", this->unknown_a5); + ret.emplace("TechBoostEntryIndex", this->tech_boost_entry_index); + ret.emplace("BehaviorFlags", this->behavior_flags); + return ret; +} + +ItemParameterTable::ArmorOrShield ItemParameterTable::ArmorOrShield::from_json(const phosg::JSON& json) { + ItemParameterTable::ArmorOrShield ret; + ret.parse_base_from_json(json); + ret.dfp = json.get_int("DFP"); + ret.evp = json.get_int("EVP"); + ret.block_particle = json.get_int("BlockParticle"); + ret.block_effect = json.get_int("BlockEffect"); + ret.class_flags = json.get_int("ClassFlags"); + ret.required_level = json.get_int("RequiredLevel"); + ret.efr = json.get_int("EFR"); + ret.eth = json.get_int("ETH"); + ret.eic = json.get_int("EIC"); + ret.edk = json.get_int("EDK"); + ret.elt = json.get_int("ELT"); + ret.dfp_range = json.get_int("DFPRange"); + ret.evp_range = json.get_int("EVPRange"); + ret.stat_boost_entry_index = json.get_int("StatBoostEntryIndex"); + ret.tech_boost_entry_index = json.get_int("TechBoostEntryIndex"); + ret.flags_type = json.get_int("FlagsType"); + ret.unknown_a4 = json.get_int("UnknownA4"); + return ret; +} +phosg::JSON ItemParameterTable::ArmorOrShield::json() const { + phosg::JSON ret = this->ItemBase::json(); + ret.emplace("DFP", this->dfp); + ret.emplace("EVP", this->evp); + ret.emplace("BlockParticle", this->block_particle); + ret.emplace("BlockEffect", this->block_effect); + ret.emplace("ClassFlags", this->class_flags); + ret.emplace("RequiredLevel", this->required_level); + ret.emplace("EFR", this->efr); + ret.emplace("ETH", this->eth); + ret.emplace("EIC", this->eic); + ret.emplace("EDK", this->edk); + ret.emplace("ELT", this->elt); + ret.emplace("DFPRange", this->dfp_range); + ret.emplace("EVPRange", this->evp_range); + ret.emplace("StatBoostEntryIndex", this->stat_boost_entry_index); + ret.emplace("TechBoostEntryIndex", this->tech_boost_entry_index); + ret.emplace("FlagsType", this->flags_type); + ret.emplace("UnknownA4", this->unknown_a4); + return ret; +} + +ItemParameterTable::Unit ItemParameterTable::Unit::from_json(const phosg::JSON& json) { + ItemParameterTable::Unit ret; + ret.parse_base_from_json(json); + ret.stat = json.get_int("Stat"); + ret.stat_amount = json.get_int("StatAmount"); + ret.modifier_amount = json.get_int("ModifierAmount"); + return ret; +} +phosg::JSON ItemParameterTable::Unit::json() const { + phosg::JSON ret = this->ItemBase::json(); + ret.emplace("Stat", this->stat); + ret.emplace("StatAmount", this->stat_amount); + ret.emplace("ModifierAmount", this->modifier_amount); + return ret; +} + +ItemParameterTable::Mag ItemParameterTable::Mag::from_json(const phosg::JSON& json) { + ItemParameterTable::Mag ret; + ret.parse_base_from_json(json); + ret.feed_table = json.get_int("FeedTable"); + ret.photon_blast = json.get_int("PhotonBlast"); + ret.activation = json.get_int("Activation"); + ret.on_pb_full = json.get_int("OnPBFull"); + ret.on_low_hp = json.get_int("OnLowHP"); + ret.on_death = json.get_int("OnDeath"); + ret.on_boss = json.get_int("OnBoss"); + ret.on_pb_full_flag = json.get_int("OnPBFullFlag"); + ret.on_low_hp_flag = json.get_int("OnLowHPFlag"); + ret.on_death_flag = json.get_int("OnDeathFlag"); + ret.on_boss_flag = json.get_int("OnBossFlag"); + ret.class_flags = json.get_int("ClassFlags"); + return ret; +} +phosg::JSON ItemParameterTable::Mag::json() const { + phosg::JSON ret = this->ItemBase::json(); + ret.emplace("FeedTable", this->feed_table); + ret.emplace("PhotonBlast", this->photon_blast); + ret.emplace("Activation", this->activation); + ret.emplace("OnPBFull", this->on_pb_full); + ret.emplace("OnLowHP", this->on_low_hp); + ret.emplace("OnDeath", this->on_death); + ret.emplace("OnBoss", this->on_boss); + ret.emplace("OnPBFullFlag", this->on_pb_full_flag); + ret.emplace("OnLowHPFlag", this->on_low_hp_flag); + ret.emplace("OnDeathFlag", this->on_death_flag); + ret.emplace("OnBossFlag", this->on_boss_flag); + ret.emplace("ClassFlags", this->class_flags); + return ret; +} + +ItemParameterTable::Tool ItemParameterTable::Tool::from_json(const phosg::JSON& json) { + ItemParameterTable::Tool ret; + ret.parse_base_from_json(json); + ret.amount = json.get_int("Amount"); + ret.tech = json.get_int("Tech"); + ret.cost = json.get_int("Cost"); + ret.item_flags = json.get_int("ItemFlags"); + return ret; +} +phosg::JSON ItemParameterTable::Tool::json() const { + phosg::JSON ret = this->ItemBase::json(); + ret.emplace("Amount", this->amount); + ret.emplace("Tech", this->tech); + ret.emplace("Cost", this->cost); + ret.emplace("ItemFlags", this->item_flags); + return ret; +} + +ItemParameterTable::MagFeedResult ItemParameterTable::MagFeedResult::from_json(const phosg::JSON& json) { + ItemParameterTable::MagFeedResult ret; + ret.def = json.get_int("DEF"); + ret.pow = json.get_int("POW"); + ret.dex = json.get_int("DEX"); + ret.mind = json.get_int("MIND"); + ret.iq = json.get_int("IQ"); + ret.synchro = json.get_int("Synchro"); + return ret; +} +phosg::JSON ItemParameterTable::MagFeedResult::json() const { + return phosg::JSON::dict({ + {"DEF", this->def}, + {"POW", this->pow}, + {"DEX", this->dex}, + {"MIND", this->mind}, + {"IQ", this->iq}, + {"Synchro", this->synchro}, + }); +} + +ItemParameterTable::Special ItemParameterTable::Special::from_json(const phosg::JSON& json) { + ItemParameterTable::Special ret; + ret.type = json.get_int("Type"); + ret.amount = json.get_int("Amount"); + return ret; +} +phosg::JSON ItemParameterTable::Special::json() const { + return phosg::JSON::dict({{"Type", this->type}, {"Amount", this->amount}}); +} + +ItemParameterTable::StatBoost ItemParameterTable::StatBoost::from_json(const phosg::JSON& json) { + ItemParameterTable::StatBoost ret; + ret.stat1 = json.get_int("Stat1"); + ret.amount1 = json.get_int("Amount1"); + ret.stat2 = json.get_int("Stat2"); + ret.amount2 = json.get_int("Amount2"); + return ret; +} +phosg::JSON ItemParameterTable::StatBoost::json() const { + return phosg::JSON::dict({ + {"Stat1", this->stat1}, + {"Amount1", this->amount1}, + {"Stat2", this->stat2}, + {"Amount2", this->amount2}, + }); +} + +ItemParameterTable::ItemCombination ItemParameterTable::ItemCombination::from_json(const phosg::JSON& json) { + ItemParameterTable::ItemCombination ret; + u32_to_item_code(ret.used_item, json.get_int("UsedItem")); + u32_to_item_code(ret.equipped_item, json.get_int("EquippedItem")); + u32_to_item_code(ret.result_item, json.get_int("ResultItem")); + ret.mag_level = json.get_int("MagLevel"); + ret.grind = json.get_int("Grind"); + ret.level = json.get_int("Level"); + ret.char_class = json.get_int("CharClass"); + return ret; +} +phosg::JSON ItemParameterTable::ItemCombination::json() const { + return phosg::JSON::dict({ + {"UsedItem", item_code_to_u32(this->used_item)}, + {"EquippedItem", item_code_to_u32(this->equipped_item)}, + {"ResultItem", item_code_to_u32(this->result_item)}, + {"MagLevel", this->mag_level}, + {"Grind", this->grind}, + {"Level", this->level}, + {"CharClass", this->char_class}, + }); +} + +ItemParameterTable::TechBoost ItemParameterTable::TechBoost::from_json(const phosg::JSON& json) { + ItemParameterTable::TechBoost ret; + ret.tech_num1 = json.get_int("TechNum1"); + ret.flags1 = json.get_int("Flags1"); + ret.amount1 = json.get_int("Amount1"); + ret.tech_num2 = json.get_int("TechNum2"); + ret.flags2 = json.get_int("Flags2"); + ret.amount2 = json.get_int("Amount2"); + ret.tech_num3 = json.get_int("TechNum3"); + ret.flags3 = json.get_int("Flags3"); + ret.amount3 = json.get_int("Amount3"); + return ret; +} +phosg::JSON ItemParameterTable::TechBoost::json() const { + return phosg::JSON::dict({ + {"TechNum1", this->tech_num1}, + {"Flags1", this->flags1}, + {"Amount1", this->amount1}, + {"TechNum2", this->tech_num2}, + {"Flags2", this->flags2}, + {"Amount2", this->amount2}, + {"TechNum3", this->tech_num3}, + {"Flags3", this->flags3}, + {"Amount3", this->amount3}, + }); +} + +ItemParameterTable::EventItem ItemParameterTable::EventItem::from_json(const phosg::JSON& json) { + ItemParameterTable::EventItem ret; + u32_to_item_code(ret.item, json.get_int("Item")); + ret.probability = json.get_int("Probability"); + return ret; +} +phosg::JSON ItemParameterTable::EventItem::json() const { + return phosg::JSON::dict({ + {"Item", item_code_to_u32(this->item)}, + {"Probability", this->probability}, + }); +} + +ItemParameterTable::UnsealableItem ItemParameterTable::UnsealableItem::from_json(const phosg::JSON& json) { + ItemParameterTable::UnsealableItem ret; + u32_to_item_code(ret.item, json.get_int("Item")); + return ret; +} +phosg::JSON ItemParameterTable::UnsealableItem::json() const { + return phosg::JSON::dict({{"Item", item_code_to_u32(this->item)}}); +} + +ItemParameterTable::NonWeaponSaleDivisors ItemParameterTable::NonWeaponSaleDivisors::from_json(const phosg::JSON& json) { + ItemParameterTable::NonWeaponSaleDivisors ret; + ret.armor_divisor = json.get_float("ArmorDivisor"); + ret.shield_divisor = json.get_float("ShieldDivisor"); + ret.unit_divisor = json.get_float("UnitDivisor"); + ret.mag_divisor = json.get_float("MagDivisor"); + return ret; +} +phosg::JSON ItemParameterTable::NonWeaponSaleDivisors::json() const { + return phosg::JSON::dict({ + {"ArmorDivisor", this->armor_divisor}, + {"ShieldDivisor", this->shield_divisor}, + {"UnitDivisor", this->unit_divisor}, + {"MagDivisor", this->mag_divisor}, + }); +} + +ItemParameterTable::ShieldEffect ItemParameterTable::ShieldEffect::from_json(const phosg::JSON& json) { + ItemParameterTable::ShieldEffect ret; + ret.sound_id = json.get_int("SoundID"); + ret.unknown_a1 = json.get_int("UnknownA1"); + return ret; +} +phosg::JSON ItemParameterTable::ShieldEffect::json() const { + return phosg::JSON::dict({{"SoundID", this->sound_id}, {"UnknownA1", this->unknown_a1}}); +} + +ItemParameterTable::PhotonColorEntry ItemParameterTable::PhotonColorEntry::from_json(const phosg::JSON& json) { + ItemParameterTable::PhotonColorEntry ret; + ret.unknown_a1 = json.get_int("UnknownA1"); + const auto& unknown_a2 = json.get_list("UnknownA2"); + const auto& unknown_a3 = json.get_list("UnknownA3"); + ret.unknown_a2.x = unknown_a2.at(0)->as_int(); + ret.unknown_a2.y = unknown_a2.at(1)->as_int(); + ret.unknown_a2.z = unknown_a2.at(2)->as_int(); + ret.unknown_a2.t = unknown_a2.at(3)->as_int(); + ret.unknown_a3.x = unknown_a3.at(0)->as_int(); + ret.unknown_a3.y = unknown_a3.at(1)->as_int(); + ret.unknown_a3.z = unknown_a3.at(2)->as_int(); + ret.unknown_a3.t = unknown_a3.at(3)->as_int(); + return ret; +} +phosg::JSON ItemParameterTable::PhotonColorEntry::json() const { + return phosg::JSON::dict({ + {"UnknownA1", this->unknown_a1}, + {"UnknownA2", phosg::JSON::list({this->unknown_a2.x.load(), this->unknown_a2.y.load(), this->unknown_a2.z.load(), this->unknown_a2.t.load()})}, + {"UnknownA3", phosg::JSON::list({this->unknown_a3.x.load(), this->unknown_a3.y.load(), this->unknown_a3.z.load(), this->unknown_a3.t.load()})}, + }); +} + +ItemParameterTable::UnknownA1 ItemParameterTable::UnknownA1::from_json(const phosg::JSON& json) { + ItemParameterTable::UnknownA1 ret; + ret.unknown_a1 = json.get_int("UnknownA1"); + ret.unknown_a2 = json.get_int("UnknownA2"); + return ret; +} +phosg::JSON ItemParameterTable::UnknownA1::json() const { + return phosg::JSON::dict({{"UnknownA1", this->unknown_a1}, {"UnknownA2", this->unknown_a2}}); +} + +ItemParameterTable::WeaponEffect ItemParameterTable::WeaponEffect::from_json(const phosg::JSON& json) { + ItemParameterTable::WeaponEffect ret; + ret.sound_id1 = json.get_int("SoundID1"); + ret.eff_value1 = json.get_int("EffectValue1"); + ret.sound_id2 = json.get_int("SoundID2"); + ret.eff_value2 = json.get_int("EffectValue2"); + std::string unknown_a5 = phosg::parse_data_string(json.get_string("UnknownA5")); + for (size_t z = 0; z < std::min(ret.unknown_a5.size(), unknown_a5.size()); z++) { + ret.unknown_a5[z] = unknown_a5[z]; + } + return ret; +} +phosg::JSON ItemParameterTable::WeaponEffect::json() const { + return phosg::JSON::dict({ + {"SoundID1", this->sound_id1}, + {"EffectValue1", this->eff_value1}, + {"SoundID2", this->sound_id2}, + {"EffectValue2", this->eff_value2}, + {"UnknownA5", phosg::format_data_string(this->unknown_a5.data(), this->unknown_a5.size())}, + }); +} + +ItemParameterTable::WeaponRange ItemParameterTable::WeaponRange::from_json(const phosg::JSON& json) { + ItemParameterTable::WeaponRange ret; + ret.unknown_a1 = json.get_int("UnknownA1"); + ret.unknown_a2 = json.get_int("UnknownA2"); + ret.unknown_a3 = json.get_int("UnknownA3"); + ret.unknown_a4 = json.get_int("UnknownA4"); + ret.unknown_a5 = json.get_int("UnknownA5"); + return ret; +} +phosg::JSON ItemParameterTable::WeaponRange::json() const { + return phosg::JSON::dict({ + {"UnknownA1", this->unknown_a1}, + {"UnknownA2", this->unknown_a2}, + {"UnknownA3", this->unknown_a3}, + {"UnknownA4", this->unknown_a4}, + {"UnknownA5", this->unknown_a5}, + }); +} + +ItemParameterTable::RangedSpecial ItemParameterTable::RangedSpecial::from_json(const phosg::JSON& json) { + ItemParameterTable::RangedSpecial ret; + ret.data1_1 = json.get_int("Data1_1"); + ret.data1_2 = json.get_int("Data1_2"); + ret.weapon_range_index = json.get_int("WeaponRangeIndex"); + ret.unknown_a1 = json.get_int("UnknownA1"); + return ret; +} +phosg::JSON ItemParameterTable::RangedSpecial::json() const { + return phosg::JSON::dict({ + {"Data1_1", this->data1_1}, + {"Data1_2", this->data1_2}, + {"WeaponRangeIndex", this->weapon_range_index}, + {"UnknownA1", this->unknown_a1}, + }); +} + +ItemParameterTable::SoundRemaps ItemParameterTable::SoundRemaps::from_json(const phosg::JSON& json) { + ItemParameterTable::SoundRemaps ret; + ret.sound_id = json.get_int("SoundID"); + for (const auto& sound_id_json : json.get_list("ByRTIndex")) { + ret.by_rt_index.emplace_back(sound_id_json->as_int()); + } + for (const auto& sound_id_json : json.get_list("ByCharClass")) { + ret.by_char_class.emplace_back(sound_id_json->as_int()); + } + return ret; +} + +phosg::JSON ItemParameterTable::SoundRemaps::json() const { + auto by_rt_index_json = phosg::JSON::list(); + auto by_char_class_json = phosg::JSON::list(); + for (uint32_t sound_id : this->by_rt_index) { + by_rt_index_json.emplace_back(sound_id); + } + for (uint32_t sound_id : this->by_char_class) { + by_char_class_json.emplace_back(sound_id); + } + return phosg::JSON::dict({ + {"SoundID", this->sound_id}, + {"ByRTIndex", std::move(by_rt_index_json)}, + {"ByCharClass", std::move(by_char_class_json)}, + }); +} + +phosg::JSON ItemParameterTable::json() const { + auto items_json = phosg::JSON::dict(); + + for (size_t data1_1 = 0; data1_1 < this->num_weapon_classes(); data1_1++) { + size_t class_size = this->num_weapons_in_class(data1_1); + uint8_t weapon_kind = this->get_weapon_kind(data1_1); + float sale_divisor = this->get_sale_divisor(0, data1_1); + for (size_t data1_2 = 0; data1_2 < class_size; data1_2++) { + auto weapon_dict = this->get_weapon(data1_1, data1_2).json(); + weapon_dict.emplace("WeaponKind", weapon_kind); + weapon_dict.emplace("SaleDivisor", sale_divisor); + items_json.emplace(std::format("00{:02X}{:02X}", data1_1, data1_2), std::move(weapon_dict)); + } + } + for (size_t data1_1 = 1; data1_1 < 3; data1_1++) { + size_t class_size = this->num_armors_or_shields_in_class(data1_1); + for (size_t data1_2 = 0; data1_2 < class_size; data1_2++) { + items_json.emplace(std::format("01{:02X}{:02X}", data1_1, data1_2), this->get_armor_or_shield(data1_1, data1_2).json()); + } + } + size_t class_size = this->num_units(); + for (size_t data1_2 = 0; data1_2 < class_size; data1_2++) { + items_json.emplace(std::format("0103{:02X}", data1_2), this->get_unit(data1_2).json()); + } + class_size = this->num_mags(); + for (size_t data1_1 = 0; data1_1 < class_size; data1_1++) { + items_json.emplace(std::format("02{:02X}", data1_1), this->get_mag(data1_1).json()); + } + for (size_t data1_1 = 0; data1_1 < this->num_tool_classes(); data1_1++) { + size_t class_size = this->num_tools_in_class(data1_1); + for (size_t data1_2 = 0; data1_2 < class_size; data1_2++) { + items_json.emplace(std::format("03{:02X}{:02X}", data1_1, data1_2), this->get_tool(data1_1, data1_2).json()); + } + } + + auto photon_colors_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_photon_colors(); z++) { + photon_colors_json.emplace_back(this->get_photon_color(z).json()); + } + + auto weapon_ranges_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_weapon_ranges(); z++) { + weapon_ranges_json.emplace_back(this->get_weapon_range(z).json()); + } + + float armor_sale_divisor = this->get_sale_divisor(1, 1); + float shield_sale_divisor = this->get_sale_divisor(1, 2); + float unit_sale_divisor = this->get_sale_divisor(1, 3); + float mag_sale_divisor = this->get_sale_divisor(2, 0); + + auto mag_feed_results_json = phosg::JSON::list(); + for (size_t table_index = 0; table_index < 8; table_index++) { + auto this_mag_feed_result_json = phosg::JSON::list(); + for (size_t item_index = 0; item_index < 11; item_index++) { + this_mag_feed_result_json.emplace_back(this->get_mag_feed_result(table_index, item_index).json()); + } + mag_feed_results_json.emplace_back(std::move(this_mag_feed_result_json)); + } + + auto [start_star_value_index, end_star_value_index] = this->get_star_value_index_range(); + auto star_values_json = phosg::JSON::list(); + for (size_t z = start_star_value_index; z < end_star_value_index; z++) { + star_values_json.emplace_back(this->get_item_stars(z)); + } + + phosg::JSON unknown_a1_json = this->get_unknown_a1(); + + auto specials_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_specials(); z++) { + specials_json.emplace_back(this->get_special(z).json()); + } + + auto weapon_effects_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_weapon_effects(); z++) { + weapon_effects_json.emplace_back(this->get_weapon_effect(z).json()); + } + + auto weapon_stat_boost_indexes_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_weapon_stat_boost_indexes(); z++) { + weapon_stat_boost_indexes_json.emplace_back(this->get_weapon_stat_boost_index(z)); + } + + auto armor_stat_boost_indexes_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_armor_stat_boost_indexes(); z++) { + armor_stat_boost_indexes_json.emplace_back(this->get_armor_stat_boost_index(z)); + } + + auto shield_stat_boost_indexes_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_shield_stat_boost_indexes(); z++) { + shield_stat_boost_indexes_json.emplace_back(this->get_shield_stat_boost_index(z)); + } + + auto stat_boosts_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_stat_boosts(); z++) { + stat_boosts_json.emplace_back(this->get_stat_boost(z).json()); + } + + auto shield_effects_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_shield_effects(); z++) { + shield_effects_json.emplace_back(this->get_shield_effect(z).json()); + } + + auto max_tech_levels_json = phosg::JSON::dict(); + for (size_t tech_num = 0; tech_num < 0x13; tech_num++) { + auto this_tech_ret = phosg::JSON::dict(); + for (size_t char_class = 0; char_class < 0x0C; char_class++) { + this_tech_ret.emplace(name_for_char_class(char_class), this->get_max_tech_level(char_class, tech_num)); + } + max_tech_levels_json.emplace(name_for_technique(tech_num), std::move(this_tech_ret)); + } + + auto combination_table_json = phosg::JSON::list(); + for (const auto& [used_item, combos] : this->all_item_combinations()) { + for (const auto& combo : combos) { + combination_table_json.emplace_back(combo.json()); + } + } + + auto sound_remaps_json = phosg::JSON::list(); + for (const auto& [_, remaps] : get_all_sound_remaps()) { + sound_remaps_json.emplace_back(remaps.json()); + } + + auto tech_boosts_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_tech_boosts(); z++) { + tech_boosts_json.emplace_back(this->get_tech_boost(z).json()); + } + + auto unwrap_tables_json = phosg::JSON::list(); + for (size_t event = 0; event < this->num_events(); event++) { + auto [items, count] = this->get_event_items(event); + for (size_t z = 0; z < count; z++) { + unwrap_tables_json.emplace_back(items[z].json()); + } + } + + auto unsealable_items_json = phosg::JSON::list(); + const auto& unsealable_items = this->all_unsealable_items(); + for (uint32_t item : unsealable_items) { + unsealable_items_json.emplace_back(item); + } + + auto ranged_specials_json = phosg::JSON::list(); + for (size_t z = 0; z < this->num_ranged_specials(); z++) { + ranged_specials_json.emplace_back(this->get_ranged_special(z).json()); + } + + return phosg::JSON::dict({ + {"ArmorSaleDivisor", armor_sale_divisor}, + {"ArmorStatBoostIndexes", std::move(armor_stat_boost_indexes_json)}, + {"ItemCombinations", std::move(combination_table_json)}, + {"Items", std::move(items_json)}, + {"MagFeedResults", std::move(mag_feed_results_json)}, + {"MagSaleDivisor", mag_sale_divisor}, + {"MaxTechLevels", std::move(max_tech_levels_json)}, + {"PhotonColors", std::move(photon_colors_json)}, + {"RangedSpecials", std::move(ranged_specials_json)}, + {"ShieldEffects", std::move(shield_effects_json)}, + {"ShieldSaleDivisor", shield_sale_divisor}, + {"ShieldStatBoostIndexes", std::move(shield_stat_boost_indexes_json)}, + {"SoundRemaps", std::move(sound_remaps_json)}, + {"Specials", std::move(specials_json)}, + {"StarValues", std::move(star_values_json)}, + {"StarValueBaseIndex", start_star_value_index}, + {"SpecialStarsBaseIndex", this->get_special_stars_base_index()}, + {"StatBoosts", std::move(stat_boosts_json)}, + {"TechBoosts", std::move(tech_boosts_json)}, + {"UnitSaleDivisor", unit_sale_divisor}, + {"UnknownA1", std::move(unknown_a1_json)}, + {"UnsealableItems", std::move(unsealable_items_json)}, + {"UnwrapTables", std::move(unwrap_tables_json)}, + {"WeaponEffects", std::move(weapon_effects_json)}, + {"WeaponRanges", std::move(weapon_ranges_json)}, + {"WeaponStatBoostIndexes", std::move(weapon_stat_boost_indexes_json)}, + }); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// JSON implementation + +class JSONItemParameterTable : public ItemParameterTable { +public: + JSONItemParameterTable() = delete; + + explicit JSONItemParameterTable(const phosg::JSON& json) { + for (const auto& [key, item_json] : json.get_dict("Items")) { + auto item_code = phosg::parse_data_string(key); + if (item_code.size() != 3) { + throw std::runtime_error("invalid item code in Items dict"); + } + uint8_t data1_0 = item_code[0]; + uint8_t data1_1 = item_code[1]; + uint8_t data1_2 = item_code[2]; + switch (data1_0) { + case 0: + if (this->weapons.size() <= data1_1) { + this->weapons.resize(data1_1 + 1); + } + if (this->weapons[data1_1].size() <= data1_2) { + this->weapons[data1_1].resize(data1_2 + 1); + } + this->weapons[data1_1][data1_2] = Weapon::from_json(*item_json); + if (this->weapon_kinds.size() <= data1_1) { + this->weapon_kinds.resize(data1_1 + 1, 0); + } + this->weapon_kinds[data1_1] = item_json->get_int("WeaponKind"); + if (this->weapon_sale_divisors.size() <= data1_1) { + this->weapon_sale_divisors.resize(data1_1 + 1, 0); + } + this->weapon_sale_divisors[data1_1] = item_json->get_int("WeaponKind"); + break; + case 1: + switch (item_code[1]) { + case 1: + if (this->armors.size() <= data1_2) { + this->armors.resize(data1_2 + 1); + } + this->armors[data1_2] = ArmorOrShield::from_json(*item_json); + break; + case 2: + if (this->shields.size() <= data1_2) { + this->shields.resize(data1_2 + 1); + } + this->shields[data1_2] = ArmorOrShield::from_json(*item_json); + break; + case 3: + if (this->units.size() <= data1_2) { + this->units.resize(data1_2 + 1); + } + this->units[data1_2] = Unit::from_json(*item_json); + break; + default: + throw std::runtime_error("invalid defensive item code in Items dict"); + } + break; + case 2: + if (this->mags.size() <= data1_1) { + this->mags.resize(data1_1 + 1); + } + this->mags[data1_1] = Mag::from_json(*item_json); + break; + case 3: + if (this->tools.size() <= data1_1) { + this->tools.resize(data1_1 + 1); + } + if (this->tools[data1_1].size() <= data1_2) { + this->tools[data1_1].resize(data1_2 + 1); + } + this->tools[data1_1][data1_2] = Tool::from_json(*item_json); + this->tools_by_id.emplace(this->tools[data1_1][data1_2].id, std::make_pair(data1_1, data1_2)); + break; + default: + throw std::runtime_error("invalid base item code in Items dict"); + } + } + + for (const auto& photon_color_json : json.get_list("PhotonColors")) { + this->photon_colors.emplace_back(PhotonColorEntry::from_json(*photon_color_json)); + } + + for (const auto& weapon_range_json : json.get_list("WeaponRanges")) { + this->weapon_ranges.emplace_back(WeaponRange::from_json(*weapon_range_json)); + } + + this->armor_sale_divisor = json.get_int("ArmorSaleDivisor"); + this->shield_sale_divisor = json.get_int("ShieldSaleDivisor"); + this->unit_sale_divisor = json.get_int("UnitSaleDivisor"); + this->mag_sale_divisor = json.get_int("MagSaleDivisor"); + + const auto& mag_feed_results_json = json.get_list("MagFeedResults"); + for (size_t table_index = 0; table_index < 8; table_index++) { + const auto& this_mag_feed_result_json = mag_feed_results_json.at(table_index); + for (size_t item_index = 0; item_index < 11; item_index++) { + this->mag_feed_results[table_index][item_index] = MagFeedResult::from_json( + this_mag_feed_result_json->at(item_index)); + } + } + + this->star_value_start_index = json.get_int("StarValueBaseIndex"); + this->special_stars_base_index = json.get_int("SpecialStarsBaseIndex"); + for (const auto& it : json.get_list("StarValues")) { + this->star_value_table.emplace_back(it->as_int()); + } + + this->unknown_a1 = json.get_string("UnknownA1"); + + for (const auto& it : json.get_list("Specials")) { + this->specials.emplace_back(Special::from_json(*it)); + } + + for (const auto& it : json.get_list("WeaponEffects")) { + this->weapon_effects.emplace_back(WeaponEffect::from_json(*it)); + } + + for (const auto& it : json.get_list("WeaponStatBoostIndexes")) { + this->weapon_stat_boost_indexes.emplace_back(it->as_int()); + } + for (const auto& it : json.get_list("ArmorStatBoostIndexes")) { + this->armor_stat_boost_indexes.emplace_back(it->as_int()); + } + for (const auto& it : json.get_list("ShieldStatBoostIndexes")) { + this->shield_stat_boost_indexes.emplace_back(it->as_int()); + } + + for (const auto& it : json.get_list("StatBoosts")) { + this->stat_boosts.emplace_back(StatBoost::from_json(*it)); + } + + for (const auto& it : json.get_list("ShieldEffects")) { + this->shield_effects.emplace_back(ShieldEffect::from_json(*it)); + } + + const auto& max_tech_levels_json = json.get_dict("MaxTechLevels"); + for (size_t tech_num = 0; tech_num < 0x13; tech_num++) { + const auto& this_tech_json = max_tech_levels_json.at(name_for_technique(tech_num))->as_dict(); + auto& this_tech_ret = this->max_tech_levels[tech_num]; + for (size_t char_class = 0; char_class < 12; char_class++) { + this_tech_ret[char_class] = this_tech_json.at(name_for_char_class(char_class))->as_int(); + } + } + + for (const auto& combo_json : json.get_list("ItemCombinations")) { + auto combo = ItemCombination::from_json(*combo_json); + this->item_combinations[item_code_to_u32(combo.used_item)].emplace_back(std::move(combo)); + } + + for (const auto& remaps_json : json.get_list("SoundRemaps")) { + auto remaps = SoundRemaps::from_json(*remaps_json); + this->sound_remaps.emplace(remaps.sound_id, std::move(remaps)); + } + + for (const auto& it : json.get_list("TechBoosts")) { + this->tech_boosts.emplace_back(TechBoost::from_json(*it)); + } + + for (const auto& table_it : json.get_list("UnwrapTables")) { + auto& this_table = this->unwrap_table.emplace_back(); + for (const auto& item_it : table_it->as_list()) { + this_table.emplace_back(EventItem::from_json(*item_it)); + } + } + + for (const auto& it : json.get_list("UnsealableItems")) { + this->unsealable_items.emplace(it->as_int()); + } + + for (const auto& it : json.get_list("RangedSpecials")) { + this->ranged_specials.emplace_back(RangedSpecial::from_json(*it)); + } + } + + virtual ~JSONItemParameterTable() = default; + + virtual size_t num_weapon_classes() const { + return this->weapons.size(); + } + virtual size_t num_weapons_in_class(uint8_t data1_1) const { + return this->weapons.at(data1_1).size(); + } + virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const { + return this->weapons.at(data1_1).at(data1_2); + } + + virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const { + if (data1_1 < 1 || data1_1 > 2) { + throw std::logic_error("invalid armor/shield class"); + } + return (data1_1 > 1) ? this->armors.size() : this->shields.size(); + } + virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const { + if (data1_1 < 1 || data1_1 > 2) { + throw std::logic_error("invalid armor/shield class"); + } + return (data1_1 > 1) ? this->armors.at(data1_2) : this->shields.at(data1_2); + } + + virtual size_t num_units() const { + return this->units.size(); + } + virtual const Unit& get_unit(uint8_t data1_2) const { + return this->units.at(data1_2); + } + + virtual size_t num_tool_classes() const { + return this->tools.size(); + } + virtual size_t num_tools_in_class(uint8_t data1_1) const { + return this->tools.at(data1_1).size(); + } + virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const { + return this->tools.at(data1_1).at(data1_2); + } + virtual std::pair find_tool_by_id(uint32_t id) const { + return this->tools_by_id.at(id); + } + + virtual size_t num_mags() const { + return this->mags.size(); + } + virtual const Mag& get_mag(uint8_t data1_1) const { + return this->mags.at(data1_1); + } + + virtual uint8_t get_weapon_kind(uint8_t data1_1) const { + return this->weapon_kinds.at(data1_1); + } + + virtual size_t num_photon_colors() const { + return this->photon_colors.size(); + } + virtual const PhotonColorEntry& get_photon_color(size_t index) const { + return this->photon_colors.at(index); + } + + virtual size_t num_weapon_ranges() const { + return this->weapon_ranges.size(); + } + virtual const WeaponRange& get_weapon_range(size_t index) const { + return this->weapon_ranges.at(index); + } + + virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const { + switch (data1_0) { + case 0: + return this->weapon_sale_divisors.at(data1_1); + case 1: + switch (data1_1) { + case 1: + return this->armor_sale_divisor; + case 2: + return this->shield_sale_divisor; + case 3: + return this->unit_sale_divisor; + } + case 2: + return this->mag_sale_divisor; + default: + throw logic_error("item type does not have a sale divisor"); + } + } + + virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const { + return this->mag_feed_results.at(table_index).at(item_index); + } + + virtual std::pair get_star_value_index_range() const { + return std::make_pair(this->star_value_start_index, this->star_value_start_index + this->star_value_table.size()); + } + virtual uint32_t get_special_stars_base_index() const { + return this->special_stars_base_index; + } + virtual uint8_t get_item_stars(uint32_t id) const { + return this->star_value_table.at(id - this->star_value_start_index); + } + virtual uint8_t get_special_stars(uint8_t special) const { + return ((special & 0x3F) && !(special & 0x80)) ? this->get_item_stars(special + this->special_stars_base_index) : 0; + } + + virtual std::string get_unknown_a1() const { + return this->unknown_a1; + } + + virtual size_t num_specials() const { + return this->specials.size(); + } + virtual const Special& get_special(uint8_t special) const { + return this->specials.at(special); + } + + virtual size_t num_weapon_effects() const { + return this->weapon_effects.size(); + } + virtual const WeaponEffect& get_weapon_effect(size_t index) const { + return this->weapon_effects.at(index); + } + + virtual size_t num_weapon_stat_boost_indexes() const { + return this->weapon_stat_boost_indexes.size(); + } + virtual uint8_t get_weapon_stat_boost_index(size_t index) const { + return this->weapon_stat_boost_indexes.at(index); + } + virtual size_t num_armor_stat_boost_indexes() const { + return this->armor_stat_boost_indexes.size(); + } + virtual uint8_t get_armor_stat_boost_index(size_t index) const { + return this->armor_stat_boost_indexes.at(index); + } + virtual size_t num_shield_stat_boost_indexes() const { + return this->shield_stat_boost_indexes.size(); + } + virtual uint8_t get_shield_stat_boost_index(size_t index) const { + return this->shield_stat_boost_indexes.at(index); + } + + virtual size_t num_stat_boosts() const { + return this->stat_boosts.size(); + } + virtual const StatBoost& get_stat_boost(size_t index) const { + return this->stat_boosts.at(index); + } + + virtual size_t num_shield_effects() const { + return this->shield_effects.size(); + } + virtual const ShieldEffect& get_shield_effect(size_t index) const { + return this->shield_effects.at(index); + } + + virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const { + return this->max_tech_levels.at(tech_num).at(char_class); + } + + virtual const std::map>& all_item_combinations() const { + return this->item_combinations; + } + + virtual const std::unordered_map& get_all_sound_remaps() const { + return this->sound_remaps; + } + + virtual size_t num_tech_boosts() const { + return this->tech_boosts.size(); + } + virtual const TechBoost& get_tech_boost(size_t index) const { + return this->tech_boosts.at(index); + } + + virtual size_t num_events() const { + return this->unwrap_table.size(); + } + virtual std::pair get_event_items(uint8_t event_number) const { + const auto& event_table = this->unwrap_table.at(event_number); + return std::make_pair(event_table.data(), event_table.size()); + } + + virtual const std::unordered_set& all_unsealable_items() const { + return this->unsealable_items; + } + + virtual size_t num_ranged_specials() const { + return this->ranged_specials.size(); + } + virtual const RangedSpecial& get_ranged_special(size_t index) const { + return this->ranged_specials.at(index); + } + +protected: + std::vector> weapons; + std::vector armors; + std::vector shields; + std::vector units; + std::vector> tools; + std::unordered_map> tools_by_id; + std::vector mags; + std::vector weapon_kinds; + std::vector photon_colors; + std::vector weapon_ranges; + std::vector weapon_sale_divisors; + float armor_sale_divisor; + float shield_sale_divisor; + float unit_sale_divisor; + float mag_sale_divisor; + std::array, 8> mag_feed_results; + std::vector star_value_table; + size_t star_value_start_index; + size_t special_stars_base_index; + std::string unknown_a1; + std::vector specials; + std::vector weapon_effects; + std::vector weapon_stat_boost_indexes; + std::vector armor_stat_boost_indexes; + std::vector shield_stat_boost_indexes; + std::vector stat_boosts; + std::vector shield_effects; + MaxTechniqueLevels max_tech_levels; + std::map> item_combinations; + std::unordered_map sound_remaps; + std::vector tech_boosts; + std::vector> unwrap_table; + std::unordered_set unsealable_items; + std::vector ranged_specials; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Serialized structures + template struct ItemBaseV2T { /* 00 */ U32T id = 0xFFFFFFFF; @@ -164,14 +1218,14 @@ template struct WeaponV3T : WeaponGCNTET { /* 24 */ uint8_t unknown_a4 = 0; /* 25 */ uint8_t unknown_a5 = 0; - /* 26 */ uint8_t tech_boost = 0; + /* 26 */ uint8_t tech_boost_entry_index = 0; /* 27 */ uint8_t behavior_flags = 0; /* 28 */ operator ItemParameterTable::Weapon() const { ItemParameterTable::Weapon ret = this->WeaponGCNTET::operator ItemParameterTable::Weapon(); ret.unknown_a4 = this->unknown_a4; ret.unknown_a5 = this->unknown_a5; - ret.tech_boost = this->tech_boost; + ret.tech_boost_entry_index = this->tech_boost_entry_index; ret.behavior_flags = this->behavior_flags; return ret; } @@ -202,7 +1256,7 @@ struct WeaponV4 { /* 25 */ parray unknown_a1 = 0; /* 28 */ uint8_t unknown_a4 = 0; /* 29 */ uint8_t unknown_a5 = 0; - /* 2A */ uint8_t tech_boost = 0; + /* 2A */ uint8_t tech_boost_entry_index = 0; /* 2B */ uint8_t behavior_flags = 0; /* 2C */ operator ItemParameterTable::Weapon() const { @@ -229,7 +1283,7 @@ struct WeaponV4 { ret.unknown_a1 = this->unknown_a1; ret.unknown_a4 = this->unknown_a4; ret.unknown_a5 = this->unknown_a5; - ret.tech_boost = this->tech_boost; + ret.tech_boost_entry_index = this->tech_boost_entry_index; ret.behavior_flags = this->behavior_flags; return ret; } @@ -282,14 +1336,14 @@ using ArmorOrShieldDCProtos = ArmorOrShieldT, false>; template struct ArmorOrShieldFinalT : ArmorOrShieldT { /* 14 */ uint8_t stat_boost_entry_index = 0; - /* 15 */ uint8_t tech_boost = 0; + /* 15 */ uint8_t tech_boost_entry_index = 0; /* 16 */ uint8_t flags_type = 0; /* 17 */ uint8_t unknown_a4 = 0; /* 18 */ operator ItemParameterTable::ArmorOrShield() const { ItemParameterTable::ArmorOrShield ret = this->ArmorOrShieldT::operator ItemParameterTable::ArmorOrShield(); ret.stat_boost_entry_index = this->stat_boost_entry_index; - ret.tech_boost = this->tech_boost; + ret.tech_boost_entry_index = this->tech_boost_entry_index; ret.flags_type = this->flags_type; ret.unknown_a4 = this->unknown_a4; return ret; @@ -454,22 +1508,40 @@ struct SpecialT { template struct StatBoostT { parray stats = 0; - parray, 2> amounts; + parray, 2> amounts = 0; operator ItemParameterTable::StatBoost() const { return {this->stats[0], this->amounts[0], this->stats[1], this->amounts[1]}; } } __packed_ws_be__(StatBoostT, 6); template -struct TechniqueBoostEntryT { - uint8_t tech_num = 0; - uint8_t flags = 0; - parray unused; - F32T amount = 0.0f; - operator ItemParameterTable::TechniqueBoost() const { - return {this->tech_num, this->flags, this->amount}; +struct TechBoostEntryT { + uint8_t tech_num1 = 0; + uint8_t flags1 = 0; + parray unused1; + F32T amount1 = 0.0f; + uint8_t tech_num2 = 0; + uint8_t flags2 = 0; + parray unused2; + F32T amount2 = 0.0f; + uint8_t tech_num3 = 0; + uint8_t flags3 = 0; + parray unused3; + F32T amount3 = 0.0f; + operator ItemParameterTable::TechBoost() const { + return { + this->tech_num1, + this->flags1, + this->amount1, + this->tech_num2, + this->flags2, + this->amount2, + this->tech_num3, + this->flags3, + this->amount3, + }; } -} __packed_ws_be__(TechniqueBoostEntryT, 8); +} __packed_ws_be__(TechBoostEntryT, 0x18); struct NonWeaponSaleDivisorsDCProtos { uint8_t armor_divisor = 0; @@ -534,16 +1606,6 @@ struct UnknownA1T { } } __packed_ws_be__(UnknownA1T, 4); -template -struct UnknownA5T { - U32T target_param; - U32T unknown_a2; - U32T unknown_a3; - operator ItemParameterTable::UnknownA5() const { - return {this->target_param, this->unknown_a2, this->unknown_a3}; - } -} __packed_ws_be__(UnknownA5T, 0x0C); - template struct WeaponRangeT { F32T unknown_a1; @@ -557,7 +1619,7 @@ struct WeaponRangeT { } __packed_ws_be__(WeaponRangeT, 0x14); template -struct WeaponEffect { +struct WeaponEffectT { U32T sound_id1; U32T eff_value1; U32T sound_id2; @@ -566,135 +1628,168 @@ struct WeaponEffect { operator ItemParameterTable::WeaponEffect() const { return {this->sound_id1, this->eff_value1, this->sound_id2, this->eff_value2, this->unknown_a5}; } -} __packed_ws_be__(WeaponEffect, 0x20); +} __packed_ws_be__(WeaponEffectT, 0x20); -/* The fields in the root structure are: - * DCTE / 112K / V1 / V2 / GCTE / V3 / V4 - * 0013 / 0013 / 0013 / 0013 / / / entry_count // Count of pointers in root struct; unused - * 0668 / 0668 / / / / / armor_stat_boost_index_table // -> [uint8_t] - * 2D94 / 2E28 / 3258 / 5A5C / 6E4C / EF90 / 1478C armor_table // -> [{count, offset -> [ArmorOrShieldV*]}](2; armors and shields) - * / / / / 737C / F5D0 / 14FF4 combination_table // -> {count, offset -> [ItemCombination]} - * 2F54 / 2FF0 / 3420 / 5F4C / 7384 / F608 / 1502C mag_feed_table // -> MagFeedResultsTable - * 2DAC / 2E40 / 3270 / 5A74 / 6E64 / EFA8 / 147A4 mag_table // -> {count, offset -> [MagV*]} - * / / / / 69D8 / DF88 / 12894 max_tech_level_table // -> MaxTechniqueLevels - * 1FE6 / 207A / 248C / 40A8 / 4A80 / BBCC / 0F83C non_weapon_sale_divisor_table // -> NonWeaponSaleDivisors - * 1994 / 1A28 / 1DB0 / 2E4C / 37A4 / A7FC / 0DE7C photon_color_table // -> PhotonColorEntry[...] - * / / / / / F600 / 15024 ranged_special_table // -> {count, offset -> [RangedSpecial]} - * / / 3198 / 5704 / 61B8 / D6E4 / 11C80 shield_effect_table // -> ShieldEffect[...] (indexed by data1[2]) - * 030C / 030C / / / / / shield_stat_boost_index_table // -> [uint8_t] - * 275E / 27F4 / 2C12 / 4540 / 4F72 / C100 / 0FE3C special_table // -> [Special] - * 22A9 / 233D / 275C / 4378 / 4D50 / BE9C / 0FB0C star_value_table // -> [uint8_t] (indexed by .id from weapon, armor, etc.) - * 2CE4 / 2D78 / 2CB8 / 58DC / 68B8 / DE50 / 1275C stat_boost_table // -> [StatBoost] - * / / / / 6B1C / EB8C / 14278 tech_boost_table // -> [TechniqueBoostEntry[3]] - * 2DB4 / 2E48 / 3278 / 5A7C / 6E6C / EFB0 / 147AC tool_table // -> [{count, offset -> [ToolV*]}] (last if out of range) - * 2DA4 / 2E38 / 3268 / 5A6C / 6E5C / EFA0 / 1479C unit_table // -> {count, offset -> [UnitV*]} (last if out of range) - * 23EE / 2484 / 28A2 / 45E4 / / / unknown_a1 // TODO - * / / / / 68B0 / DE48 / 12754 unknown_a5 // -> {count, offset -> [UnknownA5]} - * / / / / / F5F8 / 1501C unsealable_table // -> {count, offset -> [UnsealableItem]} - * / / / / / F5F0 / 15014 unwrap_table // -> {count, offset -> [{count, offset -> [EventItem]}]} - * 1F98 / 202C / 23C8 / 3DF8 / 47BC / B88C / 0F4B8 weapon_class_table // -> [uint8_t](0x89) - * 2804 / 2898 / / / 5018 / C1A4 / 0FEE0 weapon_effect_table // -> [WeaponEffect] - * 1C64 / 1CF8 / 2080 / 32CC / 3A74 / AACC / 0E194 weapon_range_table // -> WeaponRange[...] - * 1FBF / 2053 / 23F0 / 3E84 / 484C / B938 / 0F5A8 weapon_sale_divisor_table // -> [uint8_t] on DC protos; [float] on all other versions - * 1908 / 199C / / / / / weapon_stat_boost_index_table // -> [StatBoost] - * 2E1C / 2EB8 / 32E8 / 5AFC / 6F0C / F078 / 14884 weapon_table // -> [{count, offset -> [WeaponV*]}] +template +struct SoundRemapTableOffsetsT { + U32T sound_id; + U32T remaps_for_rt_index_table; + U32T remaps_for_char_class_table; +} __packed_ws_be__(SoundRemapTableOffsetsT, 0x0C); + +/* The fields in the root structures are as follows. See the specializations of BinaryItemParameterTableT for the + * values of the hardcoded constants in each version (NumWeaponClasses, etc.). + * DCTE / 112K / V1 / V2 / GCTE / GCV3 / XBV3 / V4 + * 0013 / 0013 / 0013 / 0013 / ---- / ---- / ---- / ----- entry_count // Count of pointers in root struct; unused + * 2E1C / 2EB8 / 32E8 / 5AFC / 6F0C / F078 / F084 / 14884 weapon_table // -> {count, offset -> WeaponV*[count]}[NumWeaponClasses] + * 2D94 / 2E28 / 3258 / 5A5C / 6E4C / EF90 / EF9C / 1478C armor_table // -> {count, offset -> ArmorOrShieldV*[count]}][2] + * 2DA4 / 2E38 / 3268 / 5A6C / 6E5C / EFA0 / EFAC / 1479C unit_table // -> {count, offset -> UnitV*[count]} (last if out of range) + * 2DB4 / 2E48 / 3278 / 5A7C / 6E6C / EFB0 / EFBC / 147AC tool_table // -> {count, offset -> ToolV*[count]}[NumToolClasses] (last if out of range) + * 2DAC / 2E40 / 3270 / 5A74 / 6E64 / EFA8 / EFB4 / 147A4 mag_table // -> {count, offset -> MagV*[count]} + * 1F98 / 202C / 23C8 / 3DF8 / 47BC / B88C / B8A0 / 0F4B8 weapon_kind_table // -> uint8_t[NumWeaponClasses] + * 1994 / 1A28 / 1DB0 / 2E4C / 37A4 / A7FC / A7FC / 0DE7C photon_color_table // -> PhotonColorEntry[NumPhotonColors] + * 1C64 / 1CF8 / 2080 / 32CC / 3A74 / AACC / AACC / 0E194 weapon_range_table // -> WeaponRange[...] (indexed by data1_1, but also by RangedSpecial::weapon_range_index + a version-dependent constant, and also by the result of a vfunc call on some TItemWeapons) + * 1FBF / 2053 / 23F0 / 3E84 / 484C / B938 / B94C / 0F5A8 weapon_sale_divisor_table // -> uint8_t[NumWeaponClasses] on DC protos; float[NumWeaponClasses] on all other versions + * 1FE6 / 207A / 248C / 40A8 / 4A80 / BBCC / BBE0 / 0F83C non_weapon_sale_divisor_table // -> NonWeaponSaleDivisors + * 2F54 / 2FF0 / 3420 / 5F4C / 7384 / F608 / F614 / 1502C mag_feed_table // -> MagFeedResultsTable + * 22A9 / 233D / 275C / 4378 / 4D50 / BE9C / BEB0 / 0FB0C star_value_table // -> uint8_t[...] (indexed by .id from weapon, armor, etc.) + * 23EE / 2484 / 28A2 / ---- / ---- / ---- / ---- / ----- unknown_a1 // TODO + * 275E / 27F4 / 2C12 / 4540 / 4F72 / C100 / C114 / 0FE3C special_table // -> Special[NumSpecials] + * 2804 / 2898 / 2CB8 / 45E4 / 5018 / C1A4 / C1B8 / 0FEE0 weapon_effect_table // -> WeaponEffect[...] + * 1908 / 199C / ---- / ---- / ---- / ---- / ---- / ----- weapon_stat_boost_index_table // -> uint8_t[max(weapon.id : weapon_table) - (ItemStarsFirstID - 1)] + * 0668 / 0668 / ---- / ---- / ---- / ---- / ---- / ----- armor_stat_boost_index_table // -> uint8_t[armor_table[0].count] + * 030C / 030C / ---- / ---- / ---- / ---- / ---- / ----- shield_stat_boost_index_table // -> uint8_t[armor_table[1].count] + * 2CE4 / 2D78 / 3198 / 58DC / 68B8 / DE50 / DE64 / 1275C stat_boost_table // -> StatBoost[...] + * ---- / ---- / ---- / 5704 / 61B8 / D6E4 / D6F8 / 11C80 shield_effect_table // -> ShieldEffect[...] (indexed by data1[2]) + * ---- / ---- / ---- / ---- / 69D8 / DF88 / DF9C / 12894 max_tech_level_table // -> MaxTechniqueLevels + * ---- / ---- / ---- / ---- / 737C / F5D0 / F5DC / 14FF4 combination_table // -> {count, offset -> ItemCombination[count]} + * ---- / ---- / ---- / ---- / 68B0 / DE48 / DE5C / 12754 sound_remap_table // -> {count, offset -> {sound_id, by_rt_index_offset -> uint32_t[SoundRemapRTTableSize], by_char_class_offset -> uint32_t[12]}} + * ---- / ---- / ---- / ---- / 6B1C / EB8C / EBA0 / 14278 tech_boost_table // -> TechBoostEntry[...][3] + * ---- / ---- / ---- / ---- / ---- / F5F0 / F5FC / 15014 unwrap_table // -> {count, offset -> {count, offset -> EventItem[count]}[count] + * ---- / ---- / ---- / ---- / ---- / F5F8 / F604 / 1501C unsealable_table // -> {count, offset -> UnsealableItem[count]} + * ---- / ---- / ---- / ---- / ---- / F600 / F60C / 15024 ranged_special_table // -> {count, offset -> RangedSpecial[count]} */ struct RootDCProtos { - /* 00 */ le_uint32_t entry_count; - /* 04 */ le_uint32_t weapon_table; - /* 08 */ le_uint32_t armor_table; - /* 0C */ le_uint32_t unit_table; - /* 10 */ le_uint32_t tool_table; - /* 14 */ le_uint32_t mag_table; - /* 18 */ le_uint32_t weapon_class_table; - /* 1C */ le_uint32_t photon_color_table; - /* 20 */ le_uint32_t weapon_range_table; - /* 24 */ le_uint32_t weapon_integral_sale_divisor_table; - /* 28 */ le_uint32_t non_weapon_integral_sale_divisor_table; - /* 2C */ le_uint32_t mag_feed_table; - /* 30 */ le_uint32_t star_value_table; - /* 34 */ le_uint32_t unknown_a1; - /* 38 */ le_uint32_t special_table; - /* 3C */ le_uint32_t weapon_effect_table; - /* 40 */ le_uint32_t weapon_stat_boost_index_table; - /* 44 */ le_uint32_t armor_stat_boost_index_table; - /* 48 */ le_uint32_t shield_stat_boost_index_table; - /* 4C */ le_uint32_t stat_boost_table; + /* ## / DCTE / 112K */ + /* 00 / 0013 / 0013 */ le_uint32_t entry_count; + /* 04 / 2E1C / 2EB8 */ le_uint32_t weapon_table; + /* 08 / 2D94 / 2E28 */ le_uint32_t armor_table; + /* 0C / 2DA4 / 2E38 */ le_uint32_t unit_table; + /* 10 / 2DB4 / 2E48 */ le_uint32_t tool_table; + /* 14 / 2DAC / 2E40 */ le_uint32_t mag_table; + /* 18 / 1F98 / 202C */ le_uint32_t weapon_kind_table; + /* 1C / 1994 / 1A28 */ le_uint32_t photon_color_table; + /* 20 / 1C64 / 1CF8 */ le_uint32_t weapon_range_table; + /* 24 / 1FBF / 2053 */ le_uint32_t weapon_integral_sale_divisor_table; + /* 28 / 1FE6 / 207A */ le_uint32_t non_weapon_integral_sale_divisor_table; + /* 2C / 2F54 / 2FF0 */ le_uint32_t mag_feed_table; + /* 30 / 22A9 / 233D */ le_uint32_t star_value_table; + /* 34 / 23EE / 2484 */ le_uint32_t unknown_a1; + /* 38 / 275E / 27F4 */ le_uint32_t special_table; + /* 3C / 2804 / 2898 */ le_uint32_t weapon_effect_table; + /* 40 / 1908 / 199C */ le_uint32_t weapon_stat_boost_index_table; + /* 44 / 0668 / 0668 */ le_uint32_t armor_stat_boost_index_table; + /* 48 / 030C / 030C */ le_uint32_t shield_stat_boost_index_table; + /* 4C / 2CE4 / 2D78 */ le_uint32_t stat_boost_table; } __packed_ws__(RootDCProtos, 0x50); -struct RootV1V2 { - /* 00 */ le_uint32_t entry_count; - /* 04 */ le_uint32_t weapon_table; - /* 08 */ le_uint32_t armor_table; - /* 0C */ le_uint32_t unit_table; - /* 10 */ le_uint32_t tool_table; - /* 14 */ le_uint32_t mag_table; - /* 18 */ le_uint32_t weapon_class_table; - /* 1C */ le_uint32_t photon_color_table; - /* 20 */ le_uint32_t weapon_range_table; - /* 24 */ le_uint32_t weapon_sale_divisor_table; - /* 28 */ le_uint32_t non_weapon_sale_divisor_table; - /* 2C */ le_uint32_t mag_feed_table; - /* 30 */ le_uint32_t star_value_table; - /* 34 */ le_uint32_t unknown_a1; - /* 38 */ le_uint32_t special_table; - /* 3C */ le_uint32_t stat_boost_table; - /* 40 */ le_uint32_t shield_effect_table; -} __packed_ws__(RootV1V2, 0x44); +struct RootV1 { + /* ## / DCV1 */ + /* 00 / 0013 */ le_uint32_t entry_count; + /* 04 / 32E8 */ le_uint32_t weapon_table; + /* 08 / 3258 */ le_uint32_t armor_table; + /* 0C / 3268 */ le_uint32_t unit_table; + /* 10 / 3278 */ le_uint32_t tool_table; + /* 14 / 3270 */ le_uint32_t mag_table; + /* 18 / 23C8 */ le_uint32_t weapon_kind_table; + /* 1C / 1DB0 */ le_uint32_t photon_color_table; + /* 20 / 2080 */ le_uint32_t weapon_range_table; + /* 24 / 23F0 */ le_uint32_t weapon_sale_divisor_table; + /* 28 / 248C */ le_uint32_t non_weapon_sale_divisor_table; + /* 2C / 3420 */ le_uint32_t mag_feed_table; + /* 30 / 275C */ le_uint32_t star_value_table; + /* 34 / 28A2 */ le_uint32_t unknown_a1; + /* 38 / 2C12 */ le_uint32_t special_table; + /* 3C / 2CB8 */ le_uint32_t weapon_effect_table; + /* 40 / 3198 */ le_uint32_t stat_boost_table; +} __packed_ws__(RootV1, 0x44); + +struct RootV2 { + /* ## / DCV2 */ + /* 00 / 0013 */ le_uint32_t entry_count; + /* 04 / 5AFC */ le_uint32_t weapon_table; + /* 08 / 5A5C */ le_uint32_t armor_table; + /* 0C / 5A6C */ le_uint32_t unit_table; + /* 10 / 5A7C */ le_uint32_t tool_table; + /* 14 / 5A74 */ le_uint32_t mag_table; + /* 18 / 3DF8 */ le_uint32_t weapon_kind_table; + /* 1C / 2E4C */ le_uint32_t photon_color_table; + /* 20 / 32CC */ le_uint32_t weapon_range_table; + /* 24 / 3E84 */ le_uint32_t weapon_sale_divisor_table; + /* 28 / 40A8 */ le_uint32_t non_weapon_sale_divisor_table; + /* 2C / 5F4C */ le_uint32_t mag_feed_table; + /* 30 / 4378 */ le_uint32_t star_value_table; + /* 34 / 4540 */ le_uint32_t special_table; + /* 38 / 45E4 */ le_uint32_t weapon_effect_table; + /* 3C / 58DC */ le_uint32_t stat_boost_table; + /* 40 / 5704 */ le_uint32_t shield_effect_table; +} __packed_ws__(RootV2, 0x44); struct RootGCNTE { - /* 00 */ be_uint32_t weapon_table; - /* 04 */ be_uint32_t armor_table; - /* 08 */ be_uint32_t unit_table; - /* 0C */ be_uint32_t tool_table; - /* 10 */ be_uint32_t mag_table; - /* 14 */ be_uint32_t weapon_class_table; - /* 18 */ be_uint32_t photon_color_table; - /* 1C */ be_uint32_t weapon_range_table; - /* 20 */ be_uint32_t weapon_sale_divisor_table; - /* 24 */ be_uint32_t non_weapon_sale_divisor_table; - /* 28 */ be_uint32_t mag_feed_table; - /* 2C */ be_uint32_t star_value_table; - /* 30 */ be_uint32_t special_table; - /* 34 */ be_uint32_t weapon_effect_table; - /* 38 */ be_uint32_t stat_boost_table; - /* 3C */ be_uint32_t shield_effect_table; - /* 40 */ be_uint32_t max_tech_level_table; - /* 44 */ be_uint32_t combination_table; - /* 48 */ be_uint32_t unknown_a5; - /* 4C */ be_uint32_t tech_boost_table; + /* ## / GCTE */ + /* 00 / 6F0C */ be_uint32_t weapon_table; + /* 04 / 6E4C */ be_uint32_t armor_table; + /* 08 / 6E5C */ be_uint32_t unit_table; + /* 0C / 6E6C */ be_uint32_t tool_table; + /* 10 / 6E64 */ be_uint32_t mag_table; + /* 14 / 47BC */ be_uint32_t weapon_kind_table; + /* 18 / 37A4 */ be_uint32_t photon_color_table; + /* 1C / 3A74 */ be_uint32_t weapon_range_table; + /* 20 / 484C */ be_uint32_t weapon_sale_divisor_table; + /* 24 / 4A80 */ be_uint32_t non_weapon_sale_divisor_table; + /* 28 / 7384 */ be_uint32_t mag_feed_table; + /* 2C / 4D50 */ be_uint32_t star_value_table; + /* 30 / 4F72 */ be_uint32_t special_table; + /* 34 / 5018 */ be_uint32_t weapon_effect_table; + /* 38 / 68B8 */ be_uint32_t stat_boost_table; + /* 3C / 61B8 */ be_uint32_t shield_effect_table; + /* 40 / 69D8 */ be_uint32_t max_tech_level_table; + /* 44 / 737C */ be_uint32_t combination_table; + /* 48 / 68B0 */ be_uint32_t sound_remap_table; + /* 4C / 6B1C */ be_uint32_t tech_boost_table; } __packed_ws__(RootGCNTE, 0x50); template struct RootV3V4T { - /* 00 */ U32T weapon_table; - /* 04 */ U32T armor_table; - /* 08 */ U32T unit_table; - /* 0C */ U32T tool_table; - /* 10 */ U32T mag_table; - /* 14 */ U32T weapon_class_table; - /* 18 */ U32T photon_color_table; - /* 1C */ U32T weapon_range_table; - /* 20 */ U32T weapon_sale_divisor_table; - /* 24 */ U32T non_weapon_sale_divisor_table; - /* 28 */ U32T mag_feed_table; - /* 2C */ U32T star_value_table; - /* 30 */ U32T special_table; - /* 34 */ U32T weapon_effect_table; - /* 38 */ U32T stat_boost_table; - /* 3C */ U32T shield_effect_table; - /* 40 */ U32T max_tech_level_table; - /* 44 */ U32T combination_table; - /* 48 */ U32T unknown_a5; - /* 4C */ U32T tech_boost_table; - /* 50 */ U32T unwrap_table; - /* 54 */ U32T unsealable_table; - /* 58 */ U32T ranged_special_table; + /* ## / GCV3 / XBV3 / BBV4 */ + /* 00 / F078 / F084 / 14884 */ U32T weapon_table; + /* 04 / EF90 / EF9C / 1478C */ U32T armor_table; + /* 08 / EFA0 / EFAC / 1479C */ U32T unit_table; + /* 0C / EFB0 / EFBC / 147AC */ U32T tool_table; + /* 10 / EFA8 / EFB4 / 147A4 */ U32T mag_table; + /* 14 / B88C / B8A0 / 0F4B8 */ U32T weapon_kind_table; + /* 18 / A7FC / A7FC / 0DE7C */ U32T photon_color_table; + /* 1C / AACC / AACC / 0E194 */ U32T weapon_range_table; + /* 20 / B938 / B94C / 0F5A8 */ U32T weapon_sale_divisor_table; + /* 24 / BBCC / BBE0 / 0F83C */ U32T non_weapon_sale_divisor_table; + /* 28 / F608 / F614 / 1502C */ U32T mag_feed_table; + /* 2C / BE9C / BEB0 / 0FB0C */ U32T star_value_table; + /* 30 / C100 / C114 / 0FE3C */ U32T special_table; + /* 34 / C1A4 / C1B8 / 0FEE0 */ U32T weapon_effect_table; + /* 38 / DE50 / DE64 / 1275C */ U32T stat_boost_table; + /* 3C / D6E4 / D6F8 / 11C80 */ U32T shield_effect_table; + /* 40 / DF88 / DF9C / 12894 */ U32T max_tech_level_table; + /* 44 / F5D0 / F5DC / 14FF4 */ U32T combination_table; + /* 48 / DE48 / DE5C / 12754 */ U32T sound_remap_table; + /* 4C / EB8C / EBA0 / 14278 */ U32T tech_boost_table; + /* 50 / F5F0 / F5FC / 15014 */ U32T unwrap_table; + /* 54 / F5F8 / F604 / 1501C */ U32T unsealable_table; + /* 58 / F600 / F60C / 15024 */ U32T ranged_special_table; } __packed_ws_be__(RootV3V4T, 0x5C); -ItemParameterTable::ItemParameterTable(std::shared_ptr data) - : data(data), r(*this->data) {} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Reader implementation std::set ItemParameterTable::compute_all_valid_primary_identifiers() const { set ret; @@ -861,6 +1956,39 @@ uint8_t ItemParameterTable::get_item_adjusted_stars(const ItemData& item, bool i return min(ret, 12); } +std::string ItemParameterTable::get_star_value_table() const { + auto [start_z, end_z] = this->get_star_value_index_range(); + std::string ret; + for (size_t z = start_z; z < end_z; z++) { + ret.push_back(this->get_item_stars(z)); + } + return ret; +} + +std::string ItemParameterTable::get_weapon_stat_boost_index_table() const { + std::string ret; + for (size_t z = 0; z < this->num_weapon_stat_boost_indexes(); z++) { + ret.push_back(this->get_weapon_stat_boost_index(z)); + } + return ret; +} + +std::string ItemParameterTable::get_armor_stat_boost_index_table() const { + std::string ret; + for (size_t z = 0; z < this->num_armor_stat_boost_indexes(); z++) { + ret.push_back(this->get_armor_stat_boost_index(z)); + } + return ret; +} + +std::string ItemParameterTable::get_shield_stat_boost_index_table() const { + std::string ret; + for (size_t z = 0; z < this->num_shield_stat_boost_indexes(); z++) { + ret.push_back(this->get_shield_stat_boost_index(z)); + } + return ret; +} + bool ItemParameterTable::is_item_rare(const ItemData& item) const { try { return (this->get_item_base_stars(item) >= 9); @@ -869,13 +1997,17 @@ bool ItemParameterTable::is_item_rare(const ItemData& item) const { } } +bool ItemParameterTable::is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const { + return this->all_unsealable_items().count(item_code_to_u32(data1_0, data1_1, data1_2)); +} + bool ItemParameterTable::is_unsealable_item(const ItemData& item) const { return this->is_unsealable_item(item.data1[0], item.data1[1], item.data1[2]); } const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combination( const ItemData& used_item, const ItemData& equipped_item) const { - for (const auto& def : this->get_all_combinations_for_used_item(used_item)) { + for (const auto& def : this->all_combinations_for_used_item(used_item)) { if ((def.equipped_item[0] == 0xFF || def.equipped_item[0] == equipped_item.data1[0]) && (def.equipped_item[1] == 0xFF || def.equipped_item[1] == equipped_item.data1[1]) && (def.equipped_item[2] == 0xFF || def.equipped_item[2] == equipped_item.data1[2])) { @@ -885,11 +2017,11 @@ const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combinat throw out_of_range("no item combination applies"); } -const std::vector& ItemParameterTable::get_all_combinations_for_used_item( +const std::vector& ItemParameterTable::all_combinations_for_used_item( const ItemData& used_item) const { try { - uint32_t key = (used_item.data1[0] << 16) | (used_item.data1[1] << 8) | used_item.data1[2]; - return this->get_all_item_combinations().at(key); + return this->all_item_combinations().at(item_code_to_u32( + used_item.data1[0], used_item.data1[1], used_item.data1[2])); } catch (const out_of_range&) { static const vector ret; return ret; @@ -906,7 +2038,7 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const { return 80; } - float sale_divisor = this->get_sale_divisor(item.data1[0], item.data1[1]); + float sale_divisor = this->get_sale_divisor(0, item.data1[1]); if (sale_divisor == 0.0) { throw runtime_error("item sale divisor is zero"); } @@ -936,10 +2068,10 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const { } if (item.data1[1] == 3) { // Unit - return this->get_item_adjusted_stars(item) * this->get_sale_divisor(item.data1[0], 3); + return this->get_item_adjusted_stars(item) * this->get_sale_divisor(1, 3); } - double sale_divisor = (double)this->get_sale_divisor(item.data1[0], item.data1[1]); + double sale_divisor = (double)this->get_sale_divisor(1, item.data1[1]); if (sale_divisor == 0.0) { throw runtime_error("item sale divisor is zero"); } @@ -985,13 +2117,17 @@ template < size_t ItemStarsLastID, size_t SpecialStarsBeginIndex, size_t NumSpecials, + size_t NumPhotonColors, + size_t SoundRemapRTTableSize, bool BE> -class ItemParameterTableT : public ItemParameterTable { +class BinaryItemParameterTableT : public ItemParameterTable { public: - explicit ItemParameterTableT(std::shared_ptr data) - : ItemParameterTable(data), + explicit BinaryItemParameterTableT(std::shared_ptr data) + : ItemParameterTable(), + data(data), + r(*this->data), root(&this->r.pget(BE ? r.pget_u32b(r.size() - 0x10) : r.pget_u32l(r.size() - 0x10))) {} - ~ItemParameterTableT() = default; + ~BinaryItemParameterTableT() = default; inline size_t indirect_lookup_2d_count(size_t base_offset, size_t co_index) const { return this->r.pget>(base_offset + sizeof(ArrayRefT) * co_index).count; @@ -1006,6 +2142,23 @@ public: return this->r.pget(co.offset + sizeof(T) * item_index); } + template + const ParsedT& add_to_vector_cache(std::vector& cache, size_t base_offset, size_t index) const { + while (cache.size() <= index) { + cache.emplace_back(this->r.pget(base_offset + sizeof(RawT) * cache.size())); + } + return cache[index]; + } + + template + const ParsedT& add_to_vector_cache_2d_indirect( + std::vector& cache, size_t base_offset, size_t major_index, size_t minor_index) const { + while (cache.size() <= minor_index) { + cache.emplace_back(indirect_lookup_2d(base_offset, major_index, cache.size())); + } + return cache[minor_index]; + } + virtual size_t num_weapon_classes() const { return NumWeaponClasses; } @@ -1042,10 +2195,7 @@ public: throw out_of_range("armor/shield class ID out of range"); } auto& vec = (data1_1 == 1) ? this->armors : this->shields; - while (vec.size() <= data1_2) { - vec.emplace_back(this->indirect_lookup_2d(this->root->armor_table, data1_1 - 1, vec.size())); - } - return vec[data1_2]; + return this->add_to_vector_cache_2d_indirect(vec, this->root->armor_table, data1_1 - 1, data1_2); } virtual size_t num_units() const { @@ -1053,21 +2203,7 @@ public: } virtual const Unit& get_unit(uint8_t data1_2) const { - while (this->units.size() <= data1_2) { - this->units.emplace_back(this->indirect_lookup_2d(this->root->unit_table, 0, this->units.size())); - } - return this->units[data1_2]; - } - - virtual size_t num_mags() const { - return this->indirect_lookup_2d_count(this->root->mag_table, 0); - } - - virtual const Mag& get_mag(uint8_t data1_1) const { - while (this->mags.size() <= data1_1) { - this->mags.emplace_back(this->indirect_lookup_2d(this->root->mag_table, 0, data1_1)); - } - return this->mags[data1_1]; + return this->add_to_vector_cache_2d_indirect(this->units, this->root->unit_table, 0, data1_2); } virtual size_t num_tool_classes() const { @@ -1095,8 +2231,7 @@ public: } virtual std::pair find_tool_by_id(uint32_t id) const { - const auto* cos = &this->r.pget>( - this->root->tool_table, NumToolClasses * sizeof(ArrayRefT)); + const auto* cos = &this->r.pget>(this->root->tool_table, NumToolClasses * sizeof(ArrayRefT)); for (size_t z = 0; z < NumToolClasses; z++) { const auto& co = cos[z]; const auto* defs = &this->r.pget(co.offset, sizeof(ToolT) * co.count); @@ -1109,6 +2244,37 @@ public: throw out_of_range(std::format("invalid tool class {:08X}", id)); } + virtual size_t num_mags() const { + return this->indirect_lookup_2d_count(this->root->mag_table, 0); + } + + virtual const Mag& get_mag(uint8_t data1_1) const { + return this->add_to_vector_cache_2d_indirect(this->mags, this->root->mag_table, 0, data1_1); + } + + virtual uint8_t get_weapon_kind(uint8_t data1_1) const { + return (data1_1 < NumWeaponClasses) ? this->r.pget_u8(this->root->weapon_kind_table + data1_1) : 0x00; + } + + virtual size_t num_photon_colors() const { + return NumPhotonColors; + } + + virtual const PhotonColorEntry& get_photon_color(size_t index) const { + if (index >= NumPhotonColors) { + throw std::out_of_range("invalid photon color index"); + } + return this->add_to_vector_cache>(this->photon_colors, this->root->photon_color_table, index); + } + + virtual size_t num_weapon_ranges() const { + return this->get_data_array_count>(this->root->weapon_range_table); + } + + virtual const WeaponRange& get_weapon_range(size_t index) const { + return this->add_to_vector_cache>(this->weapon_ranges, this->root->weapon_range_table, index); + } + virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const { if (data1_0 == 0) { if (data1_1 >= NumWeaponClasses) { @@ -1164,6 +2330,14 @@ public: return this->r.pget(table_offsets[table_index])[item_index]; } + virtual std::pair get_star_value_index_range() const { + return std::make_pair(ItemStarsFirstID, ItemStarsLastID); + } + + virtual uint32_t get_special_stars_base_index() const { + return SpecialStarsBeginIndex; + } + virtual uint8_t get_item_stars(uint32_t id) const { return ((id >= ItemStarsFirstID) && (id < ItemStarsLastID)) ? this->r.pget_u8(this->root->star_value_table + id - ItemStarsFirstID) @@ -1174,6 +2348,14 @@ public: return ((special & 0x3F) && !(special & 0x80)) ? this->get_item_stars(special + SpecialStarsBeginIndex) : 0; } + virtual std::string get_unknown_a1() const { + if constexpr (requires { this->root->unknown_a1; }) { + return this->r.pread(this->root->unknown_a1, this->get_data_range_size(this->root->unknown_a1)); + } else { + return ""; + } + } + virtual size_t num_specials() const { return NumSpecials; } @@ -1190,12 +2372,84 @@ public: return this->specials[special]; } - virtual const StatBoost& get_stat_boost(uint8_t entry_index) const { - while (this->stat_boosts.size() <= entry_index) { - this->stat_boosts.emplace_back(this->r.pget>( - this->root->stat_boost_table + sizeof(StatBoostT) * this->stat_boosts.size())); + virtual size_t num_weapon_effects() const { + return this->get_data_array_count>(this->root->weapon_effect_table); + } + + virtual const WeaponEffect& get_weapon_effect(size_t index) const { + return this->add_to_vector_cache>(this->weapon_effects, this->root->weapon_effect_table, index); + } + + virtual size_t num_weapon_stat_boost_indexes() const { + if constexpr (requires { this->root->weapon_stat_boost_index_table; }) { + return this->get_data_range_size(this->root->weapon_stat_boost_index_table); + } else { + return 0; + } + } + + virtual uint8_t get_weapon_stat_boost_index(size_t index) const { + if constexpr (requires { this->root->weapon_stat_boost_index_table; }) { + return this->r.pget_u8(this->root->weapon_stat_boost_index_table + index); + } else { + throw std::logic_error("weapon stat boost index table not available"); + } + } + + virtual size_t num_armor_stat_boost_indexes() const { + if constexpr (requires { this->root->armor_stat_boost_index_table; }) { + return this->get_data_range_size(this->root->armor_stat_boost_index_table); + } else { + return 0; + } + } + + virtual uint8_t get_armor_stat_boost_index(size_t index) const { + if constexpr (requires { this->root->armor_stat_boost_index_table; }) { + return this->r.pget_u8(this->root->armor_stat_boost_index_table + index); + } else { + throw std::logic_error("armor stat boost index table not available"); + } + } + + virtual size_t num_shield_stat_boost_indexes() const { + if constexpr (requires { this->root->shield_stat_boost_index_table; }) { + return this->get_data_range_size(this->root->shield_stat_boost_index_table); + } else { + return 0; + } + } + + virtual uint8_t get_shield_stat_boost_index(size_t index) const { + if constexpr (requires { this->root->shield_stat_boost_index_table; }) { + return this->r.pget_u8(this->root->shield_stat_boost_index_table + index); + } else { + throw std::logic_error("shield stat boost index table not available"); + } + } + + virtual size_t num_stat_boosts() const { + return this->get_data_array_count>(this->root->stat_boost_table); + } + + virtual const StatBoost& get_stat_boost(size_t index) const { + return this->add_to_vector_cache>(this->stat_boosts, this->root->stat_boost_table, index); + } + + virtual size_t num_shield_effects() const { + if constexpr (requires { this->root->shield_effect_table; }) { + return this->get_data_array_count>(this->root->shield_effect_table); + } else { + return 0; + } + } + + virtual const ShieldEffect& get_shield_effect(size_t index) const { + if constexpr (requires { this->root->shield_effect_table; }) { + return this->add_to_vector_cache>(this->shield_effects, this->root->shield_effect_table, index); + } else { + throw std::logic_error("shield effect table not available"); } - return this->stat_boosts[entry_index]; } virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const { @@ -1216,38 +2470,60 @@ public: } } - virtual uint8_t get_weapon_class(uint8_t data1_1) const { - return (data1_1 < NumWeaponClasses) ? this->r.pget_u8(this->root->weapon_class_table + data1_1) : 0x00; - } - - virtual bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const { - if constexpr (requires { this->root->unsealable_table; }) { - const auto& co = this->r.pget>(this->root->unsealable_table); - const auto* defs = &this->r.pget(co.offset, co.count * sizeof(UnsealableItem)); - for (size_t z = 0; z < co.count; z++) { - if ((defs[z].item[0] == data1_0) && (defs[z].item[1] == data1_1) && (defs[z].item[2] == data1_2)) { - return true; - } - } - } - return false; - } - - virtual const std::map>& get_all_item_combinations() const { + virtual const std::map>& all_item_combinations() const { if constexpr (requires { this->root->combination_table; }) { - if (this->item_combination_index.empty()) { + if (!this->item_combination_index.has_value()) { + auto& ret = this->item_combination_index.emplace(); const auto& co = this->r.pget>(this->root->combination_table); const auto* defs = &this->r.pget(co.offset, co.count * sizeof(ItemCombination)); for (size_t z = 0; z < co.count; z++) { - const auto& def = defs[z]; - uint32_t key = (def.used_item[0] << 16) | (def.used_item[1] << 8) | def.used_item[2]; - this->item_combination_index[key].emplace_back(def); + ret[item_code_to_u32(defs[z].used_item)].emplace_back(defs[z]); } } - return this->item_combination_index; + return *this->item_combination_index; + } else { + static const std::map> empty_map{}; + return empty_map; + } + } + + virtual const std::unordered_map& get_all_sound_remaps() const { + if constexpr (requires { this->root->sound_remap_table; }) { + if (!this->sound_remaps.has_value()) { + auto& ret = this->sound_remaps.emplace(); + const auto& co = this->r.pget>(this->root->sound_remap_table); + const auto* entries = this->r.pget_array>(co.offset, co.count); + for (size_t z = 0; z < co.count; z++) { + auto& remaps = ret.emplace(entries[z].sound_id, SoundRemaps{}).first->second; + remaps.sound_id = entries[z].sound_id; + auto sub_r = r.sub(entries[z].remaps_for_rt_index_table, SoundRemapRTTableSize * sizeof(U32T)); + for (size_t z = 0; z < SoundRemapRTTableSize; z++) { + remaps.by_rt_index.emplace_back(sub_r.template get>()); + } + sub_r = r.sub(entries[z].remaps_for_char_class_table, 12 * sizeof(U32T)); + for (size_t z = 0; z < 12; z++) { + remaps.by_char_class.emplace_back(sub_r.template get>()); + } + } + } + } + return *this->sound_remaps; + } + + virtual size_t num_tech_boosts() const { + if constexpr (requires { this->root->tech_boost_table; }) { + return this->get_data_array_count>(this->root->tech_boost_table); + } else { + return 0; + } + } + + virtual const TechBoost& get_tech_boost(size_t index) const { + if constexpr (requires { this->root->tech_boost_table; }) { + return this->add_to_vector_cache>(this->tech_boosts, this->root->tech_boost_table, index); + } else { + throw std::logic_error("tech boost table not available"); } - static const std::map> empty_map; - return empty_map; } virtual size_t num_events() const { @@ -1272,11 +2548,108 @@ public: } } + virtual const std::unordered_set& all_unsealable_items() const { + if constexpr (requires { this->root->unsealable_table; }) { + if (!this->unsealable_table.has_value()) { + auto& ret = this->unsealable_table.emplace(); + const auto& co = this->r.pget>(this->root->unsealable_table); + const auto* defs = &this->r.pget(co.offset, co.count * sizeof(UnsealableItem)); + for (size_t z = 0; z < co.count; z++) { + ret.emplace(item_code_to_u32(defs[z].item)); + } + } + return *this->unsealable_table; + } else { + static const std::unordered_set empty_set{}; + return empty_set; + } + } + + virtual size_t num_ranged_specials() const { + if constexpr (requires { this->root->ranged_special_table; }) { + return this->indirect_lookup_2d_count(this->root->ranged_special_table, 0); + } else { + return 0; + } + } + + virtual const RangedSpecial& get_ranged_special(size_t index) const { + if constexpr (requires { this->root->ranged_special_table; }) { + return this->indirect_lookup_2d(this->root->ranged_special_table, 0, index); + } else { + throw std::logic_error("ranged special table not available"); + } + } + + const std::set& all_start_offsets() const { + if (!this->start_offsets.has_value()) { + auto& ret = this->start_offsets.emplace(); + ret.emplace(r.size() - 0x20); // REL footer + ret.emplace(BE ? r.pget_u32b(r.size() - 0x10) : r.pget_u32l(r.size() - 0x10)); // root + ret.emplace(BE ? r.pget_u32b(r.size() - 0x20) : r.pget_u32l(r.size() - 0x20)); // relocations + + const auto& footer = r.pget>(r.size() - sizeof(RELFileFooterT)); + auto sub_r = r.sub(footer.relocations_offset, footer.num_relocations * sizeof(U16T)); + uint32_t offset = 0; + while (!sub_r.eof()) { + offset += sub_r.template get>() * 4; + ret.emplace(r.pget>(offset)); + } + } + return *this->start_offsets; + } + + size_t get_data_range_size(size_t start_offset) const { + const auto& offsets = this->all_start_offsets(); + auto it = offsets.lower_bound(start_offset); + if (it == offsets.end()) { + throw std::out_of_range("start offset out of range"); + } + if (*it == start_offset) { + it++; + } + if (it == offsets.end()) { + throw std::out_of_range("no further offset beyond start offset"); + } + return *it - start_offset; + } + + template + size_t get_data_array_count(size_t start_offset) const { + return this->get_data_range_size(start_offset) / sizeof(T); + } + protected: + std::shared_ptr data; + phosg::StringReader r; const RootT* root; + + mutable std::optional> start_offsets; + + mutable std::unordered_map weapons; + mutable std::vector armors; + mutable std::vector shields; + mutable std::vector units; + mutable std::vector mags; + mutable std::unordered_map tools; + + mutable std::vector specials; + mutable std::vector stat_boosts; + mutable std::vector photon_colors; + mutable std::vector weapon_ranges; + mutable std::vector weapon_effects; + mutable std::vector shield_effects; + mutable std::optional> sound_remaps; + mutable std::vector tech_boosts; + + // Key is used_item. We can't index on (used_item, equipped_item) because equipped_item may contain wildcards, and + // the matching order matters. + mutable std::optional>> item_combination_index; + + mutable std::optional> unsealable_table; }; -using ItemParameterTableDCNTE = ItemParameterTableT< +using ItemParameterTableDCNTE = BinaryItemParameterTableT< RootDCProtos, // typename RootT WeaponDCProtos, // typename WeaponT 0x27, // size_t NumWeaponClasses @@ -1289,8 +2662,10 @@ using ItemParameterTableDCNTE = ItemParameterTableT< 0x168, // size_t ItemStarsLastID 0xAA, // size_t SpecialStarsBeginIndex 0x28, // size_t NumSpecials + 0x14, // size_t NumPhotonColors + 0x00, // size_t SoundRemapRTTableSize false>; // bool BE -using ItemParameterTableDC112000 = ItemParameterTableT< +using ItemParameterTableDC112000 = BinaryItemParameterTableT< RootDCProtos, // typename RootT WeaponDCProtos, // typename WeaponT 0x27, // size_t NumWeaponClasses @@ -1303,9 +2678,11 @@ using ItemParameterTableDC112000 = ItemParameterTableT< 0x16C, // size_t ItemStarsLastID 0xAE, // size_t SpecialStarsBeginIndex 0x28, // size_t NumSpecials + 0x14, // size_t NumPhotonColors + 0x00, // size_t SoundRemapRTTableSize false>; // bool BE -using ItemParameterTableV1 = ItemParameterTableT< - RootV1V2, // typename RootT +using ItemParameterTableV1 = BinaryItemParameterTableT< + RootV1, // typename RootT WeaponV1V2, // typename WeaponT 0x27, // size_t NumWeaponClasses ArmorOrShieldV1V2, // typename ArmorOrShieldT @@ -1317,9 +2694,11 @@ using ItemParameterTableV1 = ItemParameterTableT< 0x16C, // size_t ItemStarsLastID 0xAE, // size_t SpecialStarsBeginIndex 0x29, // size_t NumSpecials + 0x14, // size_t NumPhotonColors + 0x00, // size_t SoundRemapRTTableSize false>; // bool BE -using ItemParameterTableV2 = ItemParameterTableT< - RootV1V2, // typename RootT +using ItemParameterTableV2 = BinaryItemParameterTableT< + RootV2, // typename RootT WeaponV1V2, // typename WeaponT 0x89, // size_t NumWeaponClasses ArmorOrShieldV1V2, // typename ArmorOrShieldT @@ -1331,8 +2710,10 @@ using ItemParameterTableV2 = ItemParameterTableT< 0x215, // size_t ItemStarsLastID 0x138, // size_t SpecialStarsBeginIndex 0x29, // size_t NumSpecials + 0x20, // size_t NumPhotonColors + 0x00, // size_t SoundRemapRTTableSize false>; // bool BE -using ItemParameterTableGCNTE = ItemParameterTableT< +using ItemParameterTableGCNTE = BinaryItemParameterTableT< RootGCNTE, // typename RootT WeaponGCNTE, // typename WeaponT 0x8D, // size_t NumWeaponClasses @@ -1345,8 +2726,10 @@ using ItemParameterTableGCNTE = ItemParameterTableT< 0x298, // size_t ItemStarsLastID 0x1A3, // size_t SpecialStarsBeginIndex 0x29, // size_t NumSpecials + 0x20, // size_t NumPhotonColors + 0x4F, // size_t SoundRemapRTTableSize true>; // bool BE -using ItemParameterTableGC = ItemParameterTableT< +using ItemParameterTableGC = BinaryItemParameterTableT< RootV3V4T, // typename RootT WeaponGC, // typename WeaponT 0xAA, // size_t NumWeaponClasses @@ -1359,8 +2742,10 @@ using ItemParameterTableGC = ItemParameterTableT< 0x2F7, // size_t ItemStarsLastID 0x1CB, // size_t SpecialStarsBeginIndex 0x29, // size_t NumSpecials + 0x20, // size_t NumPhotonColors + 0x58, // size_t SoundRemapRTTableSize true>; // bool BE -using ItemParameterTableXB = ItemParameterTableT< +using ItemParameterTableXB = BinaryItemParameterTableT< RootV3V4T, // typename RootT WeaponXB, // typename WeaponT 0xAA, // size_t NumWeaponClasses @@ -1373,8 +2758,10 @@ using ItemParameterTableXB = ItemParameterTableT< 0x2F7, // size_t ItemStarsLastID 0x1CB, // size_t SpecialStarsBeginIndex 0x29, // size_t NumSpecials + 0x20, // size_t NumPhotonColors + 0x58, // size_t SoundRemapRTTableSize false>; // bool BE -using ItemParameterTableV4 = ItemParameterTableT< +using ItemParameterTableV4 = BinaryItemParameterTableT< RootV3V4T, // typename RootT WeaponV4, // typename WeaponT 0xED, // size_t NumWeaponClasses @@ -1387,9 +2774,11 @@ using ItemParameterTableV4 = ItemParameterTableT< 0x437, // size_t ItemStarsLastID 0x256, // size_t SpecialStarsBeginIndex 0x29, // size_t NumSpecials + 0x20, // size_t NumPhotonColors + 0x6A, // size_t SoundRemapRTTableSize false>; // bool BE -std::shared_ptr ItemParameterTable::create( +std::shared_ptr ItemParameterTable::from_binary( std::shared_ptr data, Version version) { switch (version) { case Version::DC_NTE: @@ -1416,3 +2805,7 @@ std::shared_ptr ItemParameterTable::create( throw std::logic_error("Cannot create item parameter table for this version"); } } + +std::shared_ptr ItemParameterTable::from_json(const phosg::JSON& json) { + return std::make_shared(json); +} diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh index 6c9605a8..47d3e2f0 100644 --- a/src/ItemParameterTable.hh +++ b/src/ItemParameterTable.hh @@ -51,6 +51,9 @@ public: uint16_t type = 0; // "Model" in Soly's ItemPMT editor uint16_t skin = 0; // "Texture" in Soly's ItemPMT editor uint32_t team_points = 0; + + void parse_base_from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct Weapon : ItemBase { uint16_t class_flags = 0; @@ -64,7 +67,7 @@ public: uint8_t photon = 0; uint8_t special = 0; uint8_t ata = 0; - uint8_t stat_boost_entry_index = 0; // TODO: This could be larger (16 or 32 bits) + uint8_t stat_boost_entry_index = 0; uint8_t projectile = 0; int8_t trail1_x = 0; int8_t trail1_y = 0; @@ -74,13 +77,16 @@ public: parray unknown_a1 = 0; uint8_t unknown_a4 = 0; uint8_t unknown_a5 = 0; - uint8_t tech_boost = 0; + uint8_t tech_boost_entry_index = 0; // Bits in behavior_flags: // 01 = disable combos (weapon can only be used once in a row) // 02 = TODO (sets TItemWeapon flag 40000; used in TItemWeapon_v1E) // 04 = TODO (sets TItemWeapon flag 80000; used in TItemWeapon_v1E) // 08 = weapon cannot have attributes (they are ignored if present) uint8_t behavior_flags = 0; + + static Weapon from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct ArmorOrShield : ItemBase { @@ -98,7 +104,7 @@ public: uint8_t dfp_range = 0; uint8_t evp_range = 0; uint8_t stat_boost_entry_index = 0; - uint8_t tech_boost = 0; + uint8_t tech_boost_entry_index = 0; // TODO: Figure out what this does. Only a few values appear to do anything: // Armors: // 01 sets item->flags |= 1 (used in TItemProArmor_v10) @@ -108,12 +114,18 @@ public: // 03 sets item->flags |= 8 (used in TItemProShield_v1A) uint8_t flags_type = 0; uint8_t unknown_a4 = 0; + + static ArmorOrShield from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct Unit : ItemBase { uint16_t stat = 0; uint16_t stat_amount = 0; int16_t modifier_amount = 0; + + static Unit from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct Mag : ItemBase { @@ -142,6 +154,9 @@ public: uint8_t on_death_flag = 0; uint8_t on_boss_flag = 0; uint16_t class_flags = 0x00FF; + + static Mag from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct Tool : ItemBase { @@ -157,7 +172,10 @@ public: // 00000020 - usable in boss arenas // 00000040 - usable in Challenge mode // 00000080 - is rare (renders as red box; V3+ only) - /* 0C */ uint32_t item_flags = 0; + uint32_t item_flags = 0; + + static Tool from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct MagFeedResult { @@ -168,11 +186,17 @@ public: int8_t iq = 0; int8_t synchro = 0; parray unused; + + static MagFeedResult from_json(const phosg::JSON& json); + phosg::JSON json() const; } __packed_ws__(MagFeedResult, 8); struct Special { uint16_t type = 0xFFFF; uint16_t amount = 0; + + static Special from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct StatBoost { @@ -201,6 +225,9 @@ public: uint16_t amount1 = 0; uint8_t stat2 = 0; uint16_t amount2 = 0; + + static StatBoost from_json(const phosg::JSON& json); + phosg::JSON json() const; } __attribute__((packed)); // Indexed as [tech_num][char_class] @@ -215,23 +242,41 @@ public: uint8_t level = 0; uint8_t char_class = 0; parray unused; + + static ItemCombination from_json(const phosg::JSON& json); + phosg::JSON json() const; } __packed_ws__(ItemCombination, 0x10); - struct TechniqueBoost { - uint8_t tech_num = 0; - // It appears that only one bit in the flags field is used: 01 = enable piercing (for Megid) - uint8_t flags = 0; - float amount = 0.0f; + struct TechBoost { + // It appears that only one bit in the flags fields is used: 01 = enable piercing (for Megid) + uint8_t tech_num1 = 0; + uint8_t flags1 = 0; + float amount1 = 0.0f; + uint8_t tech_num2 = 0; + uint8_t flags2 = 0; + float amount2 = 0.0f; + uint8_t tech_num3 = 0; + uint8_t flags3 = 0; + float amount3 = 0.0f; + + static TechBoost from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct EventItem { parray item; uint8_t probability = 0; + + static EventItem from_json(const phosg::JSON& json); + phosg::JSON json() const; } __packed_ws__(EventItem, 4); struct UnsealableItem { parray item; uint8_t unused = 0; + + static UnsealableItem from_json(const phosg::JSON& json); + phosg::JSON json() const; } __packed_ws__(UnsealableItem, 4); struct NonWeaponSaleDivisors { @@ -239,119 +284,207 @@ public: float shield_divisor = 0.0f; float unit_divisor = 0.0f; float mag_divisor = 0.0f; + + static NonWeaponSaleDivisors from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct ShieldEffect { - uint32_t sound_id; - uint32_t unknown_a1; + uint32_t sound_id = 0; + uint32_t unknown_a1 = 0; + + static ShieldEffect from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct PhotonColorEntry { - uint32_t unknown_a1; + uint32_t unknown_a1 = 0; VectorXYZTF unknown_a2; VectorXYZTF unknown_a3; + + static PhotonColorEntry from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct UnknownA1 { - uint16_t unknown_a1; - uint16_t unknown_a2; - }; + uint16_t unknown_a1 = 0; + uint16_t unknown_a2 = 0; - struct UnknownA5 { - uint32_t target_param; // For players, char_class; for enemies, rt_index; for objects, 0x30 - uint32_t unknown_a2; - uint32_t unknown_a3; + static UnknownA1 from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct WeaponEffect { - uint32_t sound_id1; - uint32_t eff_value1; - uint32_t sound_id2; - uint32_t eff_value2; + uint32_t sound_id1 = 0; + uint32_t eff_value1 = 0; + uint32_t sound_id2 = 0; + uint32_t eff_value2 = 0; parray unknown_a5; + + static WeaponEffect from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct WeaponRange { - float unknown_a1; - float unknown_a2; - uint32_t unknown_a3; // Angle - uint32_t unknown_a4; // Angle - uint32_t unknown_a5; + float unknown_a1 = 0; + float unknown_a2 = 0; + uint32_t unknown_a3 = 0; // Angle + uint32_t unknown_a4 = 0; // Angle + uint32_t unknown_a5 = 0; + + static WeaponRange from_json(const phosg::JSON& json); + phosg::JSON json() const; }; struct RangedSpecial { - uint8_t data1_1; - uint8_t data1_2; - uint8_t weapon_range_index; - uint8_t unknown_a1; + uint8_t data1_1 = 0; + uint8_t data1_2 = 0; + uint8_t weapon_range_index = 0; + uint8_t unknown_a1 = 0; + + static RangedSpecial from_json(const phosg::JSON& json); + phosg::JSON json() const; } __packed_ws__(RangedSpecial, 4); - ItemParameterTable() = delete; + struct SoundRemaps { + uint32_t sound_id = 0; + std::vector by_rt_index; + std::vector by_char_class; + + static SoundRemaps from_json(const phosg::JSON& json); + phosg::JSON json() const; + }; + virtual ~ItemParameterTable() = default; - static std::shared_ptr create(std::shared_ptr data, Version version); + static std::shared_ptr from_binary(std::shared_ptr data, Version version); + static std::shared_ptr from_json(const phosg::JSON& json); + + phosg::JSON json() const; + // std::string serialize_binary() const; // TODO std::set compute_all_valid_primary_identifiers() const; + // weapon_table accessors virtual size_t num_weapon_classes() const = 0; virtual size_t num_weapons_in_class(uint8_t data1_1) const = 0; virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const = 0; + + // armor_table accessors virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const = 0; virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const = 0; + + // unit_table accessors virtual size_t num_units() const = 0; virtual const Unit& get_unit(uint8_t data1_2) const = 0; - virtual size_t num_mags() const = 0; - virtual const Mag& get_mag(uint8_t data1_1) const = 0; + + // tool_table accessors virtual size_t num_tool_classes() const = 0; virtual size_t num_tools_in_class(uint8_t data1_1) const = 0; virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const = 0; virtual std::pair find_tool_by_id(uint32_t id) const = 0; - std::variant - definition_for_primary_identifier(uint32_t primary_identifier) const; + // mag_table accessors + virtual size_t num_mags() const = 0; + virtual const Mag& get_mag(uint8_t data1_1) const = 0; + // weapon_kind_table accessors (data1_1 in [0, num_weapon_classes()]) + virtual uint8_t get_weapon_kind(uint8_t data1_1) const = 0; + + // photon_color_table accessors + virtual size_t num_photon_colors() const = 0; + virtual const PhotonColorEntry& get_photon_color(size_t index) const = 0; + + // weapon_range_table accessors + virtual size_t num_weapon_ranges() const = 0; + virtual const WeaponRange& get_weapon_range(size_t index) const = 0; + + // weapon_sale_divisor_table and non_weapon_sale_divisor_table accessors (data1_0 in [0, 1, 2]; data1_1 in [0, + // num_weapon_classes()] for weapons or ignored otherwise) virtual float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const = 0; - virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const = 0; + + // mag_feed_table accessors (table_index in [0, 7], item_index in [0, 10]) + virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const = 0; + + // star_value_table accessors + virtual std::pair get_star_value_index_range() const = 0; + virtual uint32_t get_special_stars_base_index() const = 0; virtual uint8_t get_item_stars(uint32_t id) const = 0; virtual uint8_t get_special_stars(uint8_t special) const = 0; + std::string get_star_value_table() const; + + // unknown_a1 accessors + virtual std::string get_unknown_a1() const = 0; + + // special_table accessors virtual size_t num_specials() const = 0; virtual const Special& get_special(uint8_t special) const = 0; - virtual const StatBoost& get_stat_boost(uint8_t entry_index) const = 0; - virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const = 0; - virtual uint8_t get_weapon_class(uint8_t data1_1) const = 0; + // weapon_effect_table accessors + virtual size_t num_weapon_effects() const = 0; + virtual const WeaponEffect& get_weapon_effect(size_t index) const = 0; + + // weapon_stat_boost_index_table accessors + virtual size_t num_weapon_stat_boost_indexes() const = 0; + virtual uint8_t get_weapon_stat_boost_index(size_t index) const = 0; + std::string get_weapon_stat_boost_index_table() const; + + // armor_stat_boost_index_table accessors + virtual size_t num_armor_stat_boost_indexes() const = 0; + virtual uint8_t get_armor_stat_boost_index(size_t index) const = 0; + std::string get_armor_stat_boost_index_table() const; + + // shield_stat_boost_index_table accessors + virtual size_t num_shield_stat_boost_indexes() const = 0; + virtual uint8_t get_shield_stat_boost_index(size_t index) const = 0; + std::string get_shield_stat_boost_index_table() const; + + // stat_boost_table accessors + virtual size_t num_stat_boosts() const = 0; + virtual const StatBoost& get_stat_boost(size_t index) const = 0; + + // shield_effect_table accessors + virtual size_t num_shield_effects() const = 0; + virtual const ShieldEffect& get_shield_effect(size_t index) const = 0; + + // max_tech_level_table accessors + virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const = 0; + + // combination_table accessors + virtual const std::map>& all_item_combinations() const = 0; + const std::vector& all_combinations_for_used_item(const ItemData& used_item) const; + const ItemCombination& get_item_combination(const ItemData& used_item, const ItemData& equipped_item) const; + + // sound_remap_table accessors + virtual const std::unordered_map& get_all_sound_remaps() const = 0; + + // tech_boost_table accessors + virtual size_t num_tech_boosts() const = 0; + virtual const TechBoost& get_tech_boost(size_t index) const = 0; + + // unwrap_table accessors + virtual size_t num_events() const = 0; + virtual std::pair get_event_items(uint8_t event_number) const = 0; + + // unsealable_table accessors + virtual const std::unordered_set& all_unsealable_items() const = 0; + bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const; + bool is_unsealable_item(const ItemData& item) const; + + // ranged_special_table accessors + virtual size_t num_ranged_specials() const = 0; + virtual const RangedSpecial& get_ranged_special(size_t index) const = 0; + + // Composite accessors + std::variant + definition_for_primary_identifier(uint32_t primary_identifier) const; uint32_t get_item_id(const ItemData& item) const; uint32_t get_item_team_points(const ItemData& item) const; uint8_t get_item_base_stars(const ItemData& item) const; uint8_t get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified = false) const; bool is_item_rare(const ItemData& item) const; - virtual bool is_unsealable_item(uint8_t data1_0, uint8_t data1_1, uint8_t data1_2) const = 0; - bool is_unsealable_item(const ItemData& item) const; - const ItemCombination& get_item_combination(const ItemData& used_item, const ItemData& equipped_item) const; - const std::vector& get_all_combinations_for_used_item(const ItemData& used_item) const; - virtual const std::map>& get_all_item_combinations() const = 0; - virtual size_t num_events() const = 0; - virtual std::pair get_event_items(uint8_t event_number) const = 0; - size_t price_for_item(const ItemData& item) const; protected: - std::shared_ptr data; - phosg::StringReader r; - - mutable std::unordered_map weapons; - mutable std::vector armors; - mutable std::vector shields; - mutable std::vector units; - mutable std::vector mags; - mutable std::unordered_map tools; - - mutable std::vector specials; - mutable std::vector stat_boosts; - - // Key is used_item. We can't index on (used_item, equipped_item) because equipped_item may contain wildcards, and - // the matching order matters. - mutable std::map> item_combination_index; - - explicit ItemParameterTable(std::shared_ptr data); + ItemParameterTable() = default; }; diff --git a/src/Main.cc b/src/Main.cc index c187ae5d..df2c8560 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -2387,6 +2387,18 @@ Action a_compare_common_item_set( cs1->print_diff(stdout, *cs2); }); +Action a_convert_item_parameter_table( + "decode-item-parameter-table", nullptr, + +[](phosg::Arguments& args) { + auto data = std::make_shared(read_input_data(args)); + auto pmt = ItemParameterTable::from_binary(data, get_cli_version(args, Version::BB_V4)); + auto json = pmt->json(); + uint32_t hex_option = args.get("hex") ? phosg::JSON::SerializeOption::HEX_INTEGERS : 0; + string json_data = json.serialize( + phosg::JSON::SerializeOption::FORMAT | hex_option | phosg::JSON::SerializeOption::SORT_DICT_KEYS); + write_output_data(args, json_data.data(), json_data.size(), nullptr); + }); + Action a_describe_item( "describe-item", "\ describe-item DATA-OR-DESCRIPTION\n\ diff --git a/src/ServerState.cc b/src/ServerState.cc index 1f6d8b71..2740808a 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -2151,7 +2151,7 @@ void ServerState::load_item_definitions() { string path = std::format("system/item-tables/ItemPMT-{}.prs", file_path_token_for_version(v)); config_log.debug_f("Loading item definition table {}", path); auto data = make_shared(prs_decompress(phosg::load_file(path))); - new_item_parameter_tables[v_s] = ItemParameterTable::create(data, v); + new_item_parameter_tables[v_s] = ItemParameterTable::from_binary(data, v); } auto json = phosg::JSON::parse(phosg::load_file("system/item-tables/translation-table.json")); diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index 5d23fc28..486022c2 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -478,8 +478,8 @@ Language language_for_name(const string& name) { } const vector tech_id_to_name = { - "foie", "gifoie", "rafoie", "barta", "gibarta", "rabarta", "zonde", "gizonde", "razonde", "grants", "deband", - "jellen", "zalure", "shifta", "ryuker", "resta", "anti", "reverser", "megid"}; + "Foie", "Gifoie", "Rafoie", "Barta", "Gibarta", "Rabarta", "Zonde", "Gizonde", "Razonde", "Grants", "Deband", + "Jellen", "Zalure", "Shifta", "Ryuker", "Resta", "Anti", "Reverser", "Megid"}; const unordered_map name_to_tech_id = { {"foie", 0}, {"gifoie", 1}, {"rafoie", 2}, {"barta", 3}, {"gibarta", 4}, {"rabarta", 5}, {"zonde", 6}, @@ -497,7 +497,7 @@ const string& name_for_technique(uint8_t tech) { uint8_t technique_for_name(const string& name) { try { - return name_to_tech_id.at(name); + return name_to_tech_id.at(phosg::tolower(name)); } catch (const out_of_range&) { } try {