add JSON encoding for ItemPMT

This commit is contained in:
Martin Michelsen
2026-05-03 20:58:34 -07:00
parent e19c0b8bc9
commit 681ce135f8
8 changed files with 2024 additions and 299 deletions
+4 -1
View File
@@ -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. * 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: 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/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/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/DCSerialNumbers.hh/cc**: PSO DC serial number validation algorithm and serial number generator
* **src/ItemData.hh**: Item format reference * **src/ItemData.hh**: Item format reference
* **src/ItemCreator.hh/cc**: Reverse-engineered item generator from Episodes 1&2 (used for all versions) * **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/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/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/RareItemSet.hh/cc**: Format of ItemRT files (rare item drop tables)
* **src/SaveFileFormats.hh**: Definitions of save file structures for all versions * **src/SaveFileFormats.hh**: Definitions of save file structures for all versions
+1 -1
View File
@@ -486,7 +486,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
if (should_encode_v2_data && (this->data1[1] > 0x26)) { if (should_encode_v2_data && (this->data1[1] > 0x26)) {
if (this->data1[1] < 0x89) { if (this->data1[1] < 0x89) {
this->data1[5] = this->data1[1]; 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) { if (this->data1[1] == 0x00) {
this->data1[1] = 0x0F; this->data1[1] = 0x0F;
} }
+195 -11
View File
@@ -1,5 +1,7 @@
#include "ItemNameIndex.hh" #include "ItemNameIndex.hh"
#include <algorithm>
#include "StaticGameData.hh" #include "StaticGameData.hh"
using namespace std; using namespace std;
@@ -787,9 +789,9 @@ void ItemNameIndex::print_table(FILE* stream) const {
auto pmt = this->item_parameter_table; auto pmt = this->item_parameter_table;
phosg::fwrite_fmt(stream, "WEAPONS\n"); 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++) { 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); float sale_divisor = pmt->get_sale_divisor(0x00, data1_1);
string divisor_str = std::format("{:g}", sale_divisor); string divisor_str = std::format("{:g}", sale_divisor);
divisor_str.resize(13, ' '); divisor_str.resize(13, ' ');
@@ -806,8 +808,24 @@ void ItemNameIndex::print_table(FILE* stream) const {
item.data1[2] = data1_2; item.data1[2] = data1_2;
string name = this->describe_item(item); string name = this->describe_item(item);
auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index); const 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",
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_1,
data1_2, data1_2,
w.id, w.id,
@@ -841,7 +859,8 @@ void ItemNameIndex::print_table(FILE* stream) const {
w.unknown_a1[2], w.unknown_a1[2],
w.unknown_a4, w.unknown_a4,
w.unknown_a5, w.unknown_a5,
w.tech_boost, w.tech_boost_entry_index,
tech_boost_str,
w.behavior_flags, w.behavior_flags,
weapon_class, weapon_class,
stars, stars,
@@ -852,7 +871,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
} }
phosg::fwrite_fmt(stream, "ARMORS\n"); 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++) { for (size_t data1_1 = 1; data1_1 < 3; data1_1++) {
float sale_divisor = pmt->get_sale_divisor(0x01, data1_1); float sale_divisor = pmt->get_sale_divisor(0x01, data1_1);
string divisor_str = std::format("{:g}", sale_divisor); 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); string name = this->describe_item(item);
auto& stat_boost = pmt->get_stat_boost(a.stat_boost_entry_index); 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_1,
data1_2, data1_2,
a.id, a.id,
@@ -895,7 +924,8 @@ void ItemNameIndex::print_table(FILE* stream) const {
stat_boost.amount1, stat_boost.amount1,
stat_boost.stat2, stat_boost.stat2,
stat_boost.amount2, stat_boost.amount2,
a.tech_boost, a.tech_boost_entry_index,
tech_boost_str,
a.flags_type, a.flags_type,
a.unknown_a4, a.unknown_a4,
stars, stars,
@@ -1040,7 +1070,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
} }
phosg::fwrite_fmt(stream, "SPECIAL DEFINITIONS\n"); 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++) { for (size_t index = 0; index < pmt->num_specials(); index++) {
const auto& sp = pmt->get_special(index); const auto& sp = pmt->get_special(index);
uint8_t stars = pmt->get_special_stars(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&) { } 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, "ITEM COMBINATIONS\n");
phosg::fwrite_fmt(stream, " ---USE + -EQUIP => RESULT MLV GND LVL CLS\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) { for (const auto& combo : combo_list_it.second) {
phosg::fwrite_fmt(stream, " {:02X}{:02X}{:02X} + {:02X}{:02X}{:02X} => {:02X}{:02X}{:02X}", 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], 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); 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<const char*, 0x10> 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<uint32_t> 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);
}
} }
+1607 -214
View File
File diff suppressed because it is too large Load Diff
+201 -68
View File
@@ -51,6 +51,9 @@ public:
uint16_t type = 0; // "Model" in Soly's ItemPMT editor uint16_t type = 0; // "Model" in Soly's ItemPMT editor
uint16_t skin = 0; // "Texture" in Soly's ItemPMT editor uint16_t skin = 0; // "Texture" in Soly's ItemPMT editor
uint32_t team_points = 0; uint32_t team_points = 0;
void parse_base_from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct Weapon : ItemBase { struct Weapon : ItemBase {
uint16_t class_flags = 0; uint16_t class_flags = 0;
@@ -64,7 +67,7 @@ public:
uint8_t photon = 0; uint8_t photon = 0;
uint8_t special = 0; uint8_t special = 0;
uint8_t ata = 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; uint8_t projectile = 0;
int8_t trail1_x = 0; int8_t trail1_x = 0;
int8_t trail1_y = 0; int8_t trail1_y = 0;
@@ -74,13 +77,16 @@ public:
parray<uint8_t, 3> unknown_a1 = 0; parray<uint8_t, 3> unknown_a1 = 0;
uint8_t unknown_a4 = 0; uint8_t unknown_a4 = 0;
uint8_t unknown_a5 = 0; uint8_t unknown_a5 = 0;
uint8_t tech_boost = 0; uint8_t tech_boost_entry_index = 0;
// Bits in behavior_flags: // Bits in behavior_flags:
// 01 = disable combos (weapon can only be used once in a row) // 01 = disable combos (weapon can only be used once in a row)
// 02 = TODO (sets TItemWeapon flag 40000; used in TItemWeapon_v1E) // 02 = TODO (sets TItemWeapon flag 40000; used in TItemWeapon_v1E)
// 04 = TODO (sets TItemWeapon flag 80000; used in TItemWeapon_v1E) // 04 = TODO (sets TItemWeapon flag 80000; used in TItemWeapon_v1E)
// 08 = weapon cannot have attributes (they are ignored if present) // 08 = weapon cannot have attributes (they are ignored if present)
uint8_t behavior_flags = 0; uint8_t behavior_flags = 0;
static Weapon from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct ArmorOrShield : ItemBase { struct ArmorOrShield : ItemBase {
@@ -98,7 +104,7 @@ public:
uint8_t dfp_range = 0; uint8_t dfp_range = 0;
uint8_t evp_range = 0; uint8_t evp_range = 0;
uint8_t stat_boost_entry_index = 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: // TODO: Figure out what this does. Only a few values appear to do anything:
// Armors: // Armors:
// 01 sets item->flags |= 1 (used in TItemProArmor_v10) // 01 sets item->flags |= 1 (used in TItemProArmor_v10)
@@ -108,12 +114,18 @@ public:
// 03 sets item->flags |= 8 (used in TItemProShield_v1A) // 03 sets item->flags |= 8 (used in TItemProShield_v1A)
uint8_t flags_type = 0; uint8_t flags_type = 0;
uint8_t unknown_a4 = 0; uint8_t unknown_a4 = 0;
static ArmorOrShield from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct Unit : ItemBase { struct Unit : ItemBase {
uint16_t stat = 0; uint16_t stat = 0;
uint16_t stat_amount = 0; uint16_t stat_amount = 0;
int16_t modifier_amount = 0; int16_t modifier_amount = 0;
static Unit from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct Mag : ItemBase { struct Mag : ItemBase {
@@ -142,6 +154,9 @@ public:
uint8_t on_death_flag = 0; uint8_t on_death_flag = 0;
uint8_t on_boss_flag = 0; uint8_t on_boss_flag = 0;
uint16_t class_flags = 0x00FF; uint16_t class_flags = 0x00FF;
static Mag from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct Tool : ItemBase { struct Tool : ItemBase {
@@ -157,7 +172,10 @@ public:
// 00000020 - usable in boss arenas // 00000020 - usable in boss arenas
// 00000040 - usable in Challenge mode // 00000040 - usable in Challenge mode
// 00000080 - is rare (renders as red box; V3+ only) // 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 { struct MagFeedResult {
@@ -168,11 +186,17 @@ public:
int8_t iq = 0; int8_t iq = 0;
int8_t synchro = 0; int8_t synchro = 0;
parray<uint8_t, 2> unused; parray<uint8_t, 2> unused;
static MagFeedResult from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(MagFeedResult, 8); } __packed_ws__(MagFeedResult, 8);
struct Special { struct Special {
uint16_t type = 0xFFFF; uint16_t type = 0xFFFF;
uint16_t amount = 0; uint16_t amount = 0;
static Special from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct StatBoost { struct StatBoost {
@@ -201,6 +225,9 @@ public:
uint16_t amount1 = 0; uint16_t amount1 = 0;
uint8_t stat2 = 0; uint8_t stat2 = 0;
uint16_t amount2 = 0; uint16_t amount2 = 0;
static StatBoost from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __attribute__((packed)); } __attribute__((packed));
// Indexed as [tech_num][char_class] // Indexed as [tech_num][char_class]
@@ -215,23 +242,41 @@ public:
uint8_t level = 0; uint8_t level = 0;
uint8_t char_class = 0; uint8_t char_class = 0;
parray<uint8_t, 3> unused; parray<uint8_t, 3> unused;
static ItemCombination from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(ItemCombination, 0x10); } __packed_ws__(ItemCombination, 0x10);
struct TechniqueBoost { struct TechBoost {
uint8_t tech_num = 0; // It appears that only one bit in the flags fields is used: 01 = enable piercing (for Megid)
// It appears that only one bit in the flags field is used: 01 = enable piercing (for Megid) uint8_t tech_num1 = 0;
uint8_t flags = 0; uint8_t flags1 = 0;
float amount = 0.0f; 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 { struct EventItem {
parray<uint8_t, 3> item; parray<uint8_t, 3> item;
uint8_t probability = 0; uint8_t probability = 0;
static EventItem from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(EventItem, 4); } __packed_ws__(EventItem, 4);
struct UnsealableItem { struct UnsealableItem {
parray<uint8_t, 3> item; parray<uint8_t, 3> item;
uint8_t unused = 0; uint8_t unused = 0;
static UnsealableItem from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(UnsealableItem, 4); } __packed_ws__(UnsealableItem, 4);
struct NonWeaponSaleDivisors { struct NonWeaponSaleDivisors {
@@ -239,119 +284,207 @@ public:
float shield_divisor = 0.0f; float shield_divisor = 0.0f;
float unit_divisor = 0.0f; float unit_divisor = 0.0f;
float mag_divisor = 0.0f; float mag_divisor = 0.0f;
static NonWeaponSaleDivisors from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct ShieldEffect { struct ShieldEffect {
uint32_t sound_id; uint32_t sound_id = 0;
uint32_t unknown_a1; uint32_t unknown_a1 = 0;
static ShieldEffect from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct PhotonColorEntry { struct PhotonColorEntry {
uint32_t unknown_a1; uint32_t unknown_a1 = 0;
VectorXYZTF unknown_a2; VectorXYZTF unknown_a2;
VectorXYZTF unknown_a3; VectorXYZTF unknown_a3;
static PhotonColorEntry from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct UnknownA1 { struct UnknownA1 {
uint16_t unknown_a1; uint16_t unknown_a1 = 0;
uint16_t unknown_a2; uint16_t unknown_a2 = 0;
};
struct UnknownA5 { static UnknownA1 from_json(const phosg::JSON& json);
uint32_t target_param; // For players, char_class; for enemies, rt_index; for objects, 0x30 phosg::JSON json() const;
uint32_t unknown_a2;
uint32_t unknown_a3;
}; };
struct WeaponEffect { struct WeaponEffect {
uint32_t sound_id1; uint32_t sound_id1 = 0;
uint32_t eff_value1; uint32_t eff_value1 = 0;
uint32_t sound_id2; uint32_t sound_id2 = 0;
uint32_t eff_value2; uint32_t eff_value2 = 0;
parray<uint8_t, 0x10> unknown_a5; parray<uint8_t, 0x10> unknown_a5;
static WeaponEffect from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct WeaponRange { struct WeaponRange {
float unknown_a1; float unknown_a1 = 0;
float unknown_a2; float unknown_a2 = 0;
uint32_t unknown_a3; // Angle uint32_t unknown_a3 = 0; // Angle
uint32_t unknown_a4; // Angle uint32_t unknown_a4 = 0; // Angle
uint32_t unknown_a5; uint32_t unknown_a5 = 0;
static WeaponRange from_json(const phosg::JSON& json);
phosg::JSON json() const;
}; };
struct RangedSpecial { struct RangedSpecial {
uint8_t data1_1; uint8_t data1_1 = 0;
uint8_t data1_2; uint8_t data1_2 = 0;
uint8_t weapon_range_index; uint8_t weapon_range_index = 0;
uint8_t unknown_a1; uint8_t unknown_a1 = 0;
static RangedSpecial from_json(const phosg::JSON& json);
phosg::JSON json() const;
} __packed_ws__(RangedSpecial, 4); } __packed_ws__(RangedSpecial, 4);
ItemParameterTable() = delete; struct SoundRemaps {
uint32_t sound_id = 0;
std::vector<uint32_t> by_rt_index;
std::vector<uint32_t> by_char_class;
static SoundRemaps from_json(const phosg::JSON& json);
phosg::JSON json() const;
};
virtual ~ItemParameterTable() = default; virtual ~ItemParameterTable() = default;
static std::shared_ptr<ItemParameterTable> create(std::shared_ptr<const std::string> data, Version version); static std::shared_ptr<ItemParameterTable> from_binary(std::shared_ptr<const std::string> data, Version version);
static std::shared_ptr<ItemParameterTable> from_json(const phosg::JSON& json);
phosg::JSON json() const;
// std::string serialize_binary() const; // TODO
std::set<uint32_t> compute_all_valid_primary_identifiers() const; std::set<uint32_t> compute_all_valid_primary_identifiers() const;
// weapon_table accessors
virtual size_t num_weapon_classes() const = 0; virtual size_t num_weapon_classes() const = 0;
virtual size_t num_weapons_in_class(uint8_t data1_1) 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; 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 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; 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 size_t num_units() const = 0;
virtual const Unit& get_unit(uint8_t data1_2) 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_tool_classes() const = 0;
virtual size_t num_tools_in_class(uint8_t data1_1) 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 const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const = 0;
virtual std::pair<uint8_t, uint8_t> find_tool_by_id(uint32_t id) const = 0; virtual std::pair<uint8_t, uint8_t> find_tool_by_id(uint32_t id) const = 0;
std::variant<const Weapon*, const ArmorOrShield*, const Unit*, const Mag*, const Tool*> // mag_table accessors
definition_for_primary_identifier(uint32_t primary_identifier) const; 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 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<uint32_t, uint32_t> 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_item_stars(uint32_t id) const = 0;
virtual uint8_t get_special_stars(uint8_t special) 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 size_t num_specials() const = 0;
virtual const Special& get_special(uint8_t special) 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<uint32_t, std::vector<ItemCombination>>& all_item_combinations() const = 0;
const std::vector<ItemCombination>& 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<uint32_t, SoundRemaps>& 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<const EventItem*, size_t> get_event_items(uint8_t event_number) const = 0;
// unsealable_table accessors
virtual const std::unordered_set<uint32_t>& 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<const Weapon*, const ArmorOrShield*, const Unit*, const Mag*, const Tool*>
definition_for_primary_identifier(uint32_t primary_identifier) const;
uint32_t get_item_id(const ItemData& item) const; uint32_t get_item_id(const ItemData& item) const;
uint32_t get_item_team_points(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_base_stars(const ItemData& item) const;
uint8_t get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified = false) const; uint8_t get_item_adjusted_stars(const ItemData& item, bool ignore_unidentified = false) const;
bool is_item_rare(const ItemData& item) 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<ItemCombination>& get_all_combinations_for_used_item(const ItemData& used_item) const;
virtual const std::map<uint32_t, std::vector<ItemCombination>>& get_all_item_combinations() const = 0;
virtual size_t num_events() const = 0;
virtual std::pair<const EventItem*, size_t> get_event_items(uint8_t event_number) const = 0;
size_t price_for_item(const ItemData& item) const; size_t price_for_item(const ItemData& item) const;
protected: protected:
std::shared_ptr<const std::string> data; ItemParameterTable() = default;
phosg::StringReader r;
mutable std::unordered_map<uint16_t, Weapon> weapons;
mutable std::vector<ArmorOrShield> armors;
mutable std::vector<ArmorOrShield> shields;
mutable std::vector<Unit> units;
mutable std::vector<Mag> mags;
mutable std::unordered_map<uint16_t, Tool> tools;
mutable std::vector<Special> specials;
mutable std::vector<StatBoost> 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<uint32_t, std::vector<ItemCombination>> item_combination_index;
explicit ItemParameterTable(std::shared_ptr<const std::string> data);
}; };
+12
View File
@@ -2387,6 +2387,18 @@ Action a_compare_common_item_set(
cs1->print_diff(stdout, *cs2); cs1->print_diff(stdout, *cs2);
}); });
Action a_convert_item_parameter_table(
"decode-item-parameter-table", nullptr,
+[](phosg::Arguments& args) {
auto data = std::make_shared<string>(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<bool>("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( Action a_describe_item(
"describe-item", "\ "describe-item", "\
describe-item DATA-OR-DESCRIPTION\n\ describe-item DATA-OR-DESCRIPTION\n\
+1 -1
View File
@@ -2151,7 +2151,7 @@ void ServerState::load_item_definitions() {
string path = std::format("system/item-tables/ItemPMT-{}.prs", file_path_token_for_version(v)); string path = std::format("system/item-tables/ItemPMT-{}.prs", file_path_token_for_version(v));
config_log.debug_f("Loading item definition table {}", path); config_log.debug_f("Loading item definition table {}", path);
auto data = make_shared<string>(prs_decompress(phosg::load_file(path))); auto data = make_shared<string>(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")); auto json = phosg::JSON::parse(phosg::load_file("system/item-tables/translation-table.json"));
+3 -3
View File
@@ -478,8 +478,8 @@ Language language_for_name(const string& name) {
} }
const vector<string> tech_id_to_name = { const vector<string> tech_id_to_name = {
"foie", "gifoie", "rafoie", "barta", "gibarta", "rabarta", "zonde", "gizonde", "razonde", "grants", "deband", "Foie", "Gifoie", "Rafoie", "Barta", "Gibarta", "Rabarta", "Zonde", "Gizonde", "Razonde", "Grants", "Deband",
"jellen", "zalure", "shifta", "ryuker", "resta", "anti", "reverser", "megid"}; "Jellen", "Zalure", "Shifta", "Ryuker", "Resta", "Anti", "Reverser", "Megid"};
const unordered_map<string, uint8_t> name_to_tech_id = { const unordered_map<string, uint8_t> name_to_tech_id = {
{"foie", 0}, {"gifoie", 1}, {"rafoie", 2}, {"barta", 3}, {"gibarta", 4}, {"rabarta", 5}, {"zonde", 6}, {"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) { uint8_t technique_for_name(const string& name) {
try { try {
return name_to_tech_id.at(name); return name_to_tech_id.at(phosg::tolower(name));
} catch (const out_of_range&) { } catch (const out_of_range&) {
} }
try { try {