support v2 and v3 ItemPMT files

This commit is contained in:
Martin Michelsen
2023-10-27 21:08:04 -07:00
parent 7651922dc9
commit 1c2786ef43
26 changed files with 5106 additions and 1943 deletions
+1
View File
@@ -73,6 +73,7 @@ add_executable(newserv
src/IPStackSimulator.cc
src/ItemCreator.cc
src/ItemData.cc
src/ItemNameIndex.cc
src/ItemParameterTable.cc
src/Items.cc
src/LevelTable.cc
+7 -6
View File
@@ -1148,8 +1148,9 @@ static void server_command_what(shared_ptr<Client> c, const std::string&) {
if (nearest_item_id == 0xFFFFFFFF) {
send_text_message(c, "$C4No items are near you");
} else {
auto s = c->require_server_state();
const auto& item = l->item_id_to_floor_item.at(nearest_item_id);
string name = item.data.name(true);
string name = s->describe_item(c->version(), item.data, true);
send_text_message(c, name);
}
}
@@ -1264,13 +1265,13 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
check_is_game(l, true);
check_cheats_enabled(l);
ItemData item(args);
ItemData item = s->item_name_index->parse_item_description(c->version(), args);
item.id = l->generate_item_id(c->lobby_client_id);
l->add_item(item, c->area, c->x, c->z);
send_drop_stacked_item(l, item, c->area, c->x, c->z);
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message(c, "$C7Item created:\n" + name);
}
@@ -1292,20 +1293,20 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
bool set_drop = (!args.empty() && (args[0] == '!'));
ItemData item(set_drop ? args.substr(1) : args);
ItemData item = s->item_name_index->parse_item_description(ses->version(), (set_drop ? args.substr(1) : args));
item.id = random_object<uint32_t>();
if (set_drop) {
ses->next_drop_item = item;
string name = ses->next_drop_item.name(true);
string name = s->describe_item(ses->version(), item, true);
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
} else {
send_drop_stacked_item(ses->client_channel, item, ses->area, ses->x, ses->z);
send_drop_stacked_item(ses->server_channel, item, ses->area, ses->x, ses->z);
string name = item.name(true);
string name = s->describe_item(ses->version(), item, true);
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
}
}
+2 -2
View File
@@ -614,8 +614,8 @@ uint8_t ItemCreator::generate_tech_disk_level(uint32_t tech_num, uint32_t area_n
return range.min;
}
void ItemCreator::generate_common_tool_type(uint8_t tool_class, ItemData& item) const {
auto data = this->item_parameter_table->find_tool_by_class(tool_class);
void ItemCreator::generate_common_tool_type(uint8_t id, ItemData& item) const {
auto data = this->item_parameter_table->find_tool_by_id(id);
item.data1[0] = 0x03;
item.data1[1] = data.first;
item.data1[2] = data.second;
+2 -1567
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -101,19 +101,15 @@ struct ItemData { // 0x14 bytes
} __attribute__((packed));
ItemData();
explicit ItemData(const std::string& orig_description, bool allow_raw_data = true);
ItemData(const ItemData& other);
ItemData& operator=(const ItemData& other);
void parse(const std::string& desc, bool skip_specials);
bool operator==(const ItemData& other) const;
bool operator!=(const ItemData& other) const;
void clear();
std::string hex() const;
std::string name(bool include_color_codes) const;
uint32_t primary_identifier() const;
bool is_wrapped() const;
+673
View File
@@ -0,0 +1,673 @@
#include "ItemNameIndex.hh"
#include "StaticGameData.hh"
using namespace std;
ItemNameIndex::ItemNameIndex(JSON&& v2_names, JSON&& v3_names, JSON&& v4_names) {
auto get_or_create_meta = [&](uint32_t primary_identifier) {
shared_ptr<ItemMetadata> meta;
try {
return this->primary_identifier_index.at(primary_identifier);
} catch (const out_of_range&) {
auto meta = make_shared<ItemMetadata>();
meta->primary_identifier = primary_identifier;
this->primary_identifier_index.emplace(primary_identifier, meta);
return meta;
}
};
for (const auto& it : v2_names.as_dict()) {
uint32_t primary_identifier = stoul(it.first, nullptr, 16);
auto meta = get_or_create_meta(primary_identifier);
meta->v2_name = std::move(it.second->as_string());
this->v2_name_index.emplace(tolower(meta->v2_name), meta);
}
for (const auto& it : v3_names.as_dict()) {
uint32_t primary_identifier = stoul(it.first, nullptr, 16);
auto meta = get_or_create_meta(primary_identifier);
meta->v3_name = std::move(it.second->as_string());
this->v3_name_index.emplace(tolower(meta->v3_name), meta);
}
for (const auto& it : v4_names.as_dict()) {
uint32_t primary_identifier = stoul(it.first, nullptr, 16);
auto meta = get_or_create_meta(primary_identifier);
meta->v4_name = std::move(it.second->as_string());
this->v4_name_index.emplace(tolower(meta->v4_name), meta);
}
}
static const char* s_rank_name_characters = "\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
static constexpr array<const char*, 0x29> name_for_weapon_special = {
nullptr,
"Draw",
"Drain",
"Fill",
"Gush",
"Heart",
"Mind",
"Soul",
"Geist",
"Master\'s",
"Lord\'s",
"King\'s",
"Charge",
"Spirit",
"Berserk",
"Ice",
"Frost",
"Freeze",
"Blizzard",
"Bind",
"Hold",
"Seize",
"Arrest",
"Heat",
"Fire",
"Flame",
"Burning",
"Shock",
"Thunder",
"Storm",
"Tempest",
"Dim",
"Shadow",
"Dark",
"Hell",
"Panic",
"Riot",
"Havoc",
"Chaos",
"Devil\'s",
"Demon\'s",
};
const array<const char*, 0x11> name_for_s_rank_special = {
nullptr,
"Jellen",
"Zalure",
"HP-Revival",
"TP-Revival",
"Burning",
"Tempest",
"Blizzard",
"Arrest",
"Chaos",
"Hell",
"Spirit",
"Berserk",
"Demon\'s",
"Gush",
"Geist",
"King\'s",
};
std::string ItemNameIndex::describe_item(
GameVersion version,
const ItemData& item,
std::shared_ptr<const ItemParameterTable> item_parameter_table) const {
if (item.data1[0] == 0x04) {
return string_printf("%s%" PRIu32 " Meseta", item_parameter_table ? "$C7" : "", item.data2d.load());
}
vector<string> ret_tokens;
// For weapons, specials appear before the weapon name
if ((item.data1[0] == 0x00) && (item.data1[4] != 0x00) && !item.is_s_rank_weapon()) {
// 0x80 is the unidentified flag, but we always return the identified name
// of the item here, so we ignore it
bool is_present = item.data1[4] & 0x40;
uint8_t special_id = item.data1[4] & 0x3F;
if (is_present) {
ret_tokens.emplace_back("Wrapped");
}
if (special_id) {
try {
ret_tokens.emplace_back(name_for_weapon_special.at(special_id));
} catch (const out_of_range&) {
ret_tokens.emplace_back(string_printf("!SP:%02hhX", special_id));
}
}
}
if ((item.data1[0] == 0x00) && (item.data1[2] != 0x00) && item.is_s_rank_weapon()) {
try {
ret_tokens.emplace_back(name_for_s_rank_special.at(item.data1[2]));
} catch (const out_of_range&) {
ret_tokens.emplace_back(string_printf("!SSP:%02hhX", item.data1[2]));
}
}
// Armors, shields, and units (0x01) can be wrapped, as can mags (0x02) and
// non-stackable tools (0x03). However, each of these item classes has its
// flags in a different location.
if (((item.data1[1] == 0x01) && (item.data1[4] & 0x40)) ||
((item.data1[0] == 0x02) && (item.data2[2] & 0x40)) ||
((item.data1[0] == 0x03) && !item.is_stackable() && (item.data1[3] & 0x40))) {
ret_tokens.emplace_back("Wrapped");
}
// Add the item name. Technique disks are special because the level is part of
// the primary identifier, so we manually generate the name instead of looking
// it up.
uint32_t primary_identifier = item.primary_identifier();
if ((primary_identifier & 0xFFFFFF00) == 0x00030200) {
string technique_name;
try {
technique_name = tech_id_to_name.at(item.data1[4]);
technique_name[0] = toupper(technique_name[0]);
} catch (const out_of_range&) {
technique_name = string_printf("!TECH:%02hhX", item.data1[4]);
}
// Hide the level for Reverser and Ryuker, unless the level isn't 1
if ((item.data1[2] == 0) && ((item.data1[4] == 0x0E) || (item.data1[4] == 0x11))) {
ret_tokens.emplace_back(string_printf("Disk:%s", technique_name.c_str()));
} else {
ret_tokens.emplace_back(string_printf("Disk:%s Lv.%d", technique_name.c_str(), item.data1[2] + 1));
}
} else {
try {
auto meta = this->primary_identifier_index.at(primary_identifier);
const string* name;
switch (version) {
case GameVersion::DC:
case GameVersion::PC:
name = &meta->v2_name;
break;
case GameVersion::GC:
case GameVersion::XB:
name = &meta->v3_name;
break;
case GameVersion::BB:
name = &meta->v4_name;
break;
default:
throw logic_error("invalid game version");
}
if (name->empty()) {
throw out_of_range("item does not exist");
}
ret_tokens.emplace_back(*name);
} catch (const out_of_range&) {
ret_tokens.emplace_back(string_printf("!ID:%06" PRIX32, primary_identifier));
}
}
// For weapons, add the grind and percentages, or S-rank name if applicable
if (item.data1[0] == 0x00) {
if (item.data1[3] > 0) {
ret_tokens.emplace_back(string_printf("+%hhu", item.data1[3]));
}
if (item.is_s_rank_weapon()) {
// S-rank (has name instead of percent bonuses)
uint8_t char_indexes[8] = {
static_cast<uint8_t>((item.data1w[3] >> 5) & 0x1F),
static_cast<uint8_t>(item.data1w[3] & 0x1F),
static_cast<uint8_t>((item.data1w[4] >> 10) & 0x1F),
static_cast<uint8_t>((item.data1w[4] >> 5) & 0x1F),
static_cast<uint8_t>(item.data1w[4] & 0x1F),
static_cast<uint8_t>((item.data1w[5] >> 10) & 0x1F),
static_cast<uint8_t>((item.data1w[5] >> 5) & 0x1F),
static_cast<uint8_t>(item.data1w[5] & 0x1F),
};
string name;
for (size_t x = 0; x < 8; x++) {
char ch = s_rank_name_characters[char_indexes[x]];
if (ch == 0) {
break;
}
name += ch;
}
if (!name.empty()) {
ret_tokens.emplace_back("(" + name + ")");
}
} else { // Not S-rank (extended name bits not set)
parray<int8_t, 5> percentages(0);
for (size_t x = 0; x < 3; x++) {
uint8_t which = item.data1[6 + 2 * x];
uint8_t value = item.data1[7 + 2 * x];
if (which == 0) {
continue;
}
if (which > 5) {
ret_tokens.emplace_back(string_printf("!PC:%02hhX%02hhX", which, value));
} else {
percentages[which - 1] = value;
}
}
if (!percentages.is_filled_with(0)) {
ret_tokens.emplace_back(string_printf("%hhd/%hhd/%hhd/%hhd/%hhd",
percentages[0], percentages[1], percentages[2], percentages[3], percentages[4]));
}
}
// For armors, add the slots, unit modifiers, and/or DEF/EVP bonuses
} else if (item.data1[0] == 0x01) {
if (item.data1[1] == 0x03) { // Units
uint16_t modifier = (item.data1[8] << 8) | item.data1[7];
if (modifier == 0x0001 || modifier == 0x0002) {
ret_tokens.back().append("+");
} else if (modifier == 0x0003 || modifier == 0x0004) {
ret_tokens.back().append("++");
} else if (modifier == 0xFFFF || modifier == 0xFFFE) {
ret_tokens.back().append("-");
} else if (modifier == 0xFFFD || modifier == 0xFFFC) {
ret_tokens.back().append("--");
} else if (modifier != 0x0000) {
ret_tokens.emplace_back(string_printf("!MD:%04hX", modifier));
}
} else { // Armor/shields
if (item.data1[5] > 0) {
if (item.data1[5] == 1) {
ret_tokens.emplace_back("(1 slot)");
} else {
ret_tokens.emplace_back(string_printf("(%hhu slots)", item.data1[5]));
}
}
if (item.data1w[3] != 0) {
ret_tokens.emplace_back(string_printf("+%hdDEF",
static_cast<int16_t>(item.data1w[3].load())));
}
if (item.data1w[4] != 0) {
ret_tokens.emplace_back(string_printf("+%hdEVP",
static_cast<int16_t>(item.data1w[4].load())));
}
}
// For mags, add tons of info
} else if (item.data1[0] == 0x02) {
ret_tokens.emplace_back(string_printf("LV%hhu", item.data1[2]));
uint16_t def = item.data1w[2];
uint16_t pow = item.data1w[3];
uint16_t dex = item.data1w[4];
uint16_t mind = item.data1w[5];
auto format_stat = +[](uint16_t stat) -> string {
uint16_t level = stat / 100;
uint8_t partial = stat % 100;
if (partial == 0) {
return string_printf("%hu", level);
} else if (partial % 10 == 0) {
return string_printf("%hu.%hhu", level, static_cast<uint8_t>(partial / 10));
} else {
return string_printf("%hu.%02hhu", level, partial);
}
};
ret_tokens.emplace_back(format_stat(def) + "/" + format_stat(pow) + "/" + format_stat(dex) + "/" + format_stat(mind));
ret_tokens.emplace_back(string_printf("%hhu%%", item.data2[0]));
ret_tokens.emplace_back(string_printf("%hhuIQ", item.data2[1]));
uint8_t flags = item.data2[2];
if (flags & 7) {
static const vector<const char*> pb_shortnames = {
"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
const char* pb_names[3] = {nullptr, nullptr, nullptr};
uint8_t left_pb = item.mag_photon_blast_for_slot(2);
uint8_t center_pb = item.mag_photon_blast_for_slot(0);
uint8_t right_pb = item.mag_photon_blast_for_slot(1);
if (left_pb != 0xFF) {
pb_names[0] = pb_shortnames[left_pb];
}
if (center_pb != 0xFF) {
pb_names[1] = pb_shortnames[center_pb];
}
if (right_pb != 0xFF) {
pb_names[2] = pb_shortnames[right_pb];
}
string token = "PB:";
for (size_t x = 0; x < 3; x++) {
if (pb_names[x] == nullptr) {
continue;
}
if (token.size() > 3) {
token += ',';
}
token += pb_names[x];
}
ret_tokens.emplace_back(std::move(token));
}
try {
ret_tokens.emplace_back(string_printf("(%s)", name_for_mag_color.at(item.data2[3])));
} catch (const out_of_range&) {
ret_tokens.emplace_back(string_printf("(!CL:%02hhX)", item.data2[3]));
}
// For tools, add the amount (if applicable)
} else if (item.data1[0] == 0x03) {
if (item.max_stack_size() > 1) {
ret_tokens.emplace_back(string_printf("x%hhu", item.data1[5]));
}
}
string ret = join(ret_tokens, " ");
if (item_parameter_table) {
if (item.is_s_rank_weapon()) {
return "$C4" + ret;
} else if (item_parameter_table->is_item_rare(item)) {
return "$C6" + ret;
} else if (item.has_bonuses()) {
return "$C2" + ret;
} else {
return "$C7" + ret;
}
} else {
return ret;
}
}
ItemData ItemNameIndex::parse_item_description(GameVersion version, const std::string& desc) const {
try {
return this->parse_item_description_phase(version, desc, false);
} catch (const exception& e1) {
try {
return this->parse_item_description_phase(version, desc, true);
} catch (const exception& e2) {
try {
string data = parse_data_string(desc);
if (data.size() < 2) {
throw runtime_error("item code too short");
}
if (data[0] > 4) {
throw runtime_error("invalid item class");
}
if (data.size() > 16) {
throw runtime_error("item code too long");
}
ItemData ret;
if (data.size() <= 12) {
memcpy(ret.data1.data(), data.data(), data.size());
} else {
memcpy(ret.data1.data(), data.data(), 12);
memcpy(ret.data2.data(), data.data() + 12, data.size() - 12);
}
return ret;
} catch (const exception& ed) {
if (strcmp(e1.what(), e2.what())) {
throw runtime_error(string_printf("cannot parse item description (as text 1: %s) (as text 2: %s) (as data: %s)", e1.what(), e2.what(), ed.what()));
} else {
throw runtime_error(string_printf("cannot parse item description (as text: %s) (as data: %s)", e1.what(), ed.what()));
}
}
}
}
}
ItemData ItemNameIndex::parse_item_description_phase(GameVersion version, const std::string& description, bool skip_special) const {
ItemData ret;
ret.data1d.clear(0);
ret.id = 0xFFFFFFFF;
ret.data2d = 0;
string desc = tolower(description);
if (ends_with(desc, " meseta")) {
ret.data1[0] = 0x04;
ret.data2d = stol(desc, nullptr, 10);
return ret;
}
if (starts_with(desc, "disk:")) {
auto tokens = split(desc, ' ');
tokens[0] = tokens[0].substr(5); // Trim off "disk:"
if ((tokens[0] == "reverser") || (tokens[0] == "ryuker")) {
uint8_t tech = technique_for_name(tokens[0]);
ret.data1[0] = 0x03;
ret.data1[1] = 0x02;
ret.data1[2] = 0x00;
ret.data1[4] = tech;
} else {
if (tokens.size() != 2) {
throw runtime_error("invalid tech disk format");
}
if (!starts_with(tokens[1], "lv.")) {
throw runtime_error("invalid tech disk level");
}
uint8_t tech = technique_for_name(tokens[0]);
uint8_t level = stoul(tokens[1].substr(3), nullptr, 10) - 1;
ret.data1[0] = 0x03;
ret.data1[1] = 0x02;
ret.data1[2] = level;
ret.data1[4] = tech;
}
return ret;
}
bool is_wrapped = starts_with(desc, "wrapped ");
if (is_wrapped) {
desc = desc.substr(8);
}
// TODO: It'd be nice to be able to parse S-rank weapon specials here too.
uint8_t weapon_special = 0;
if (!skip_special) {
for (size_t z = 0; z < name_for_weapon_special.size(); z++) {
if (!name_for_weapon_special[z]) {
continue;
}
string prefix = tolower(name_for_weapon_special[z]);
prefix += ' ';
if (starts_with(desc, prefix)) {
weapon_special = z;
desc = desc.substr(prefix.size());
break;
}
}
}
const map<string, shared_ptr<ItemMetadata>>* name_index;
switch (version) {
case GameVersion::DC:
case GameVersion::PC:
name_index = &this->v2_name_index;
break;
case GameVersion::GC:
case GameVersion::XB:
name_index = &this->v3_name_index;
break;
case GameVersion::BB:
name_index = &this->v4_name_index;
break;
default:
throw logic_error("invalid game version");
}
auto name_it = name_index->lower_bound(desc);
// Look up to 3 places before the lower bound. We have to do this to catch
// cases like Sange vs. Sange & Yasha - if the input is like "Sange 0/...",
// then we'll see Sange & Yasha first, which we should skip.
size_t lookback = 0;
while (lookback < 4) {
if (name_it != name_index->end() && desc.starts_with(name_it->first)) {
break;
} else if (name_it == name_index->begin()) {
throw runtime_error("no such item");
} else {
name_it--;
lookback++;
}
}
if (lookback >= 4) {
throw runtime_error("item not found: " + desc);
}
desc = desc.substr(name_it->first.size());
if (starts_with(desc, " ")) {
desc = desc.substr(1);
}
uint32_t primary_identifier = name_it->second->primary_identifier;
ret.data1[0] = (primary_identifier >> 16) & 0xFF;
ret.data1[1] = (primary_identifier >> 8) & 0xFF;
ret.data1[2] = primary_identifier & 0xFF;
if (ret.data1[0] == 0x00) {
// Weapons: add special, grind and percentages (or name, if S-rank)
ret.data1[4] = weapon_special | (is_wrapped ? 0x40 : 0x00);
auto tokens = split(desc, ' ');
for (auto& token : tokens) {
if (token.empty()) {
continue;
}
if (starts_with(token, "+")) {
token = token.substr(1);
ret.data1[3] = stoul(token, nullptr, 10);
} else if (ret.is_s_rank_weapon()) {
if (token.size() > 8) {
throw runtime_error("s-rank name too long");
}
uint8_t char_indexes[8] = {0, 0, 0, 0, 0, 0, 0, 0};
for (size_t z = 0; z < token.size(); z++) {
char ch = toupper(token[z]);
const char* pos = strchr(s_rank_name_characters, ch);
if (!pos) {
throw runtime_error(string_printf("s-rank name contains invalid character %02hhX (%c)", ch, ch));
}
char_indexes[z] = (pos - s_rank_name_characters);
}
ret.data1w[3] = (char_indexes[1] & 0x1F) | ((char_indexes[0] & 0x1F) << 5);
ret.data1w[4] = (char_indexes[4] & 0x1F) | ((char_indexes[3] & 0x1F) << 5) | ((char_indexes[2] & 0x1F) << 10);
ret.data1w[5] = (char_indexes[7] & 0x1F) | ((char_indexes[6] & 0x1F) << 5) | ((char_indexes[5] & 0x1F) << 10);
} else {
auto p_tokens = split(token, '/');
if (p_tokens.size() > 5) {
throw runtime_error("invalid bonuses token");
}
uint8_t bonus_index = 0;
for (size_t z = 0; z < p_tokens.size(); z++) {
int8_t bonus_value = stol(p_tokens[z], nullptr, 10);
if (bonus_value == 0) {
continue;
}
if (bonus_index >= 3) {
throw runtime_error("weapon has too many bonuses");
}
ret.data1[6 + (2 * bonus_index)] = z + 1;
ret.data1[7 + (2 * bonus_index)] = static_cast<uint8_t>(bonus_value);
bonus_index++;
}
}
}
} else if (ret.data1[0] == 0x01) {
if (ret.data1[1] == 0x03) { // Unit
static const unordered_map<string, uint16_t> modifiers({
{"--", 0xFFFC},
{"-", 0xFFFE},
{"", 0x0000},
{"+", 0x0002},
{"++", 0x0004},
});
uint16_t modifier = modifiers.at(desc);
ret.data1[7] = modifier & 0xFF;
ret.data1[8] = (modifier >> 8) & 0xFF;
} else { // Armor/shield
for (const auto& token : split(desc, ' ')) {
if (token.empty()) {
continue;
} else if (!starts_with(token, "+")) {
throw runtime_error("invalid armor/shield modifier");
}
if (ends_with(token, "def")) {
ret.data1w[3] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
} else if (ends_with(token, "evp")) {
ret.data1w[4] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
} else {
ret.data1[5] = stoul(token.substr(1), nullptr, 10);
}
}
}
if (is_wrapped) {
ret.data1[4] |= 0x40;
}
} else if (ret.data1[0] == 0x02) {
for (const auto& token : split(desc, ' ')) {
if (token.empty()) {
continue;
} else if (starts_with(token, "pb:")) { // Photon blasts
auto pb_tokens = split(token.substr(3), ',');
if (pb_tokens.size() > 3) {
throw runtime_error("too many photon blasts specified");
}
static const unordered_map<string, uint8_t> name_to_pb_num({
{"f", 0},
{"e", 1},
{"g", 2},
{"p", 3},
{"l", 4},
{"m&y", 5},
});
for (const auto& pb_token : pb_tokens) {
ret.add_mag_photon_blast(name_to_pb_num.at(pb_token));
}
} else if (ends_with(token, "%")) { // Synchro
ret.data2[0] = stoul(token.substr(0, token.size() - 1), nullptr, 10);
} else if (ends_with(token, "iq")) { // IQ
ret.data2[1] = stoul(token.substr(0, token.size() - 2), nullptr, 10);
} else if (!token.empty() && isdigit(token[0])) { // Stats
auto s_tokens = split(token, '/');
if (s_tokens.size() != 4) {
throw runtime_error("incorrect stat count");
}
for (size_t z = 0; z < 4; z++) {
auto n_tokens = split(s_tokens[z], '.');
if (n_tokens.size() == 0 || n_tokens.size() > 2) {
throw logic_error("incorrect stats argument format");
} else if ((n_tokens.size() == 1) || (n_tokens[1].size() == 0)) {
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100;
} else if (n_tokens[1].size() == 1) {
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100 + stoul(n_tokens[1], nullptr, 10) * 10;
} else if (n_tokens[1].size() == 2) {
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100 + stoul(n_tokens[1], nullptr, 10);
} else {
throw runtime_error("incorrect stat format");
}
}
ret.data1[2] = ret.compute_mag_level();
} else { // Color
ret.data2[3] = mag_color_for_name.at(token);
}
}
if (is_wrapped) {
ret.data2[2] |= 0x40;
}
} else if (ret.data1[0] == 0x03) {
if (ret.max_stack_size() > 1) {
if (starts_with(desc, "x")) {
ret.data1[5] = stoul(desc.substr(1), nullptr, 10);
} else {
ret.data1[5] = 1;
}
} else if (!desc.empty()) {
throw runtime_error("item cannot be stacked");
}
if (is_wrapped) {
if (ret.is_stackable()) {
throw runtime_error("stackable items cannot be wrapped");
} else {
ret.data1[3] |= 0x40;
}
}
} else {
throw logic_error("invalid item class");
}
return ret;
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <stdint.h>
#include <memory>
#include <phosg/JSON.hh>
#include <string>
#include <unordered_map>
#include <vector>
#include "ItemData.hh"
#include "ItemParameterTable.hh"
class ItemNameIndex {
public:
ItemNameIndex(JSON&& v2_names, JSON&& v3_names, JSON&& v4_names);
std::string describe_item(
GameVersion version,
const ItemData& item,
std::shared_ptr<const ItemParameterTable> item_parameter_table = nullptr) const;
ItemData parse_item_description(GameVersion version, const std::string& description) const;
private:
ItemData parse_item_description_phase(GameVersion version, const std::string& description, bool skip_special) const;
struct ItemMetadata {
uint32_t primary_identifier;
std::string v2_name;
std::string v3_name;
std::string v4_name;
};
std::unordered_map<uint32_t, std::shared_ptr<ItemMetadata>> primary_identifier_index;
std::map<std::string, std::shared_ptr<ItemMetadata>> v2_name_index;
std::map<std::string, std::shared_ptr<ItemMetadata>> v3_name_index;
std::map<std::string, std::shared_ptr<ItemMetadata>> v4_name_index;
};
+568 -125
View File
@@ -2,70 +2,404 @@
using namespace std;
ItemParameterTable::ItemParameterTable(shared_ptr<const string> data)
ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version version)
: data(data),
r(*data) {
size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10);
this->offsets = &r.pget<TableOffsets>(offset_table_offset);
r(*data),
offsets_v2(nullptr),
offsets_v3(nullptr),
offsets_v4(nullptr) {
switch (version) {
case Version::V2: {
size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10);
this->offsets_v2 = &this->r.pget<TableOffsetsV2>(offset_table_offset);
this->num_weapon_classes = 0x89;
this->num_tool_classes = 0x10;
this->item_stars_first_id = 0x4E;
this->item_stars_last_id = 0x215;
this->special_stars_begin_index = 0x138;
this->star_value_table_size = 0x1C7;
break;
}
case Version::V3: {
size_t offset_table_offset = this->r.pget_u32b(this->data->size() - 0x10);
this->offsets_v3 = &this->r.pget<TableOffsetsV3V4<true>>(offset_table_offset);
this->num_weapon_classes = 0xAA;
this->num_tool_classes = 0x18;
this->item_stars_first_id = 0x94;
this->item_stars_last_id = 0x2F7;
this->special_stars_begin_index = 0x1CB;
this->star_value_table_size = 0x263;
break;
}
case Version::V4: {
size_t offset_table_offset = this->r.pget_u32l(this->data->size() - 0x10);
this->offsets_v4 = &this->r.pget<TableOffsetsV3V4<false>>(offset_table_offset);
this->num_weapon_classes = 0xED;
this->num_tool_classes = 0x1B;
this->item_stars_first_id = 0xB1;
this->item_stars_last_id = 0x437;
this->special_stars_begin_index = 0x256;
this->star_value_table_size = 0x330;
break;
}
default:
throw logic_error("invalid item parameter table version");
}
this->num_specials = 0x29;
this->first_rare_mag_index = 0x28;
}
const ItemParameterTable::Weapon& ItemParameterTable::get_weapon(
uint8_t data1_1, uint8_t data1_2) const {
if (data1_1 >= 0xED) {
const ItemParameterTable::WeaponV4& ItemParameterTable::get_weapon(uint8_t data1_1, uint8_t data1_2) const {
if (data1_1 >= this->num_weapon_classes) {
throw runtime_error("weapon ID out of range");
}
const auto& co = this->r.pget<CountAndOffset>(
this->offsets->weapon_table + sizeof(CountAndOffset) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("weapon ID out of range");
if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->weapon_table + sizeof(ArrayRefLE) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("weapon ID out of range");
}
return this->r.pget<WeaponV4>(co.offset + sizeof(WeaponV4) * data1_2);
}
uint16_t key = (data1_1 << 8) | data1_2;
try {
return this->parsed_weapons.at(key);
} catch (const std::out_of_range&) {
auto& def_v4 = this->parsed_weapons.emplace(key, WeaponV4{}).first->second;
if (this->offsets_v2) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->weapon_table + sizeof(ArrayRefLE) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("weapon ID out of range");
}
const auto& def_v2 = this->r.pget<WeaponV2>(co.offset + sizeof(WeaponV2) * data1_2);
def_v4.base.id = def_v2.base.id;
def_v4.class_flags = def_v2.class_flags;
def_v4.atp_min = def_v2.atp_min;
def_v4.atp_max = def_v2.atp_max;
def_v4.atp_required = def_v2.atp_required;
def_v4.mst_required = def_v2.mst_required;
def_v4.ata_required = def_v2.ata_required;
def_v4.max_grind = def_v2.max_grind;
def_v4.photon = def_v2.photon;
def_v4.special = def_v2.special;
def_v4.ata = def_v2.ata;
} else if (this->offsets_v3) {
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->weapon_table + sizeof(ArrayRefBE) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("weapon ID out of range");
}
const auto& def_v3 = this->r.pget<WeaponV3<true>>(co.offset + sizeof(WeaponV3<true>) * data1_2);
def_v4.base.id = def_v3.base.id.load();
def_v4.base.type = def_v3.base.type.load();
def_v4.base.skin = def_v3.base.skin.load();
def_v4.class_flags = def_v3.class_flags.load();
def_v4.atp_min = def_v3.atp_min.load();
def_v4.atp_max = def_v3.atp_max.load();
def_v4.atp_required = def_v3.atp_required.load();
def_v4.mst_required = def_v3.mst_required.load();
def_v4.ata_required = def_v3.ata_required.load();
def_v4.mst = def_v3.mst.load();
def_v4.max_grind = def_v3.max_grind;
def_v4.photon = def_v3.photon;
def_v4.special = def_v3.special;
def_v4.ata = def_v3.ata;
def_v4.stat_boost = def_v3.stat_boost;
def_v4.projectile = def_v3.projectile;
def_v4.trail1_x = def_v3.trail1_x;
def_v4.trail1_y = def_v3.trail1_y;
def_v4.trail2_x = def_v3.trail2_x;
def_v4.trail2_y = def_v3.trail2_y;
def_v4.color = def_v3.color;
def_v4.unknown_a1 = def_v3.unknown_a1;
def_v4.unknown_a2 = def_v3.unknown_a2;
def_v4.unknown_a3 = def_v3.unknown_a3;
def_v4.unknown_a4 = def_v3.unknown_a4;
def_v4.unknown_a5 = def_v3.unknown_a5;
def_v4.tech_boost = def_v3.tech_boost;
def_v4.combo_type = def_v3.combo_type;
} else {
throw logic_error("table is not v2, v3, or v4");
}
return def_v4;
}
return this->r.pget<Weapon>(co.offset + sizeof(Weapon) * data1_2);
}
const ItemParameterTable::ArmorOrShield& ItemParameterTable::get_armor_or_shield(
uint8_t data1_1, uint8_t data1_2) const {
const ItemParameterTable::ArmorOrShieldV4& ItemParameterTable::get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const {
if ((data1_1 < 1) || (data1_1 > 2)) {
throw runtime_error("armor/shield ID out of range");
throw runtime_error("armor/shield class ID out of range");
}
const auto& co = this->r.pget<CountAndOffset>(
this->offsets->armor_table + sizeof(CountAndOffset) * (data1_1 - 1));
if (data1_2 >= co.count) {
throw runtime_error("armor/shield ID out of range");
if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->armor_table + sizeof(ArrayRefLE) * (data1_1 - 1));
if (data1_2 >= co.count) {
throw runtime_error("armor/shield ID out of range");
}
return this->r.pget<ArmorOrShieldV4>(co.offset + sizeof(ArmorOrShieldV4) * data1_2);
}
auto& parsed_vec = (data1_1 == 2) ? this->parsed_shields : this->parsed_armors;
try {
const auto& ret = parsed_vec.at(data1_2);
if (ret.base.id == 0xFFFFFFFF) {
throw out_of_range("cache entry not populated");
}
return ret;
} catch (const std::out_of_range&) {
if (data1_2 <= parsed_vec.size()) {
parsed_vec.resize(data1_2 + 1);
}
auto& def_v4 = parsed_vec[data1_2];
if (this->offsets_v2) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->armor_table + sizeof(ArrayRefLE) * (data1_1 - 1));
if (data1_2 >= co.count) {
throw runtime_error("armor/shield ID out of range");
}
const auto& def_v2 = this->r.pget<ArmorOrShieldV2>(co.offset + sizeof(ArmorOrShieldV2) * data1_2);
def_v4.base.id = def_v2.base.id;
def_v4.dfp = def_v2.dfp;
def_v4.evp = def_v2.evp;
def_v4.block_particle = def_v2.block_particle;
def_v4.block_effect = def_v2.block_effect;
def_v4.class_flags = def_v2.class_flags;
def_v4.required_level = def_v2.required_level;
def_v4.efr = def_v2.efr;
def_v4.eth = def_v2.eth;
def_v4.eic = def_v2.eic;
def_v4.edk = def_v2.edk;
def_v4.elt = def_v2.elt;
def_v4.dfp_range = def_v2.dfp_range;
def_v4.evp_range = def_v2.evp_range;
def_v4.stat_boost = def_v2.stat_boost;
def_v4.tech_boost = def_v2.tech_boost;
def_v4.unknown_a2 = def_v2.unknown_a2;
} else if (this->offsets_v3) {
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->armor_table + sizeof(ArrayRefBE) * (data1_1 - 1));
if (data1_2 >= co.count) {
throw runtime_error("armor/shield ID out of range");
}
const auto& def_v3 = this->r.pget<ArmorOrShieldV3>(co.offset + sizeof(ArmorOrShieldV3) * data1_2);
def_v4.base.id = def_v3.base.id.load();
def_v4.base.type = def_v3.base.type.load();
def_v4.base.skin = def_v3.base.skin.load();
def_v4.dfp = def_v3.dfp.load();
def_v4.evp = def_v3.evp.load();
def_v4.block_particle = def_v3.block_particle;
def_v4.block_effect = def_v3.block_effect;
def_v4.class_flags = def_v3.class_flags.load();
def_v4.required_level = def_v3.required_level;
def_v4.efr = def_v3.efr;
def_v4.eth = def_v3.eth;
def_v4.eic = def_v3.eic;
def_v4.edk = def_v3.edk;
def_v4.elt = def_v3.elt;
def_v4.dfp_range = def_v3.dfp_range;
def_v4.evp_range = def_v3.evp_range;
def_v4.stat_boost = def_v3.stat_boost;
def_v4.tech_boost = def_v3.tech_boost;
def_v4.unknown_a2 = def_v3.unknown_a2.load();
} else {
throw logic_error("table is not v2, v3, or v4");
}
return def_v4;
}
return this->r.pget<ArmorOrShield>(co.offset + sizeof(ArmorOrShield) * data1_2);
}
const ItemParameterTable::Unit& ItemParameterTable::get_unit(
uint8_t data1_2) const {
const auto& co = this->r.pget<CountAndOffset>(this->offsets->unit_table);
if (data1_2 >= co.count) {
throw runtime_error("unit ID out of range");
const ItemParameterTable::UnitV4& ItemParameterTable::get_unit(uint8_t data1_2) const {
if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->unit_table);
if (data1_2 >= co.count) {
throw runtime_error("unit ID out of range");
}
return this->r.pget<UnitV4>(co.offset + sizeof(UnitV4) * data1_2);
}
try {
const auto& ret = this->parsed_units.at(data1_2);
if (ret.base.id == 0xFFFFFFFF) {
throw out_of_range("cache entry not populated");
}
return ret;
} catch (const std::out_of_range&) {
if (data1_2 <= this->parsed_units.size()) {
this->parsed_units.resize(data1_2 + 1);
}
auto& def_v4 = this->parsed_units[data1_2];
if (this->offsets_v2) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->unit_table);
if (data1_2 >= co.count) {
throw runtime_error("unit ID out of range");
}
const auto& def_v2 = this->r.pget<UnitV2>(co.offset + sizeof(UnitV2) * data1_2);
def_v4.base.id = def_v2.base.id;
def_v4.stat = def_v2.stat;
def_v4.stat_amount = def_v2.stat_amount;
def_v4.modifier_amount = def_v2.modifier_amount;
} else if (this->offsets_v3) {
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->unit_table);
if (data1_2 >= co.count) {
throw runtime_error("unit ID out of range");
}
const auto& def_v3 = this->r.pget<UnitV3>(co.offset + sizeof(UnitV3) * data1_2);
def_v4.base.id = def_v3.base.id.load();
def_v4.base.type = def_v3.base.type.load();
def_v4.base.skin = def_v3.base.skin.load();
def_v4.stat = def_v3.stat.load();
def_v4.stat_amount = def_v3.stat_amount.load();
def_v4.modifier_amount = def_v3.modifier_amount.load();
} else {
throw logic_error("table is not v2, v3, or v4");
}
return def_v4;
}
return this->r.pget<Unit>(co.offset + sizeof(Unit) * data1_2);
}
const ItemParameterTable::Tool& ItemParameterTable::get_tool(
uint8_t data1_1, uint8_t data1_2) const {
if (data1_1 > 0x1A) {
throw runtime_error("tool ID out of range");
const ItemParameterTable::MagV4& ItemParameterTable::get_mag(uint8_t data1_1) const {
if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->mag_table);
if (data1_1 >= co.count) {
throw runtime_error("mag ID out of range");
}
return this->r.pget<MagV4>(co.offset + sizeof(MagV4) * data1_1);
}
const auto& co = this->r.pget<CountAndOffset>(
this->offsets->tool_table + sizeof(CountAndOffset) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("tool ID out of range");
try {
const auto& ret = this->parsed_mags.at(data1_1);
if (ret.base.id == 0xFFFFFFFF) {
throw out_of_range("cache entry not populated");
}
return ret;
} catch (const std::out_of_range&) {
if (data1_1 <= this->parsed_mags.size()) {
this->parsed_mags.resize(data1_1 + 1);
}
auto& def_v4 = this->parsed_mags[data1_1];
if (this->offsets_v2) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->mag_table);
if (data1_1 >= co.count) {
throw runtime_error("mag ID out of range");
}
const auto& def_v2 = this->r.pget<MagV2>(co.offset + sizeof(MagV2) * data1_1);
def_v4.base.id = def_v2.base.id;
def_v4.feed_table = def_v2.feed_table;
def_v4.photon_blast = def_v2.photon_blast;
def_v4.activation = def_v2.activation;
def_v4.on_pb_full = def_v2.on_pb_full;
def_v4.on_low_hp = def_v2.on_low_hp;
def_v4.on_death = def_v2.on_death;
def_v4.on_boss = def_v2.on_boss;
def_v4.on_pb_full_flag = def_v2.on_pb_full_flag;
def_v4.on_low_hp_flag = def_v2.on_low_hp_flag;
def_v4.on_death_flag = def_v2.on_death_flag;
def_v4.on_boss_flag = def_v2.on_boss_flag;
def_v4.class_flags = def_v2.class_flags;
} else if (this->offsets_v3) {
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->mag_table);
if (data1_1 >= co.count) {
throw runtime_error("mag ID out of range");
}
const auto& def_v3 = this->r.pget<MagV3>(co.offset + sizeof(MagV3) * data1_1);
def_v4.base.id = def_v3.base.id.load();
def_v4.base.type = def_v3.base.type.load();
def_v4.base.skin = def_v3.base.skin.load();
def_v4.feed_table = def_v3.feed_table.load();
def_v4.photon_blast = def_v3.photon_blast;
def_v4.activation = def_v3.activation;
def_v4.on_pb_full = def_v3.on_pb_full;
def_v4.on_low_hp = def_v3.on_low_hp;
def_v4.on_death = def_v3.on_death;
def_v4.on_boss = def_v3.on_boss;
def_v4.on_pb_full_flag = def_v3.on_pb_full_flag;
def_v4.on_low_hp_flag = def_v3.on_low_hp_flag;
def_v4.on_death_flag = def_v3.on_death_flag;
def_v4.on_boss_flag = def_v3.on_boss_flag;
def_v4.class_flags = def_v3.class_flags.load();
} else {
throw logic_error("table is not v2, v3, or v4");
}
return def_v4;
}
return this->r.pget<Tool>(co.offset + sizeof(Tool) * data1_2);
}
pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_class(
uint8_t tool_class) const {
const auto& cos = this->r.pget<parray<CountAndOffset, 0x18>>(
this->offsets->tool_table);
for (size_t z = 0; z < cos.size(); z++) {
const ItemParameterTable::ToolV4& ItemParameterTable::get_tool(uint8_t data1_1, uint8_t data1_2) const {
if (data1_1 >= this->num_tool_classes) {
throw runtime_error("tool class ID out of range");
}
if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->tool_table + sizeof(ArrayRefLE) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("tool ID out of range");
}
return this->r.pget<ToolV4>(co.offset + sizeof(ToolV4) * data1_2);
}
uint16_t key = (data1_1 << 8) | data1_2;
try {
return this->parsed_tools.at(key);
} catch (const std::out_of_range&) {
auto& def_v4 = this->parsed_tools.emplace(key, ToolV4{}).first->second;
if (this->offsets_v2) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v2->tool_table + sizeof(ArrayRefLE) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("tool ID out of range");
}
const auto& def_v2 = this->r.pget<ToolV2>(co.offset + sizeof(ToolV2) * data1_2);
def_v4.base.id = def_v2.base.id;
def_v4.amount = def_v2.amount;
def_v4.tech = def_v2.tech;
def_v4.cost = def_v2.cost;
def_v4.item_flag = def_v2.item_flag;
} else if (this->offsets_v3) {
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->tool_table + sizeof(ArrayRefBE) * data1_1);
if (data1_2 >= co.count) {
throw runtime_error("tool ID out of range");
}
const auto& def_v3 = this->r.pget<ToolV3>(co.offset + sizeof(ToolV3) * data1_2);
def_v4.base.id = def_v3.base.id.load();
def_v4.base.type = def_v3.base.type.load();
def_v4.base.skin = def_v3.base.skin.load();
def_v4.amount = def_v3.amount.load();
def_v4.tech = def_v3.tech.load();
def_v4.cost = def_v3.cost.load();
def_v4.item_flag = def_v3.item_flag;
} else {
throw logic_error("table is not v2, v3, or v4");
}
return def_v4;
}
}
template <typename ToolT, bool IsBigEndian>
pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_id_t(uint32_t tool_table_offset, uint32_t item_id) const {
const auto* cos = &this->r.pget<ArrayRef<IsBigEndian>>(
tool_table_offset, this->num_tool_classes * sizeof(ArrayRef<IsBigEndian>));
for (size_t z = 0; z < this->num_tool_classes; z++) {
const auto& co = cos[z];
const auto* defs = &this->r.pget<Tool>(co.offset, sizeof(Tool) * co.count);
const auto* defs = &this->r.pget<ToolT>(co.offset, sizeof(ToolT) * co.count);
for (size_t y = 0; y < co.count; y++) {
if (defs[y].base.id == tool_class) {
if (defs[y].base.id == item_id) {
return make_pair(z, y);
}
}
@@ -73,43 +407,66 @@ pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_class(
throw runtime_error("invalid tool class");
}
const ItemParameterTable::Mag& ItemParameterTable::get_mag(
uint8_t data1_1) const {
const auto& co = this->r.pget<CountAndOffset>(this->offsets->mag_table);
if (data1_1 >= co.count) {
throw runtime_error("unit ID out of range");
pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_id(uint32_t item_id) const {
if (this->offsets_v2) {
return this->find_tool_by_id_t<ToolV2, false>(this->offsets_v2->tool_table, item_id);
} else if (this->offsets_v3) {
return this->find_tool_by_id_t<ToolV3, true>(this->offsets_v3->tool_table, item_id);
} else if (this->offsets_v4) {
return this->find_tool_by_id_t<ToolV4, false>(this->offsets_v4->tool_table, item_id);
} else {
throw logic_error("table is not v2, v3, or v4");
}
}
template <bool IsBigEndian>
float ItemParameterTable::get_sale_divisor_t(
uint32_t weapon_table_offset, uint32_t non_weapon_table_offset, uint8_t data1_0, uint8_t data1_1) const {
using FloatT = typename std::conditional<IsBigEndian, be_float, le_float>::type;
switch (data1_0) {
case 0:
if (data1_1 >= this->num_weapon_classes) {
return 0.0f;
}
return this->r.pget<FloatT>(weapon_table_offset + data1_1 * sizeof(FloatT));
case 1: {
const auto& divisors = this->r.pget<NonWeaponSaleDivisors<IsBigEndian>>(non_weapon_table_offset);
switch (data1_1) {
case 1:
return divisors.armor_divisor;
case 2:
return divisors.shield_divisor;
case 3:
return divisors.unit_divisor;
}
return 0.0f;
}
case 2: {
const auto& divisors = this->r.pget<NonWeaponSaleDivisors<IsBigEndian>>(non_weapon_table_offset);
return divisors.mag_divisor;
}
default:
return 0.0f;
}
return this->r.pget<Mag>(co.offset + sizeof(Mag) * data1_1);
}
float ItemParameterTable::get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const {
if (data1_0 == 0) { // Weapon
if (data1_1 < 0xED) {
return this->r.pget_f32l(
this->offsets->weapon_sale_divisor_table + data1_1 * sizeof(float));
}
return 0.0f;
if (this->offsets_v2) {
return this->get_sale_divisor_t<false>(
this->offsets_v2->weapon_sale_divisor_table, this->offsets_v2->sale_divisor_table, data1_0, data1_1);
} else if (this->offsets_v3) {
return this->get_sale_divisor_t<true>(
this->offsets_v3->weapon_sale_divisor_table, this->offsets_v3->sale_divisor_table, data1_0, data1_1);
} else if (this->offsets_v4) {
return this->get_sale_divisor_t<false>(
this->offsets_v4->weapon_sale_divisor_table, this->offsets_v4->sale_divisor_table, data1_0, data1_1);
} else {
throw logic_error("table is not v2, v3, or v4");
}
const auto& divisors = this->r.pget<NonWeaponSaleDivisors>(
this->offsets->sale_divisor_table);
if (data1_0 == 1) {
switch (data1_1) {
case 1:
return divisors.armor_divisor;
case 2:
return divisors.shield_divisor;
case 3:
return divisors.unit_divisor;
}
return 0.0f;
}
if (data1_0 == 2) {
return divisors.mag_divisor;
}
return 0.0f;
}
const ItemParameterTable::MagFeedResult& ItemParameterTable::get_mag_feed_result(
@@ -120,33 +477,70 @@ const ItemParameterTable::MagFeedResult& ItemParameterTable::get_mag_feed_result
if (item_index >= 11) {
throw runtime_error("invalid mag feed item index");
}
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets>(this->offsets->mag_feed_table);
const auto& results = this->r.pget<MagFeedResultsList>(table_offsets.offsets[table_index]);
return results.results[item_index];
uint32_t offset;
if (this->offsets_v2) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<false>>(this->offsets_v2->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else if (this->offsets_v3) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<true>>(this->offsets_v3->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else if (this->offsets_v4) {
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsets<false>>(this->offsets_v4->mag_feed_table);
offset = table_offsets.offsets[table_index];
} else {
throw logic_error("table is not v2, v3, or v4");
}
return this->r.pget<MagFeedResultsList>(offset)[item_index];
}
uint8_t ItemParameterTable::get_item_stars(uint16_t slot) const {
if ((slot >= 0xB1) && (slot < 0x437)) {
return this->r.pget_u8(this->offsets->star_value_table + slot - 0xB1);
uint8_t ItemParameterTable::get_item_stars(uint32_t item_id) const {
uint32_t base_offset;
if (this->offsets_v2) {
base_offset = this->offsets_v2->star_value_table;
} else if (this->offsets_v3) {
base_offset = this->offsets_v3->star_value_table;
} else if (this->offsets_v4) {
base_offset = this->offsets_v4->star_value_table;
} else {
throw logic_error("table is not v2, v3, or v4");
}
return 0;
return ((item_id >= this->item_stars_first_id) && (item_id < this->item_stars_last_id))
? this->r.pget_u8(base_offset + item_id - this->item_stars_first_id)
: 0;
}
uint8_t ItemParameterTable::get_special_stars(uint8_t det) const {
if (!(det & 0x3F) || (det & 0x80)) {
return 0;
}
// Note: PSO GC uses 0x1CB here. 0x256 was chosen to point to the same data in
// PSO BB's ItemPMT file.
return this->get_item_stars(det + 0x0256);
return ((det & 0x3F) && !(det & 0x80))
? this->get_item_stars(det + this->special_stars_begin_index)
: 0;
}
const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_t special) const {
const ItemParameterTable::Special<false>& ItemParameterTable::get_special(uint8_t special) const {
special &= 0x3F;
if (special >= 0x29) {
if (special >= this->num_specials) {
throw runtime_error("invalid special index");
}
return this->r.pget<Special>(this->offsets->special_data_table + sizeof(Special) * special);
if (this->offsets_v2) {
return this->r.pget<Special<false>>(this->offsets_v2->special_data_table + sizeof(Special<false>) * special);
} else if (this->offsets_v3) {
if ((this->parsed_specials.size() <= special) || (this->parsed_specials[special].type != 0xFFFF)) {
if (this->parsed_specials.size() <= special) {
this->parsed_specials.resize(special + 1);
}
const auto& sp_be = this->r.pget<Special<true>>(this->offsets_v3->special_data_table + sizeof(Special<true>) * special);
this->parsed_specials[special].type = sp_be.type.load();
this->parsed_specials[special].amount = sp_be.amount.load();
}
return this->parsed_specials[special];
} else if (this->offsets_v4) {
return this->r.pget<Special<false>>(this->offsets_v4->special_data_table + sizeof(Special<false>) * special);
} else {
throw logic_error("table is not v2, v3, or v4");
}
}
uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_num) const {
@@ -156,28 +550,42 @@ uint8_t ItemParameterTable::get_max_tech_level(uint8_t char_class, uint8_t tech_
if (tech_num >= 19) {
throw runtime_error("invalid technique number");
}
return r.pget_u8(this->offsets->max_tech_level_table + tech_num * 12 + char_class);
if (this->offsets_v2) {
if ((tech_num == 14) || (tech_num == 17)) { // Ryuker or Reverser
return 0;
} else if (tech_num == 16) { // Anti
return 7;
} else {
return ((char_class == 6) || (char_class == 7) || (char_class == 8) || (char_class == 10)) ? 29 : 14;
}
} else if (this->offsets_v3) {
return r.pget_u8(this->offsets_v3->max_tech_level_table + tech_num * 12 + char_class);
} else if (this->offsets_v4) {
return r.pget_u8(this->offsets_v4->max_tech_level_table + tech_num * 12 + char_class);
} else {
throw logic_error("table is not v2, v3, or v4");
}
}
const ItemParameterTable::ItemBase& ItemParameterTable::get_item_definition(
const ItemData& item) const {
uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
switch (item.data1[0]) {
case 0:
return this->get_weapon(item.data1[1], item.data1[2]).base;
return this->get_weapon(item.data1[1], item.data1[2]).base.id;
case 1:
if (item.data1[1] == 3) {
return this->get_unit(item.data1[2]).base;
return this->get_unit(item.data1[2]).base.id;
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
return this->get_armor_or_shield(item.data1[1], item.data1[2]).base;
return this->get_armor_or_shield(item.data1[1], item.data1[2]).base.id;
}
throw runtime_error("invalid item");
case 2:
return this->get_mag(item.data1[1]).base;
return this->get_mag(item.data1[1]).base.id;
case 3:
if (item.data1[1] == 2) {
return this->get_tool(2, item.data1[4]).base;
return this->get_tool(2, item.data1[4]).base.id;
} else {
return this->get_tool(item.data1[1], item.data1[2]).base;
return this->get_tool(item.data1[1], item.data1[2]).base.id;
}
throw logic_error("this should be impossible");
case 4:
@@ -189,9 +597,9 @@ const ItemParameterTable::ItemBase& ItemParameterTable::get_item_definition(
uint8_t ItemParameterTable::get_item_base_stars(const ItemData& item) const {
if (item.data1[0] == 2) {
return (item.data1[1] > 0x27) ? 12 : 0;
return (item.data1[1] >= this->first_rare_mag_index) ? 12 : 0;
} else if (item.data1[0] < 2) {
return this->get_item_stars(this->get_item_definition(item).id);
return this->get_item_stars(this->get_item_id(item));
} else if (item.data1[0] == 3) {
const auto& def = (item.data1[1] == 2)
? this->get_tool(2, item.data1[4])
@@ -230,10 +638,23 @@ bool ItemParameterTable::is_item_rare(const ItemData& item) const {
}
bool ItemParameterTable::is_unsealable_item(const ItemData& item) const {
const auto& co = this->r.pget<CountAndOffset>(this->offsets->unsealable_table);
const auto* defs = &this->r.pget<UnsealableItem>(
co.offset, co.count * sizeof(UnsealableItem));
for (size_t z = 0; z < co.count; z++) {
uint32_t offset, count;
if (this->offsets_v2) {
return false;
} else if (this->offsets_v3) {
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->unsealable_table);
offset = co.offset;
count = co.count;
} else if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->unsealable_table);
offset = co.offset;
count = co.count;
} else {
throw logic_error("table is not v2, v3, or v4");
}
const auto* defs = &this->r.pget<UnsealableItem>(offset, count * sizeof(UnsealableItem));
for (size_t z = 0; z < count; z++) {
if ((defs[z].item[0] == item.data1[0]) &&
(defs[z].item[1] == item.data1[1]) &&
(defs[z].item[2] == item.data1[2])) {
@@ -243,21 +664,6 @@ bool ItemParameterTable::is_unsealable_item(const ItemData& item) const {
return false;
}
void ItemParameterTable::populate_item_combination_index() const {
if (!this->item_combination_index.empty()) {
return;
}
const auto& co = this->r.pget<CountAndOffset>(this->offsets->combination_table);
const auto* defs = &this->r.pget<ItemCombination>(
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);
}
}
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)) {
@@ -282,20 +688,57 @@ const std::vector<ItemParameterTable::ItemCombination>& ItemParameterTable::get_
}
const std::map<uint32_t, std::vector<ItemParameterTable::ItemCombination>>& ItemParameterTable::get_all_item_combinations() const {
this->populate_item_combination_index();
if (this->item_combination_index.empty()) {
uint32_t offset, count;
if (this->offsets_v2) {
static const std::map<uint32_t, std::vector<ItemParameterTable::ItemCombination>> empty_map;
return empty_map;
} else if (this->offsets_v3) {
const auto& co = this->r.pget<ArrayRefBE>(this->offsets_v3->combination_table);
offset = co.offset;
count = co.count;
} else if (this->offsets_v4) {
const auto& co = this->r.pget<ArrayRefLE>(this->offsets_v4->combination_table);
offset = co.offset;
count = co.count;
} else {
throw logic_error("table is not v2, v3, or v4");
}
const auto* defs = &this->r.pget<ItemCombination>(offset, count * sizeof(ItemCombination));
for (size_t z = 0; z < 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);
}
}
return this->item_combination_index;
}
std::pair<const ItemParameterTable::EventItem*, size_t> ItemParameterTable::get_event_items(uint8_t event_number) const {
const auto& co = this->r.pget<CountAndOffset>(this->offsets->unwrap_table);
template <bool IsBigEndian>
std::pair<const ItemParameterTable::EventItem*, size_t> ItemParameterTable::get_event_items_t(
uint32_t base_offset, uint8_t event_number) const {
const auto& co = this->r.pget<ArrayRef<IsBigEndian>>(base_offset);
if (event_number >= co.count) {
throw runtime_error("invalid event number");
}
const auto& event_co = this->r.pget<CountAndOffset>(co.offset + sizeof(CountAndOffset) * event_number);
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(ItemCombination));
const auto& event_co = this->r.pget<ArrayRef<IsBigEndian>>(co.offset + sizeof(ArrayRef<IsBigEndian>) * event_number);
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(EventItem));
return make_pair(defs, event_co.count);
}
std::pair<const ItemParameterTable::EventItem*, size_t> ItemParameterTable::get_event_items(uint8_t event_number) const {
if (this->offsets_v2) {
return make_pair(nullptr, 0);
} else if (this->offsets_v3) {
return this->get_event_items_t<true>(this->offsets_v3->unwrap_table, event_number);
} else if (this->offsets_v4) {
return this->get_event_items_t<false>(this->offsets_v4->unwrap_table, event_number);
} else {
throw logic_error("table is not v2, v3, or v4");
}
}
size_t ItemParameterTable::price_for_item(const ItemData& item) const {
switch (item.data1[0]) {
case 0: {
+310 -155
View File
@@ -13,58 +13,167 @@
class ItemParameterTable {
public:
struct CountAndOffset {
le_uint32_t count;
le_uint32_t offset;
template <bool IsBigEndian>
struct ArrayRef {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T count;
U32T offset;
} __attribute__((packed));
struct ArrayRefLE : ArrayRef<false> {
} __attribute__((packed));
struct ArrayRefBE : ArrayRef<true> {
} __attribute__((packed));
struct ItemBase {
template <bool IsBigEndian>
struct ItemBaseV2 {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
// id specifies several things; notably, it doubles as the index of the
// item's name in the text archive (e.g. TextEnglish) collection 0.
le_uint32_t id;
le_uint16_t type;
le_uint16_t skin;
le_uint32_t team_points;
U32T id = 0xFFFFFFFF;
} __attribute__((packed));
template <bool IsBigEndian>
struct ItemBaseV3 : ItemBaseV2<IsBigEndian> {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
U16T type = 0;
U16T skin = 0;
} __attribute__((packed));
template <bool IsBigEndian>
struct ItemBaseV4 : ItemBaseV3<IsBigEndian> {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
U32T team_points = 0;
} __attribute__((packed));
struct WeaponV2 {
ItemBaseV2<false> base;
le_uint16_t class_flags = 0;
le_uint16_t atp_min = 0;
le_uint16_t atp_max = 0;
le_uint16_t atp_required = 0;
le_uint16_t mst_required = 0;
le_uint16_t ata_required = 0;
uint8_t max_grind = 0;
uint8_t photon = 0;
uint8_t special = 0;
uint8_t ata = 0;
parray<uint8_t, 4> unknown_a9;
} __attribute__((packed));
template <bool IsBigEndian>
struct WeaponV3 {
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
ItemBaseV3<IsBigEndian> base;
U16T class_flags = 0;
U16T atp_min = 0;
U16T atp_max = 0;
U16T atp_required = 0;
U16T mst_required = 0;
U16T ata_required = 0;
U16T mst = 0;
uint8_t max_grind = 0;
uint8_t photon = 0;
uint8_t special = 0;
uint8_t ata = 0;
uint8_t stat_boost = 0;
uint8_t projectile = 0;
int8_t trail1_x = 0;
int8_t trail1_y = 0;
int8_t trail2_x = 0;
int8_t trail2_y = 0;
int8_t color = 0;
uint8_t unknown_a1 = 0;
uint8_t unknown_a2 = 0;
uint8_t unknown_a3 = 0;
uint8_t unknown_a4 = 0;
uint8_t unknown_a5 = 0;
uint8_t tech_boost = 0;
uint8_t combo_type = 0;
} __attribute__((packed));
struct WeaponV4 {
ItemBaseV4<false> base;
le_uint16_t class_flags = 0x00FF;
le_uint16_t atp_min = 0;
le_uint16_t atp_max = 0;
le_uint16_t atp_required = 0;
le_uint16_t mst_required = 0;
le_uint16_t ata_required = 0;
le_uint16_t mst = 0;
uint8_t max_grind = 0;
uint8_t photon = 0;
uint8_t special = 0;
uint8_t ata = 0;
uint8_t stat_boost = 0;
uint8_t projectile = 0;
int8_t trail1_x = 0;
int8_t trail1_y = 0;
int8_t trail2_x = 0;
int8_t trail2_y = 0;
int8_t color = 0;
uint8_t unknown_a1 = 0;
uint8_t unknown_a2 = 0;
uint8_t unknown_a3 = 0;
uint8_t unknown_a4 = 0;
uint8_t unknown_a5 = 0;
uint8_t tech_boost = 0;
uint8_t combo_type = 0;
} __attribute__((packed));
template <typename BaseT, bool IsBigEndian>
struct ArmorOrShield {
ItemBase base;
le_uint16_t dfp;
le_uint16_t evp;
uint8_t block_particle;
uint8_t block_effect;
uint8_t item_class;
uint8_t unknown_a1;
uint8_t required_level;
uint8_t efr;
uint8_t eth;
uint8_t eic;
uint8_t edk;
uint8_t elt;
uint8_t dfp_range;
uint8_t evp_range;
uint8_t stat_boost;
uint8_t tech_boost;
le_uint16_t unknown_a2;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
BaseT base;
U16T dfp = 0;
U16T evp = 0;
uint8_t block_particle = 0;
uint8_t block_effect = 0;
U16T class_flags = 0x00FF;
uint8_t required_level = 0;
uint8_t efr = 0;
uint8_t eth = 0;
uint8_t eic = 0;
uint8_t edk = 0;
uint8_t elt = 0;
uint8_t dfp_range = 0;
uint8_t evp_range = 0;
uint8_t stat_boost = 0;
uint8_t tech_boost = 0;
U16T unknown_a2 = 0;
} __attribute__((packed));
struct ArmorOrShieldV2 : ArmorOrShield<ItemBaseV2<false>, false> {
} __attribute__((packed));
struct ArmorOrShieldV3 : ArmorOrShield<ItemBaseV3<true>, true> {
} __attribute__((packed));
struct ArmorOrShieldV4 : ArmorOrShield<ItemBaseV4<false>, false> {
} __attribute__((packed));
template <typename BaseT, bool IsBigEndian>
struct Unit {
ItemBase base;
le_uint16_t stat;
le_uint16_t stat_amount;
le_int16_t modifier_amount;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using S16T = typename std::conditional<IsBigEndian, be_int16_t, le_int16_t>::type;
BaseT base;
U16T stat = 0;
U16T stat_amount = 0;
S16T modifier_amount = 0;
parray<uint8_t, 2> unused;
} __attribute__((packed));
struct UnitV2 : Unit<ItemBaseV2<false>, false> {
} __attribute__((packed));
struct UnitV3 : Unit<ItemBaseV3<true>, true> {
} __attribute__((packed));
struct UnitV4 : Unit<ItemBaseV4<false>, false> {
} __attribute__((packed));
template <typename BaseT, bool IsBigEndian>
struct Mag {
ItemBase base;
le_uint16_t feed_table;
uint8_t photon_blast;
uint8_t activation;
uint8_t on_pb_full;
uint8_t on_low_hp;
uint8_t on_death;
uint8_t on_boss;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
BaseT base;
U16T feed_table = 0;
uint8_t photon_blast = 0;
uint8_t activation = 0;
uint8_t on_pb_full = 0;
uint8_t on_low_hp = 0;
uint8_t on_death = 0;
uint8_t on_boss = 0;
// These flags control how likely each effect is to activate. First, the
// game computes step_synchro as follows:
// if synchro in [0, 30], step_synchro = 0
@@ -80,172 +189,140 @@ public:
// flag == 3 => activation - 10
// flag == 4 => step_synchro - 10
// anything else => 0 (effect never occurs)
uint8_t on_pb_full_flag;
uint8_t on_low_hp_flag;
uint8_t on_death_flag;
uint8_t on_boss_flag;
uint8_t item_class;
parray<uint8_t, 3> unused;
uint8_t on_pb_full_flag = 0;
uint8_t on_low_hp_flag = 0;
uint8_t on_death_flag = 0;
uint8_t on_boss_flag = 0;
U16T class_flags = 0x00FF;
parray<uint8_t, 2> unused;
} __attribute__((packed));
struct MagV2 : Mag<ItemBaseV2<false>, false> {
} __attribute__((packed));
struct MagV3 : Mag<ItemBaseV3<true>, true> {
} __attribute__((packed));
struct MagV4 : Mag<ItemBaseV4<false>, false> {
} __attribute__((packed));
template <typename BaseT, bool IsBigEndian>
struct Tool {
ItemBase base;
le_uint16_t amount;
le_uint16_t tech;
le_int32_t cost;
uint8_t item_flag;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
using S32T = typename std::conditional<IsBigEndian, be_int32_t, le_int32_t>::type;
BaseT base;
U16T amount = 0;
U16T tech = 0;
S32T cost = 0;
uint8_t item_flag = 0;
parray<uint8_t, 3> unused;
} __attribute__((packed));
struct Weapon {
ItemBase base;
uint8_t item_class;
uint8_t unknown_a0;
le_uint16_t atp_min;
le_uint16_t atp_max;
le_uint16_t atp_required;
le_uint16_t mst_required;
le_uint16_t ata_required;
le_uint16_t mst;
uint8_t max_grind;
uint8_t photon;
uint8_t special;
uint8_t ata;
uint8_t stat_boost;
uint8_t projectile;
int8_t trail1_x;
int8_t trail1_y;
int8_t trail2_x;
int8_t trail2_y;
int8_t color;
uint8_t unknown_a1;
uint8_t unknown_a2;
uint8_t unknown_a3;
uint8_t unknown_a4;
uint8_t unknown_a5;
uint8_t tech_boost;
uint8_t combo_type;
struct ToolV2 : Tool<ItemBaseV2<false>, false> {
} __attribute__((packed));
struct ToolV3 : Tool<ItemBaseV3<true>, true> {
} __attribute__((packed));
struct ToolV4 : Tool<ItemBaseV4<false>, false> {
} __attribute__((packed));
struct MagFeedResult {
int8_t def;
int8_t pow;
int8_t dex;
int8_t mind;
int8_t iq;
int8_t synchro;
int8_t def = 0;
int8_t pow = 0;
int8_t dex = 0;
int8_t mind = 0;
int8_t iq = 0;
int8_t synchro = 0;
parray<uint8_t, 2> unused;
} __attribute__((packed));
struct MagFeedResultsList {
parray<MagFeedResult, 11> results;
} __attribute__((packed));
using MagFeedResultsList = parray<MagFeedResult, 11>;
template <bool IsBigEndian>
struct MagFeedResultsListOffsets {
parray<le_uint32_t, 8> offsets; // Offsets of MagFeedResultsList structs
} __attribute__((packed));
struct ItemStarValue {
uint8_t num_stars;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
parray<U32T, 8> offsets; // Offsets of MagFeedResultsList objects
} __attribute__((packed));
template <bool IsBigEndian>
struct Special {
le_uint16_t type;
le_uint16_t amount;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
U16T type = 0xFFFF;
U16T amount = 0;
} __attribute__((packed));
template <bool IsBigEndian>
struct StatBoost {
uint8_t stat1;
uint8_t stat2;
le_uint16_t amount1;
le_uint16_t amount2;
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
uint8_t stat1 = 0;
uint8_t stat2 = 0;
U16T amount1 = 0;
U16T amount2 = 0;
} __attribute__((packed));
struct MaxTechniqueLevels {
// Indexed as [tech_num][char_class]
parray<parray<uint8_t, 12>, 19> max_level;
} __attribute__((packed));
// Indexed as [tech_num][char_class]
using MaxTechniqueLevels = parray<parray<uint8_t, 12>, 19>;
struct ItemCombination {
parray<uint8_t, 3> used_item;
parray<uint8_t, 3> equipped_item;
parray<uint8_t, 3> result_item;
uint8_t mag_level;
uint8_t grind;
uint8_t level;
uint8_t char_class;
uint8_t mag_level = 0;
uint8_t grind = 0;
uint8_t level = 0;
uint8_t char_class = 0;
parray<uint8_t, 3> unused;
} __attribute__((packed));
template <bool IsBigEndian>
struct TechniqueBoost {
le_uint32_t tech1;
le_float boost1;
le_uint32_t tech2;
le_float boost2;
le_uint32_t tech3;
le_float boost3;
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
using FloatT = typename std::conditional<IsBigEndian, be_float, le_float>::type;
U32T tech1 = 0;
FloatT boost1 = 0.0f;
U32T tech2 = 0;
FloatT boost2 = 0.0f;
U32T tech3 = 0;
FloatT boost3 = 0.0f;
} __attribute__((packed));
struct EventItem {
parray<uint8_t, 3> item;
uint8_t probability;
uint8_t probability = 0;
} __attribute__((packed));
struct UnsealableItem {
parray<uint8_t, 3> item;
uint8_t unused;
uint8_t unused = 0;
} __attribute__((packed));
template <bool IsBigEndian>
struct NonWeaponSaleDivisors {
le_float armor_divisor;
le_float shield_divisor;
le_float unit_divisor;
le_float mag_divisor;
using FloatT = typename std::conditional<IsBigEndian, be_float, le_float>::type;
FloatT armor_divisor = 0.0f;
FloatT shield_divisor = 0.0f;
FloatT unit_divisor = 0.0f;
FloatT mag_divisor = 0.0f;
} __attribute__((packed));
struct TableOffsets {
/* 00 / 14884 */ le_uint32_t weapon_table; // -> [{count, offset -> [Weapon]}](0xED)
/* 04 / 1478C */ le_uint32_t armor_table; // -> [{count, offset -> [ArmorOrShield]}](2; armors and shields)
/* 08 / 1479C */ le_uint32_t unit_table; // -> {count, offset -> [Unit]} (last if out of range)
/* 0C / 147AC */ le_uint32_t tool_table; // -> [{count, offset -> [Tool]}](0x1A) (last if out of range)
/* 10 / 147A4 */ le_uint32_t mag_table; // -> {count, offset -> [Mag]}
/* 14 / 0F4B8 */ le_uint32_t attack_animation_table; // -> [uint8_t](0xED)
/* 18 / 0DE7C */ le_uint32_t photon_color_table; // -> [0x24-byte structs](0x20)
/* 1C / 0E194 */ le_uint32_t weapon_range_table; // -> ???
/* 20 / 0F5A8 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0xED)
/* 24 / 0F83C */ le_uint32_t sale_divisor_table; // -> NonWeaponSaleDivisors
/* 28 / 1502C */ le_uint32_t mag_feed_table; // -> MagFeedResultsTable
/* 2C / 0FB0C */ le_uint32_t star_value_table; // -> [uint8_t] (indexed by .id from weapon, armor, etc.)
/* 30 / 0FE3C */ le_uint32_t special_data_table; // -> [Special]
/* 34 / 0FEE0 */ le_uint32_t weapon_effect_table; // -> [16-byte structs]
/* 38 / 1275C */ le_uint32_t stat_boost_table; // -> [StatBoost]
/* 3C / 11C80 */ le_uint32_t shield_effect_table; // -> [8-byte structs]
/* 40 / 12894 */ le_uint32_t max_tech_level_table; // -> MaxTechniqueLevels
/* 44 / 14FF4 */ le_uint32_t combination_table; // -> {count, offset -> [ItemCombination]}
/* 48 / 12754 */ le_uint32_t unknown_a1;
/* 4C / 14278 */ le_uint32_t tech_boost_table; // -> [TechniqueBoost] (always 0x2C of them? from counts struct?)
/* 50 / 15014 */ le_uint32_t unwrap_table; // -> {count, offset -> [{count, offset -> [EventItem]}]}
/* 54 / 1501C */ le_uint32_t unsealable_table; // -> {count, offset -> [UnsealableItem]}
/* 58 / 15024 */ le_uint32_t ranged_special_table; // -> {count, offset -> [4-byte structs]}
} __attribute__((packed));
enum class Version {
V2,
V3,
V4,
};
ItemParameterTable(std::shared_ptr<const std::string> data);
ItemParameterTable(std::shared_ptr<const std::string> data, Version version);
~ItemParameterTable() = default;
const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const;
const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const;
const Unit& get_unit(uint8_t data1_2) const;
const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const;
std::pair<uint8_t, uint8_t> find_tool_by_class(uint8_t tool_class) const;
const Mag& get_mag(uint8_t data1_1) const;
const WeaponV4& get_weapon(uint8_t data1_1, uint8_t data1_2) const;
const ArmorOrShieldV4& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const;
const UnitV4& get_unit(uint8_t data1_2) const;
const MagV4& get_mag(uint8_t data1_1) const;
const ToolV4& get_tool(uint8_t data1_1, uint8_t data1_2) const;
std::pair<uint8_t, uint8_t> find_tool_by_id(uint32_t id) const;
float get_sale_divisor(uint8_t data1_0, uint8_t data1_1) const;
const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t which) const;
uint8_t get_item_stars(uint16_t slot) const;
uint8_t get_item_stars(uint32_t id) const;
uint8_t get_special_stars(uint8_t det) const;
const Special& get_special(uint8_t special) const;
const Special<false>& get_special(uint8_t special) const;
uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const;
const ItemBase& get_item_definition(const ItemData& item) const;
uint32_t get_item_id(const ItemData& item) const;
uint8_t get_item_base_stars(const ItemData& item) const;
uint8_t get_item_adjusted_stars(const ItemData& item) const;
bool is_item_rare(const ItemData& item) const;
@@ -258,14 +335,92 @@ public:
size_t price_for_item(const ItemData& item) const;
private:
struct TableOffsetsV2 {
// TODO: Is weapon count 0x89 or 0x8A? It could be that the last entry in
// weapon_table is used for ???? items.
/* 00 / 0013 */ le_uint32_t unknown_a0;
/* 04 / 5AFC */ le_uint32_t weapon_table; // -> [{count, offset -> [WeaponV2]}](0x89)
/* 08 / 5A5C */ le_uint32_t armor_table; // -> [{count, offset -> [ArmorOrShieldV2]}](2; armors and shields)
/* 0C / 5A6C */ le_uint32_t unit_table; // -> {count, offset -> [UnitV2]} (last if out of range)
/* 10 / 5A7C */ le_uint32_t tool_table; // -> [{count, offset -> [ToolV2]}](0x10) (last if out of range)
/* 14 / 5A74 */ le_uint32_t mag_table; // -> {count, offset -> [MagV2]}
/* 18 / 3DF8 */ le_uint32_t attack_animation_table; // -> [uint8_t](0x89)
/* 1C / 2E4C */ le_uint32_t photon_color_table; // -> [0x24-byte structs](0x20)
/* 20 / 32CC */ le_uint32_t weapon_range_table; // -> ???
/* 24 / 3E84 */ le_uint32_t weapon_sale_divisor_table; // -> [float](0x89)
/* 28 / 40A8 */ le_uint32_t sale_divisor_table; // -> NonWeaponSaleDivisors
/* 2C / 5F4C */ le_uint32_t mag_feed_table; // -> MagFeedResultsTable
/* 30 / 4378 */ le_uint32_t star_value_table; // -> [uint8_t](0x1C7)
/* 34 / 4540 */ le_uint32_t special_data_table; // -> [Special](0x29)
/* 38 / 45E4 */ le_uint32_t weapon_effect_table; // -> [16-byte structs]
/* 3C / 58DC */ le_uint32_t stat_boost_table; // -> [StatBoost]
/* 40 / 5704 */ le_uint32_t shield_effect_table; // -> [8-byte structs]
} __attribute__((packed));
template <bool IsBigEndian>
struct TableOffsetsV3V4 {
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::type;
/* ## / GC / BB */
/* 00 / F078 / 14884 */ U32T weapon_table; // -> [{count, offset -> [WeaponV3/WeaponV4]}](0xED)
/* 04 / EF90 / 1478C */ U32T armor_table; // -> [{count, offset -> [ArmorOrShieldV3/ArmorOrShieldV4]}](2; armors and shields)
/* 08 / EFA0 / 1479C */ U32T unit_table; // -> {count, offset -> [UnitV3/UnitV4]} (last if out of range)
/* 0C / EFB0 / 147AC */ U32T tool_table; // -> [{count, offset -> [ToolV3/ToolV4]}](0x1A) (last if out of range)
/* 10 / EFA8 / 147A4 */ U32T mag_table; // -> {count, offset -> [MagV3/MagV4]}
/* 14 / B88C / 0F4B8 */ U32T attack_animation_table; // -> [uint8_t](0xED)
/* 18 / A7FC / 0DE7C */ U32T photon_color_table; // -> [0x24-byte structs](0x20)
/* 1C / AACC / 0E194 */ U32T weapon_range_table; // -> ???
/* 20 / B938 / 0F5A8 */ U32T weapon_sale_divisor_table; // -> [float](0xED)
/* 24 / BBCC / 0F83C */ U32T sale_divisor_table; // -> NonWeaponSaleDivisors
/* 28 / F608 / 1502C */ U32T mag_feed_table; // -> MagFeedResultsTable
/* 2C / BE9C / 0FB0C */ U32T star_value_table; // -> [uint8_t](0x330) (indexed by .id from weapon, armor, etc.)
/* 30 / C100 / 0FE3C */ U32T special_data_table; // -> [Special]
/* 34 / C1A4 / 0FEE0 */ U32T weapon_effect_table; // -> [16-byte structs]
/* 38 / DE50 / 1275C */ U32T stat_boost_table; // -> [StatBoost]
/* 3C / D6E4 / 11C80 */ U32T shield_effect_table; // -> [8-byte structs]
/* 40 / DF88 / 12894 */ U32T max_tech_level_table; // -> MaxTechniqueLevels
/* 44 / F5D0 / 14FF4 */ U32T combination_table; // -> {count, offset -> [ItemCombination]}
/* 48 / DE48 / 12754 */ U32T unknown_a1;
/* 4C / EB8C / 14278 */ U32T tech_boost_table; // -> [TechniqueBoost] (always 0x2C of them? from counts struct?)
/* 50 / F5F0 / 15014 */ U32T unwrap_table; // -> {count, offset -> [{count, offset -> [EventItem]}]}
/* 54 / F5F8 / 1501C */ U32T unsealable_table; // -> {count, offset -> [UnsealableItem]}
/* 58 / F600 / 15024 */ U32T ranged_special_table; // -> {count, offset -> [4-byte structs]}
} __attribute__((packed));
std::shared_ptr<const std::string> data;
StringReader r;
const TableOffsets* offsets;
const TableOffsetsV2* offsets_v2;
const TableOffsetsV3V4<true>* offsets_v3;
const TableOffsetsV3V4<false>* offsets_v4;
// These are unused if offsets_v4 is not null (in that case, we just return
// references pointing inside the data string)
mutable std::unordered_map<uint16_t, WeaponV4> parsed_weapons;
mutable std::vector<ArmorOrShieldV4> parsed_armors;
mutable std::vector<ArmorOrShieldV4> parsed_shields;
mutable std::vector<UnitV4> parsed_units;
mutable std::vector<MagV4> parsed_mags;
mutable std::unordered_map<uint16_t, ToolV4> parsed_tools;
mutable std::vector<Special<false>> parsed_specials;
// Key is used_item. We can't index on (used_item, equipped_item) because
// equipped_item may contain wildcards, and the matching order matters.
void populate_item_combination_index() const;
mutable std::map<uint32_t, std::vector<ItemCombination>> item_combination_index;
template <typename ToolT, bool IsBigEndian>
std::pair<uint8_t, uint8_t> find_tool_by_id_t(uint32_t tool_table_offset, uint32_t id) const;
template <bool IsBigEndian>
float get_sale_divisor_t(uint32_t weapon_table_offset, uint32_t non_weapon_table_offset, uint8_t data1_0, uint8_t data1_1) const;
template <bool IsBigEndian>
std::pair<const ItemParameterTable::EventItem*, size_t> get_event_items_t(uint32_t base_offset, uint8_t event_number) const;
size_t num_weapon_classes;
size_t num_tool_classes;
size_t item_stars_first_id;
size_t item_stars_last_id;
size_t special_stars_begin_index;
size_t num_specials;
size_t first_rare_mag_index;
size_t star_value_table_size;
};
class MagEvolutionTable {
+13 -8
View File
@@ -24,7 +24,8 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
// Nothing to do (it should be deleted)
} else if (item_identifier == 0x030200) { // Technique disk
uint8_t max_level = s->item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]);
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]);
if (item.data.data1[2] > max_level) {
throw runtime_error("technique level too high");
}
@@ -35,7 +36,8 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
throw runtime_error("incorrect grinder value");
}
auto& weapon = player->inventory.items[player->inventory.find_equipped_weapon()];
auto weapon_def = s->item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
if (weapon.data.data1[3] >= weapon_def.max_grind) {
throw runtime_error("weapon already at maximum grind");
}
@@ -159,7 +161,8 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
} else if ((item_identifier & 0xFFFF00) == 0x031500) {
// Christmas Present, etc. - use unwrap_table + probabilities therein
auto table = s->item_parameter_table->get_event_items(item.data.data1[2]);
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
auto table = item_parameter_table->get_event_items(item.data.data1[2]);
size_t sum = 0;
for (size_t z = 0; z < table.second; z++) {
sum += table.first[z].probability;
@@ -197,8 +200,8 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
continue;
}
try {
const auto& combo = s->item_parameter_table->get_item_combination(
item.data, inv_item.data);
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data);
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.char_class) {
throw runtime_error("item combination requires specific char_class");
}
@@ -269,8 +272,9 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
auto& mag_item = player->inventory.items[mag_item_index];
size_t result_index = result_index_for_fed_item.at(fed_item.data.primary_identifier());
const auto& mag_def = s->item_parameter_table->get_mag(mag_item.data.data1[1]);
const auto& feed_result = s->item_parameter_table->get_mag_feed_result(mag_def.feed_table, result_index);
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
const auto& mag_def = item_parameter_table->get_mag(mag_item.data.data1[1]);
const auto& feed_result = item_parameter_table->get_mag_feed_result(mag_def.feed_table, result_index);
auto update_stat = +[](ItemData& data, size_t which, int8_t delta) -> void {
uint16_t existing_stat = data.data1w[which] % 100;
@@ -477,7 +481,8 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
// If the mag has evolved, add its new photon blast
if (mag_number != mag_item.data.data1[1]) {
const auto& new_mag_def = s->item_parameter_table->get_mag(mag_item.data.data1[1]);
auto item_parameter_table = s->item_parameter_table_for_version(c->version());
const auto& new_mag_def = item_parameter_table->get_mag(mag_item.data.data1[1]);
mag_item.data.add_mag_photon_blast(new_mag_def.photon_blast);
}
}
+3 -2
View File
@@ -66,7 +66,7 @@ void Lobby::create_item_creator() {
s->tool_random_set,
s->weapon_random_sets.at(this->difficulty),
s->tekker_adjustment_set,
s->item_parameter_table,
s->item_parameter_table_for_version(this->base_version),
this->episode,
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
this->difficulty,
@@ -180,13 +180,14 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
// If the lobby is a game and item tracking is enabled, assign the inventory's
// item IDs
if (this->is_game() && (this->flags & Lobby::Flag::ITEM_TRACKING_ENABLED)) {
auto s = this->require_server_state();
auto p = c->game_data.player();
auto& inv = p->inventory;
size_t count = min<uint8_t>(inv.num_items, 30);
for (size_t x = 0; x < count; x++) {
inv.items[x].data.id = this->generate_item_id(c->lobby_client_id);
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
}
// If the lobby is recording a battle record, add the player join event
+28 -8
View File
@@ -1551,20 +1551,25 @@ int main(int argc, char** argv) {
}
case Behavior::FORMAT_RARE_ITEM_SET: {
auto name_index = make_shared<ItemNameIndex>(
JSON::parse(load_file("system/item-tables/names-v2.json")),
JSON::parse(load_file("system/item-tables/names-v3.json")),
JSON::parse(load_file("system/item-tables/names-v4.json")));
shared_ptr<string> data(new string(read_input_data()));
shared_ptr<RareItemSet> rs;
if (json) {
rs.reset(new JSONRareItemSet(JSON::parse(read_input_data())));
rs.reset(new JSONRareItemSet(JSON::parse(read_input_data()), cli_version, name_index));
} else {
rs.reset(new RELRareItemSet(data));
}
auto format_drop = +[](const RareItemSet::ExpandedDrop& r) -> string {
auto format_drop = [&](const RareItemSet::ExpandedDrop& r) -> string {
ItemData item;
item.data1[0] = r.item_code[0];
item.data1[1] = r.item_code[1];
item.data1[2] = r.item_code[2];
string name = item.name(false);
string name = name_index->describe_item(cli_version, item);
auto frac = reduce_fraction<uint64_t>(r.probability, 0x100000000);
return string_printf(
@@ -1631,6 +1636,11 @@ int main(int argc, char** argv) {
case Behavior::CONVERT_ITEMRT_REL_TO_JSON:
case Behavior::CONVERT_ITEMRT_GSL_TO_JSON:
case Behavior::CONVERT_ITEMRT_AFS_TO_JSON: {
auto name_index = make_shared<ItemNameIndex>(
JSON::parse(load_file("system/item-tables/names-v2.json")),
JSON::parse(load_file("system/item-tables/names-v3.json")),
JSON::parse(load_file("system/item-tables/names-v4.json")));
shared_ptr<string> data(new string(read_input_data()));
unique_ptr<RareItemSet> rs;
if (behavior == Behavior::CONVERT_ITEMRT_GSL_TO_JSON) {
@@ -1693,7 +1703,7 @@ int main(int argc, char** argv) {
data.data1[0] = spec.item_code[0];
data.data1[1] = spec.item_code[1];
data.data1[2] = spec.item_code[2];
id_json = data.name(false);
id_json = name_index->describe_item(cli_version, data);
} else {
id_json = primary_identifier;
}
@@ -1723,7 +1733,7 @@ int main(int argc, char** argv) {
data.data1[0] = spec.item_code[0];
data.data1[1] = spec.item_code[1];
data.data1[2] = spec.item_code[2];
id_json = data.name(false);
id_json = name_index->describe_item(cli_version, data);
} else {
id_json = primary_identifier;
}
@@ -1759,6 +1769,11 @@ int main(int argc, char** argv) {
}
case Behavior::DESCRIBE_ITEM: {
auto name_index = make_shared<ItemNameIndex>(
JSON::parse(load_file("system/item-tables/names-v2.json")),
JSON::parse(load_file("system/item-tables/names-v3.json")),
JSON::parse(load_file("system/item-tables/names-v4.json")));
string data = parse_data_string(input_filename);
ItemData item;
@@ -1771,14 +1786,19 @@ int main(int argc, char** argv) {
}
}
string desc = item.name(false);
string desc = name_index->describe_item(cli_version, item);
log_info("Item: %s", desc.c_str());
break;
}
case Behavior::ENCODE_ITEM: {
ItemData item(input_filename, false);
string desc = item.name(false);
auto name_index = make_shared<ItemNameIndex>(
JSON::parse(load_file("system/item-tables/names-v2.json")),
JSON::parse(load_file("system/item-tables/names-v3.json")),
JSON::parse(load_file("system/item-tables/names-v4.json")));
ItemData item = name_index->parse_item_description(cli_version, input_filename);
string desc = name_index->describe_item(cli_version, item);
log_info("Data: %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX %02hhX%02hhX%02hhX%02hhX -------- %02hhX%02hhX%02hhX%02hhX",
item.data1[0], item.data1[1], item.data1[2], item.data1[3],
item.data1[4], item.data1[5], item.data1[6], item.data1[7],
+2 -2
View File
@@ -482,12 +482,12 @@ void SavedPlayerDataBB::clear_all_material_usage() {
}
}
void SavedPlayerDataBB::print_inventory(FILE* stream) const {
void SavedPlayerDataBB::print_inventory(FILE* stream, GameVersion version, shared_ptr<const ItemNameIndex> name_index) const {
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", this->disp.stats.meseta.load());
fprintf(stream, "[PlayerInventory] %hhu items\n", this->inventory.num_items);
for (size_t x = 0; x < this->inventory.num_items; x++) {
const auto& item = this->inventory.items[x];
auto name = item.data.name(false);
auto name = name_index->describe_item(version, item.data);
auto hex = item.data.hex();
fprintf(stream, "[PlayerInventory] %2zu: %s (%s)\n", x, hex.c_str(), name.c_str());
}
+2 -1
View File
@@ -11,6 +11,7 @@
#include "Episode3/DataIndexes.hh"
#include "ItemCreator.hh"
#include "ItemNameIndex.hh"
#include "LevelTable.hh"
#include "PlayerSubordinates.hh"
#include "Text.hh"
@@ -72,7 +73,7 @@ struct SavedPlayerDataBB { // .nsc file format
void set_material_usage(MaterialType which, uint8_t usage);
void clear_all_material_usage();
void print_inventory(FILE* stream) const;
void print_inventory(FILE* stream, GameVersion version, std::shared_ptr<const ItemNameIndex> name_index) const;
} __attribute__((packed));
enum AccountFlag {
+5 -2
View File
@@ -222,7 +222,7 @@ const RELRareItemSet::Table& RELRareItemSet::get_table(
return tables[(ep_index * 10 * 4) + (difficulty * 10) + secid];
}
JSONRareItemSet::JSONRareItemSet(const JSON& json) {
JSONRareItemSet::JSONRareItemSet(const JSON& json, GameVersion version, shared_ptr<const ItemNameIndex> name_index) {
for (const auto& mode_it : json.as_dict()) {
static const unordered_map<string, GameMode> mode_keys(
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
@@ -285,7 +285,10 @@ JSONRareItemSet::JSONRareItemSet(const JSON& json) {
d.item_code[1] = (item_code >> 8) & 0xFF;
d.item_code[2] = item_code & 0xFF;
} else if (item_desc.is_string()) {
ItemData data(item_desc.as_string());
if (!name_index) {
throw runtime_error("item name index is not available");
}
ItemData data = name_index->parse_item_description(version, item_desc.as_string());
d.item_code[0] = data.data1[0];
d.item_code[1] = data.data1[1];
d.item_code[2] = data.data1[2];
+3 -1
View File
@@ -10,8 +10,10 @@
#include "AFSArchive.hh"
#include "GSLArchive.hh"
#include "ItemNameIndex.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
class RareItemSet {
public:
@@ -109,7 +111,7 @@ private:
class JSONRareItemSet : public RareItemSet {
public:
explicit JSONRareItemSet(const JSON& json);
explicit JSONRareItemSet(const JSON& json, GameVersion version, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
virtual ~JSONRareItemSet() = default;
virtual std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
+2 -2
View File
@@ -2145,11 +2145,11 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
if (vq->battle_rules) {
lc->game_data.create_battle_overlay(vq->battle_rules, s->level_table);
lc->log.info("Created battle overlay");
lc->game_data.player()->print_inventory(stderr);
lc->game_data.player()->print_inventory(stderr, c->version(), s->item_name_index);
} else if (vq->challenge_template_index >= 0) {
lc->game_data.create_challenge_overlay(vq->challenge_template_index, s->level_table);
lc->log.info("Created challenge overlay");
lc->game_data.player()->print_inventory(stderr);
lc->game_data.player()->print_inventory(stderr, c->version(), s->item_name_index);
}
// If an overlay was created, item IDs need to be assigned
+60 -47
View File
@@ -687,15 +687,16 @@ static void on_player_drop_item(shared_ptr<Client> c, uint8_t command, uint8_t f
auto item = p->remove_item(cmd.item_id, 0, c->version() != GameVersion::BB);
l->add_item(item, cmd.area, cmd.x, cmd.z);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu dropped item %08" PRIX32 " (%s) at %hu:(%g, %g)",
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), cmd.area.load(), cmd.x.load(), cmd.z.load());
if (c->options.debug) {
string name = item.name(true);
auto name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5DROP %08" PRIX32 "\n%s",
cmd.item_id.load(), name.c_str());
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
}
forward_subcommand(c, command, flag, data, size);
@@ -744,13 +745,14 @@ static void on_create_inventory_item_t(shared_ptr<Client> c, uint8_t command, ui
l->on_item_id_generated_externally(c->lobby_client_id, item.id);
p->add_item(item);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu created inventory item %08" PRIX32 " (%s)", c->lobby_client_id, item.id.load(), name.c_str());
if (c->options.debug) {
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5CREATE %08" PRIX32 "\n%s", item.id.load(), name.c_str());
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
}
forward_subcommand_with_mag_transcode_t(c, command, flag, cmd);
@@ -787,15 +789,16 @@ static void on_drop_partial_stack_t(shared_ptr<Client> c, uint8_t command, uint8
l->on_item_id_generated_externally(c->lobby_client_id, item.id);
l->add_item(item, cmd.area, cmd.x, cmd.z);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu split stack to create floor item %08" PRIX32 " (%s) at %hu:(%g, %g)",
cmd.header.client_id.load(), item.id.load(), name.c_str(),
cmd.area.load(), cmd.x.load(), cmd.z.load());
if (c->options.debug) {
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5SPLIT %08" PRIX32 "\n%s", item.id.load(), name.c_str());
}
c->game_data.player()->print_inventory(stderr);
c->game_data.player()->print_inventory(stderr, c->version(), s->item_name_index);
}
forward_subcommand_with_mag_transcode_t(c, command, flag, cmd);
@@ -841,16 +844,17 @@ static void on_drop_partial_stack_bb(shared_ptr<Client> c, uint8_t command, uint
l->add_item(item, cmd.area, cmd.x, cmd.z);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu split stack %08" PRIX32 " (removed: %s) at %hu:(%g, %g)",
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(),
cmd.area.load(), cmd.x.load(), cmd.z.load());
if (c->options.debug) {
string name = item.name(true);
auto name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5SPLIT/BB %08" PRIX32 "\n%s",
cmd.item_id.load(), name.c_str());
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
send_drop_stacked_item(l, item, cmd.area, cmd.x, cmd.z);
@@ -878,16 +882,16 @@ static void on_buy_shop_item(shared_ptr<Client> c, uint8_t command, uint8_t flag
l->on_item_id_generated_externally(c->lobby_client_id, item.id);
p->add_item(item);
size_t price = s->item_parameter_table->price_for_item(item);
auto name = item.name(false);
size_t price = s->item_parameter_table_for_version(c->version())->price_for_item(item);
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu bought item %08" PRIX32 " (%s) from shop (%zu Meseta)",
cmd.header.client_id.load(), item.id.load(), name.c_str(), price);
if (c->options.debug) {
string name = item.name(true);
auto name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5BUY %08" PRIX32 "\n%s", item.id.load(), name.c_str());
}
p->remove_meseta(price, c->version() != GameVersion::BB);
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
}
forward_subcommand_with_mag_transcode_t(c, command, flag, cmd);
@@ -917,11 +921,12 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
l->on_item_id_generated_externally(c->lobby_client_id, item.id);
l->add_item(item, cmd.item.area, cmd.item.x, cmd.item.z);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hhu (leader) created floor item %08" PRIX32 " (%s) at %hhu:(%g, %g)",
l->leader_id, item.id.load(), name.c_str(), cmd.item.area, cmd.item.x.load(), cmd.item.z.load());
if (c->options.debug) {
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5DROP %08" PRIX32 "\n%s", item.id.load(), name.c_str());
}
}
@@ -971,14 +976,15 @@ static void on_pick_up_item(shared_ptr<Client> c, uint8_t command, uint8_t flag,
auto item = l->remove_item(cmd.item_id);
effective_p->add_item(item);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu picked up %08" PRIX32 " (%s)",
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
if (c->options.debug) {
string name = item.name(true);
auto name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5PICK %08" PRIX32 "\n%s", cmd.item_id.load(), name.c_str());
}
effective_p->print_inventory(stderr);
effective_p->print_inventory(stderr, c->version(), s->item_name_index);
}
forward_subcommand(c, command, flag, data, size);
@@ -1002,15 +1008,16 @@ static void on_pick_up_item_request(shared_ptr<Client> c, uint8_t command, uint8
auto item = l->remove_item(cmd.item_id);
p->add_item(item);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hu picked up (BB) %08" PRIX32 " (%s)",
cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
if (c->options.debug) {
string name = item.name(true);
auto name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5PICK/BB %08" PRIX32 "\n%s",
cmd.item_id.load(), name.c_str());
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
send_pick_up_item(c, cmd.item_id, cmd.area);
@@ -1055,6 +1062,7 @@ static void on_use_item(
auto l = c->require_lobby();
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
auto s = c->require_server_state();
auto p = c->game_data.player();
size_t index = p->inventory.find_item(cmd.item_id);
string name, colored_name;
@@ -1062,8 +1070,8 @@ static void on_use_item(
// Note: We do this weird scoping thing because player_use_item will
// likely delete the item, which will break the reference here.
const auto& item = p->inventory.items[index].data;
name = item.name(false);
colored_name = item.name(true);
name = s->describe_item(c->version(), item, false);
colored_name = s->describe_item(c->version(), item, true);
}
player_use_item(c, index);
@@ -1073,7 +1081,7 @@ static void on_use_item(
send_text_message_printf(c, "$C5USE %08" PRIX32 "\n%s",
cmd.item_id.load(), colored_name.c_str());
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
}
forward_subcommand(c, command, flag, data, size);
@@ -1092,6 +1100,7 @@ static void on_feed_mag(
auto l = c->require_lobby();
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
auto s = c->require_server_state();
auto p = c->game_data.player();
size_t mag_index = p->inventory.find_item(cmd.mag_item_id);
@@ -1101,11 +1110,11 @@ static void on_feed_mag(
// Note: We do this weird scoping thing because player_use_item will
// likely delete the item, which will break the reference here.
const auto& fed_item = p->inventory.items[fed_index].data;
fed_name = fed_item.name(false);
fed_colored_name = fed_item.name(true);
fed_name = s->describe_item(c->version(), fed_item, false);
fed_colored_name = s->describe_item(c->version(), fed_item, true);
const auto& mag_item = p->inventory.items[mag_index].data;
mag_name = mag_item.name(false);
mag_colored_name = mag_item.name(true);
mag_name = s->describe_item(c->version(), mag_item, false);
mag_colored_name = s->describe_item(c->version(), mag_item, true);
}
player_feed_mag(c, mag_index, fed_index);
@@ -1125,7 +1134,7 @@ static void on_feed_mag(
cmd.fed_item_id.load(), fed_colored_name.c_str(),
cmd.mag_item_id.load(), mag_colored_name.c_str());
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
}
forward_subcommand(c, command, flag, data, size);
@@ -1163,7 +1172,7 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<Client> c, uint8_t com
}
for (auto& item : c->game_data.shop_contents[cmd.shop_type]) {
item.id = l->generate_item_id(c->lobby_client_id);
item.data2d = s->item_parameter_table->price_for_item(item);
item.data2d = s->item_parameter_table_for_version(c->version())->price_for_item(item);
}
send_shop(c, cmd.shop_type);
@@ -1507,7 +1516,7 @@ static void on_steal_exp_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*
((weapon.data.data1[1] < 0x0D) && (weapon.data.data1[2] < 0x04))) {
special = weapon.data.data1[4] & 0x3F;
} else {
special = s->item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]).special;
special = s->item_parameter_table_for_version(c->version())->get_weapon(weapon.data.data1[1], weapon.data.data1[2]).special;
}
if (special >= 0x09 && special <= 0x0B) {
@@ -1598,7 +1607,7 @@ static void on_enemy_killed_bb(shared_ptr<Client> c, uint8_t command, uint8_t fl
for (size_t z = 0; z < inventory.num_items; z++) {
auto& item = inventory.items[z];
if ((item.flags & 0x08) &&
s->item_parameter_table->is_unsealable_item(item.data)) {
s->item_parameter_table_for_version(c->version())->is_unsealable_item(item.data)) {
item.data.set_sealed_item_kill_count(item.data.get_sealed_item_kill_count() + 1);
}
}
@@ -1649,17 +1658,18 @@ static void on_destroy_inventory_item(shared_ptr<Client> c, uint8_t command, uin
}
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
auto s = c->require_server_state();
auto p = c->game_data.player();
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
auto name = item.name(false);
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)",
c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str());
if (c->options.debug) {
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5DESTROY %08" PRIX32 "\n%s",
cmd.item_id.load(), name.c_str());
}
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
forward_subcommand(c, command, flag, data, size);
}
}
@@ -1673,12 +1683,13 @@ static void on_destroy_ground_item(shared_ptr<Client> c, uint8_t command, uint8_
}
if (l->flags & Lobby::Flag::ITEM_TRACKING_ENABLED) {
auto s = c->require_server_state();
auto item = l->remove_item(cmd.item_id);
auto name = item.name(false);
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hhu destroyed floor item %08" PRIX32 " (%s)",
c->lobby_client_id, cmd.item_id.load(), name.c_str());
if (c->options.debug) {
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5DESTROY/GND %08" PRIX32 "\n%s",
cmd.item_id.load(), name.c_str());
}
@@ -1751,17 +1762,18 @@ static void on_sell_item_at_shop_bb(shared_ptr<Client> c, uint8_t command, uint8
throw logic_error("item tracking not enabled in BB game");
}
auto s = c->require_server_state();
auto p = c->game_data.player();
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != GameVersion::BB);
size_t price = (s->item_parameter_table->price_for_item(item) >> 3) * cmd.amount;
size_t price = (s->item_parameter_table_for_version(c->version())->price_for_item(item) >> 3) * cmd.amount;
p->add_meseta(price);
auto name = item.name(false);
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hhu sold inventory item %08" PRIX32 " (%s) for %zu Meseta",
c->lobby_client_id, cmd.item_id.load(), name.c_str(), price);
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
if (c->options.debug) {
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5DESTROY/SELL %08" PRIX32 "\n+%zu Meseta\n%s",
cmd.item_id.load(), price, name.c_str());
}
@@ -1795,12 +1807,13 @@ static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const vo
p->add_item(item);
send_create_inventory_item(c, item);
auto name = item.name(false);
auto s = c->require_server_state();
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hhu purchased item %08" PRIX32 " (%s) for %zu meseta",
c->lobby_client_id, cmd.inventory_item_id.load(), name.c_str(), price);
p->print_inventory(stderr);
p->print_inventory(stderr, c->version(), s->item_name_index);
if (c->options.debug) {
string name = item.name(true);
string name = s->describe_item(c->version(), item, true);
send_text_message_printf(c, "$C5CREATE/BUY %08" PRIX32 "\n-%zu Meseta\n%s",
cmd.inventory_item_id.load(), price, name.c_str());
}
+4 -3
View File
@@ -815,20 +815,21 @@ Proxy session commands:\n\
throw runtime_error("proxy session is not game leader");
}
ItemData item(command_args);
auto s = session->require_server_state();
ItemData item = s->item_name_index->parse_item_description(session->version(), command_args);
item.id = random_object<uint32_t>();
if (command_name == "set-next-item") {
session->next_drop_item = item;
string name = session->next_drop_item.name(true);
string name = s->describe_item(session->version(), session->next_drop_item, true);
send_text_message(session->client_channel, "$C7Next drop:\n" + name);
} else {
send_drop_stacked_item(session->client_channel, item, session->area, session->x, session->z);
send_drop_stacked_item(session->server_channel, item, session->area, session->x, session->z);
string name = item.name(true);
string name = s->describe_item(session->version(), session->next_drop_item, true);
send_text_message(session->client_channel, "$C7Item created:\n" + name);
}
+44 -7
View File
@@ -398,6 +398,28 @@ const vector<pair<string, uint16_t>>& ServerState::redirect_destinations_for_ver
}
}
std::shared_ptr<const ItemParameterTable> ServerState::item_parameter_table_for_version(GameVersion version) const {
switch (version) {
case GameVersion::DC:
case GameVersion::PC:
return this->item_parameter_table_v2;
case GameVersion::GC:
case GameVersion::XB:
return this->item_parameter_table_v3;
case GameVersion::BB:
return this->item_parameter_table_v4;
default:
throw out_of_range("no item parameter table exists for this version");
}
}
std::string ServerState::describe_item(GameVersion version, const ItemData& item, bool include_color_codes) const {
return this->item_name_index->describe_item(
version,
item,
include_color_codes ? this->item_parameter_table_for_version(version) : nullptr);
}
void ServerState::set_port_configuration(
const vector<PortConfiguration>& port_configs) {
this->name_to_port_config.clear();
@@ -926,6 +948,12 @@ void ServerState::load_word_select_table() {
}
void ServerState::load_item_tables() {
config_log.info("Loading item name index");
this->item_name_index.reset(new ItemNameIndex(
JSON::parse(load_file("system/item-tables/names-v2.json")),
JSON::parse(load_file("system/item-tables/names-v3.json")),
JSON::parse(load_file("system/item-tables/names-v4.json"))));
config_log.info("Loading rare item sets");
for (const auto& filename : list_directory_sorted("system/item-tables")) {
if (!starts_with(filename, "rare-table-")) {
@@ -936,9 +964,15 @@ void ServerState::load_item_tables() {
size_t ext_offset = filename.rfind('.');
string basename = (ext_offset == string::npos) ? filename : filename.substr(0, ext_offset);
if (ends_with(filename, ".json")) {
config_log.info("Loading JSON rare item table %s", filename.c_str());
this->rare_item_sets.emplace(basename, new JSONRareItemSet(JSON::parse(load_file(path))));
if (ends_with(filename, "-v2.json")) {
config_log.info("Loading v2 JSON rare item table %s", filename.c_str());
this->rare_item_sets.emplace(basename, new JSONRareItemSet(JSON::parse(load_file(path)), GameVersion::PC, this->item_name_index));
} else if (ends_with(filename, "-v3.json")) {
config_log.info("Loading v3 JSON rare item table %s", filename.c_str());
this->rare_item_sets.emplace(basename, new JSONRareItemSet(JSON::parse(load_file(path)), GameVersion::GC, this->item_name_index));
} else if (ends_with(filename, "-v4.json")) {
config_log.info("Loading v4 JSON rare item table %s", filename.c_str());
this->rare_item_sets.emplace(basename, new JSONRareItemSet(JSON::parse(load_file(path)), GameVersion::BB, this->item_name_index));
} else if (ends_with(filename, ".afs")) {
config_log.info("Loading AFS rare item table %s", filename.c_str());
@@ -1006,10 +1040,13 @@ void ServerState::load_item_tables() {
"system/item-tables/JudgeItem-gc.rel")));
this->tekker_adjustment_set.reset(new TekkerAdjustmentSet(tekker_data));
config_log.info("Loading item definition table");
shared_ptr<string> pmt_data(new string(prs_decompress(load_file(
"system/item-tables/ItemPMT-bb.prs"))));
this->item_parameter_table.reset(new ItemParameterTable(pmt_data));
config_log.info("Loading item definition tables");
shared_ptr<string> pmt_data_v2(new string(prs_decompress(load_file("system/item-tables/ItemPMT-v2.prs"))));
this->item_parameter_table_v2.reset(new ItemParameterTable(pmt_data_v2, ItemParameterTable::Version::V2));
shared_ptr<string> pmt_data_v3(new string(prs_decompress(load_file("system/item-tables/ItemPMT-gc.prs"))));
this->item_parameter_table_v3.reset(new ItemParameterTable(pmt_data_v3, ItemParameterTable::Version::V3));
shared_ptr<string> pmt_data_v4(new string(prs_decompress(load_file("system/item-tables/ItemPMT-bb.prs"))));
this->item_parameter_table_v4.reset(new ItemParameterTable(pmt_data_v4, ItemParameterTable::Version::V4));
config_log.info("Loading mag evolution table");
shared_ptr<string> mag_data(new string(prs_decompress(load_file(
+8 -1
View File
@@ -15,6 +15,7 @@
#include "Episode3/Tournament.hh"
#include "FunctionCompiler.hh"
#include "GSLArchive.hh"
#include "ItemNameIndex.hh"
#include "ItemParameterTable.hh"
#include "LevelTable.hh"
#include "License.hh"
@@ -106,8 +107,11 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const ToolRandomSet> tool_random_set;
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
std::shared_ptr<const ItemParameterTable> item_parameter_table;
std::shared_ptr<const ItemParameterTable> item_parameter_table_v2;
std::shared_ptr<const ItemParameterTable> item_parameter_table_v3;
std::shared_ptr<const ItemParameterTable> item_parameter_table_v4;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table;
std::shared_ptr<const ItemNameIndex> item_name_index;
std::shared_ptr<const WordSelectTable> word_select_table;
std::shared_ptr<Episode3::TournamentIndex> ep3_tournament_index;
@@ -214,6 +218,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const Menu> proxy_destinations_menu_for_version(GameVersion version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(GameVersion version) const;
std::shared_ptr<const ItemParameterTable> item_parameter_table_for_version(GameVersion version) const;
std::string describe_item(GameVersion version, const ItemData& item, bool include_color_codes) const;
std::shared_ptr<const QuestIndex> quest_index_for_client(std::shared_ptr<Client> c) const;
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
BIN
View File
Binary file not shown.
Binary file not shown.
+549
View File
@@ -0,0 +1,549 @@
{
"000100": "Saber",
"000101": "Brand",
"000102": "Buster",
"000103": "Pallasch",
"000104": "Gladius",
"000105": "DB'S SABER",
"000106": "KALADBOLG",
"000107": "DURANDAL",
"000200": "Sword",
"000201": "Gigush",
"000202": "Breaker",
"000203": "Claymore",
"000204": "Calibur",
"000205": "FLOWEN'S SWORD",
"000206": "LAST SURVIVOR",
"000207": "DRAGON SLAYER",
"000300": "Dagger",
"000301": "Knife",
"000302": "Blade",
"000303": "Edge",
"000304": "Ripper",
"000305": "BLADE DANCE",
"000306": "BLOODY ART",
"000307": "CROSS SCAR",
"000400": "Partisan",
"000401": "Halbert",
"000402": "Glaive",
"000403": "Berdys",
"000404": "Gungnir",
"000405": "BRIONAC",
"000406": "VJAYA",
"000407": "GAE BOLG",
"000500": "Slicer",
"000501": "Spinner",
"000502": "Cutter",
"000503": "Sawcer",
"000504": "Diska",
"000505": "SLICER OF ASSASSIN",
"000506": "DISKA OF LIBERATOR",
"000507": "DISKA OF BRAVEMAN",
"000600": "Handgun",
"000601": "Autogun",
"000602": "Lockgun",
"000603": "Railgun",
"000604": "Raygun",
"000605": "VARISTA",
"000606": "CUSTOM RAY ver.OO",
"000607": "BRAVACE",
"000700": "Rifle",
"000701": "Sniper",
"000702": "Blaster",
"000703": "Beam",
"000704": "Laser",
"000705": "VISK-235W",
"000706": "WALS-MK2",
"000707": "JUSTY-23ST",
"000800": "Mechgun",
"000801": "Assault",
"000802": "Repeater",
"000803": "Gatling",
"000804": "Vulcan",
"000805": "M&A60 VISE",
"000806": "H&S25 JUSTICE",
"000807": "L&K14 COMBAT",
"000900": "Shot",
"000901": "Spread",
"000902": "Cannon",
"000903": "Launcher",
"000904": "Arms",
"000905": "CRUSH BULLET",
"000906": "METEOR SMASH",
"000907": "FINAL IMPACT",
"000A00": "Cane",
"000A01": "Stick",
"000A02": "Mace",
"000A03": "Club",
"000A04": "CLUB OF LACONIUM",
"000A05": "MACE OF ADAMAN",
"000A06": "CLUB OF ZUMIURAN",
"000B00": "Rod",
"000B01": "Pole",
"000B02": "Pillar",
"000B03": "Striker",
"000B04": "BATTLE VERGE",
"000B05": "BRAVE HAMMER",
"000B06": "ALIVE AQHU",
"000C00": "Wand",
"000C01": "Staff",
"000C02": "Baton",
"000C03": "Scepter",
"000C04": "FIRE SCEPTER:AGNI",
"000C05": "ICE STAFF:DAGON",
"000C06": "STORM WAND:INDRA",
"000D00": "PHOTON CLAW",
"000D01": "SILENCE CLAW",
"000D02": "NEI'S CLAW",
"000E00": "DOUBLE SABER",
"000E01": "STAG CUTLERY",
"000E02": "TWIN BRAND",
"000F00": "BRAVE KNUCKLE",
"000F01": "ANGRY FIST",
"000F02": "GOD HAND",
"000F03": "SONIC KNUCKLE",
"000F04": "OROTIAGITO",
"001000": "OROTIAGITO",
"001001": "AGITO",
"001002": "AGITO",
"001003": "AGITO",
"001004": "AGITO",
"001005": "AGITO",
"001006": "AGITO",
"001100": "SOUL EATER",
"001101": "SOUL BANISH",
"001200": "SPREAD NEEDLE",
"001300": "HOLY RAY",
"001400": "INFERNO BAZOOKA",
"001500": "FLAME VISIT",
"001600": "AKIKO'S FRYING PAN",
"001700": "C-SORCERER'S CANE",
"001800": "S-BEAT'S BLADE",
"001900": "P-ARMS'S BLADE",
"001A00": "DELSABER'S BUSTER",
"001B00": "C-BRINGER'S RIFLE",
"001C00": "EGG BLASTER",
"001D00": "PSYCHO WAND",
"001E00": "HEAVEN PUNISHER",
"001F00": "LAVIS CANNON",
"002000": "VICTOR AXE",
"002100": "CHAIN SAWD",
"002200": "CADUCEUS",
"002300": "STING TIP",
"002400": "MAGICAL PIECE",
"002500": "TECHNICAL CROZIER",
"002600": "SUPPRESSED GUN",
"002700": "ANCIENT SABER",
"002800": "HARISEN BATTLE FAN",
"002900": "YAMIGARASU",
"002A00": "AKIKO'S WOK",
"002B00": "TOY HAMMER",
"002C00": "ELYSION",
"002D00": "RED SABER",
"002E00": "METEOR CUDGEL",
"002F00": "MONKEY KING BAR",
"003000": "DOUBLE CANNON",
"003100": "HUGE BATTLE FAN",
"003200": "TSUMIKIRI J-SWORD",
"003300": "SEALED J-SWORD",
"003400": "RED SWORD",
"003500": "CRAZY TUNE",
"003600": "TWIN CHAKRAM",
"003700": "WOK OF AKIKO'S SHOP",
"003800": "LAVIS BLADE",
"003900": "RED DAGGER",
"003A00": "MADAM'S PARASOL",
"003B00": "MADAM'S UMBRELLA",
"003C00": "IMPERIAL PICK",
"003D00": "BERDYSH",
"003E00": "RED PARTISAN",
"003F00": "FLIGHT CUTTER",
"004000": "FLIGHT FAN",
"004100": "RED SLICER",
"004200": "HANDGUN:GULD",
"004300": "HANDGUN:MILLA",
"004400": "RED HANDGUN",
"004500": "FROZEN SHOOTER",
"004600": "ANTI ANDROID RIFLE",
"004700": "ROCKET PUNCH",
"004800": "SAMBA MARACAS",
"004900": "TWIN PSYCHOGUN",
"004A00": "DRILL LAUNCHER",
"004B00": "GULD MILLA",
"004C00": "RED MECHGUN",
"004D00": "BERLA CANNON",
"004E00": "PANZER FAUST",
"004F00": "SUMMIT MOON",
"005000": "WINDMILL",
"005100": "EVIL CURST",
"005200": "FLOWER CANE",
"005300": "HILDEBEAR'S CANE",
"005400": "HILDEBLUE'S CANE",
"005500": "RABBIT WAND",
"005600": "PLANTAIN LEAF",
"005700": "DEMONIC FORK",
"005800": "STRIKER OF CHAO",
"005900": "BROOM",
"005A00": "PROPHETS OF MOTAV",
"005B00": "THE SIGH OF A GOD",
"005C00": "TWINKLE STAR",
"005D00": "PLANTAIN FAN",
"005E00": "TWIN BLAZE",
"005F00": "MARINA'S BAG",
"006000": "DRAGON'S CLAW",
"006100": "PANTHER'S CLAW",
"006200": "S-RED'S BLADE",
"006300": "PLANTAIN HUGE FAN",
"006400": "CHAMELEON SCYTHE",
"006500": "YASMINKOV 3000R",
"006600": "ANO RIFLE",
"006700": "BARANZ LAUNCHER",
"006800": "BRANCH OF PAKUPAKU",
"006900": "HEART OF POUMN",
"006A00": "YASMINKOV 2000H",
"006B00": "YASMINKOV 7000V",
"006C00": "YASMINKOV 9200M",
"006D00": "MASER BEAM",
"006E00": "GAME MAGAZNE",
"006F00": "FLOWER BOUQUET",
"007000": "SABER",
"007100": "SWORD",
"007200": "BLADE",
"007300": "PARTISAN",
"007400": "SLICER",
"007500": "GUN",
"007600": "RIFLE",
"007700": "MECHGUN",
"007800": "SHOT",
"007900": "CANE",
"007A00": "ROD",
"007B00": "WAND",
"007C00": "TWIN",
"007D00": "CLAW",
"007E00": "BAZOOKA",
"007F00": "NEEDLE",
"008000": "SCYTHE",
"008100": "HAMMER",
"008200": "MOON",
"008300": "PSYCHOGUN",
"008400": "PUNCH",
"008500": "WINDMILL",
"008600": "HARISEN",
"008700": "J-BLADE",
"008800": "J-CUTTER",
"010100": "Frame",
"010101": "Armor",
"010102": "Psy Armor",
"010103": "Giga Frame",
"010104": "Soul Frame",
"010105": "Cross Armor",
"010106": "Solid Frame",
"010107": "Brave Armor",
"010108": "Hyper Frame",
"010109": "Grand Armor",
"01010A": "Shock Frame",
"01010B": "King's Frame",
"01010C": "Dragon Frame",
"01010D": "Absorb Armor",
"01010E": "Protect Frame",
"01010F": "General Armor",
"010110": "Perfect Frame",
"010111": "Valiant Frame",
"010112": "Imperial Armor",
"010113": "Holiness Armor",
"010114": "Guardian Armor",
"010115": "Divinity Armor",
"010116": "Ultimate Frame",
"010117": "Celestial Armor",
"010118": "HUNTER FIELD",
"010119": "RANGER FIELD",
"01011A": "FORCE FIELD",
"01011B": "REVIVAL GARMENT",
"01011C": "SPIRIT GARMENT",
"01011D": "STINK FRAME",
"01011E": "D-PARTS ver1.01",
"01011F": "D-PARTS ver2.10",
"010120": "PARASITE WEAR:De Rol",
"010121": "PARASITE WEAR:Nelgal",
"010122": "PARASITE WEAR:Vajulla",
"010123": "SENSE PLATE",
"010124": "GRAVITON PLATE",
"010125": "ATTRIBUTE PLATE",
"010126": "FLOWEN'S FRAME",
"010127": "CUSTOM FRAME ver.OO",
"010128": "DB'S ARMOR",
"010129": "GUARD WAVE",
"01012A": "DF FIELD",
"01012B": "LUMINOUS FIELD",
"01012C": "CHU CHU FEVER",
"01012D": "LOVE HEART",
"01012E": "FLAME GARMENT",
"01012F": "VIRUS ARMOR:Lafuteria",
"010130": "BRIGHTNESS CIRCLE",
"010131": "AURA FIELD",
"010132": "ELECTRO FRAME",
"010133": "SACRED CLOTH",
"010134": "SMOKING PLATE",
"010200": "Barrier",
"010201": "Shield",
"010202": "Core Shield",
"010203": "Giga Shield",
"010204": "Soul Barrier",
"010205": "Hard Shield",
"010206": "Brave Barrier",
"010207": "Solid Shield",
"010208": "Flame Barrier",
"010209": "Plasma Barrier",
"01020A": "Freeze Barrier",
"01020B": "Psychic Barrier",
"01020C": "General Shield",
"01020D": "Protect Barrier",
"01020E": "Glorious Shield",
"01020F": "Imperial Barrier",
"010210": "Guardian Shield",
"010211": "Divinity Barrier",
"010212": "Ultimate Shield",
"010213": "Spiritual Shield",
"010214": "Celestial Shield",
"010215": "INVISIBLE GUARD",
"010216": "SACRED GUARD",
"010217": "S-PARTS ver1.16",
"010218": "S-PARTS ver2.01",
"010219": "LIGHT RELIEF",
"01021A": "SHIELD OF DELSABER",
"01021B": "FORCE WALL",
"01021C": "RANGER WALL",
"01021D": "HUNTER WALL",
"01021E": "ATTRIBUTE WALL",
"01021F": "SECRET GEAR",
"010220": "COMBAT GEAR",
"010221": "PROTO REGENE GEAR",
"010222": "REGENERATE GEAR",
"010223": "REGENE GEAR ADV.",
"010224": "FLOWEN'S SHIELD",
"010225": "CUSTOM BARRIER ver.OO",
"010226": "DB'S SHIELD",
"010227": "RED RING",
"010228": "TRIPOLIC SHIELD",
"010229": "STANDSTILL SHIELD",
"01022A": "SAFETY HEART",
"01022B": "KASAMI BRACER",
"01022C": "GODS SHIELD SUZAKU",
"01022D": "GODS SHIELD GENBU",
"01022E": "GODS SHIELD BYAKKO",
"01022F": "GODS SHIELD SEIRYU",
"010230": "HANTER'S SHELL",
"010231": "RIKO'S GLASSES",
"010232": "RIKO'S EARRING",
"010233": "BLUE RING",
"010234": "YELLOW RING",
"010235": "SECURE FEET",
"010236": "PURPLE RING",
"010237": "GREEN RING",
"010238": "BLACK RING",
"010239": "WHITE RING",
"010300": "Knight/Power",
"010301": "General/Power",
"010302": "Ogre/Power",
"010303": "God/Power",
"010304": "Priest/Mind",
"010305": "General/Mind",
"010306": "Angel/Mind",
"010307": "God/Mind",
"010308": "Marksman/Arm",
"010309": "General/Arm",
"01030A": "Elf/Arm",
"01030B": "God/Arm",
"01030C": "Thief/Legs",
"01030D": "General/Legs",
"01030E": "Elf/Legs",
"01030F": "God/Legs",
"010310": "Digger/HP",
"010311": "General/HP",
"010312": "Dragon/HP",
"010313": "God/HP",
"010314": "Magician/TP",
"010315": "General/TP",
"010316": "Angel/TP",
"010317": "God/TP",
"010318": "Warrior/Body",
"010319": "General/Body",
"01031A": "Metal/Body",
"01031B": "God/Body",
"01031C": "Angel/Luck",
"01031D": "God/Luck",
"01031E": "Master/Ability",
"01031F": "Hero/Ability",
"010320": "God/Ability",
"010321": "Resist/Fire",
"010322": "Resist/Flame",
"010323": "Resist/Burning",
"010324": "Resist/Cold",
"010325": "Resist/Freeze",
"010326": "Resist/Blizzard",
"010327": "Resist/Shock",
"010328": "Resist/Thunder",
"010329": "Resist/Storm",
"01032A": "Resist/Light",
"01032B": "Resist/Saint",
"01032C": "Resist/Holy",
"01032D": "Resist/Dark",
"01032E": "Resist/Evil",
"01032F": "Resist/Devil",
"010330": "All/Resist",
"010331": "Super/Resist",
"010332": "Perfect/Resist",
"010333": "HP/Restorate",
"010334": "HP/Generate",
"010335": "HP/Revival",
"010336": "TP/Restorate",
"010337": "TP/Generate",
"010338": "TP/Revival",
"010339": "PB/Amplifier",
"01033A": "PB/Generate",
"01033B": "PB/Create",
"01033C": "Wizard/Technique",
"01033D": "Devil/Technique",
"01033E": "God/Technique",
"01033F": "General/Battle",
"010340": "Devil/Battle",
"010341": "God/Battle",
"010342": "State/Maintenance",
"010343": "Trap/Search",
"020000": "Mag",
"020100": "Varuna",
"020200": "Mitra",
"020300": "Surya",
"020400": "Vayu",
"020500": "Varaha",
"020600": "Kama",
"020700": "Ushasu",
"020800": "Apsaras",
"020900": "Kumara",
"020A00": "Kaitabha",
"020B00": "Tapas",
"020C00": "Bhirava",
"020D00": "Kalki",
"020E00": "Rudra",
"020F00": "Marutah",
"021000": "Yaksa",
"021100": "Sita",
"021200": "Garuda",
"021300": "Nandin",
"021400": "Ashvinau",
"021500": "Ribhava",
"021600": "Soma",
"021700": "Ila",
"021800": "Durga",
"021900": "Vritra",
"021A00": "Namuci",
"021B00": "Sumba",
"021C00": "Naga",
"021D00": "Pitri",
"021E00": "Kabanda",
"021F00": "Ravana",
"022000": "Marica",
"022100": "Soniti",
"022200": "Preta",
"022300": "Andhaka",
"022400": "Bana",
"022500": "Naraka",
"022600": "Madhu",
"022700": "Churel",
"022800": "ROBOCHAO",
"022900": "OPA-OPA",
"022A00": "PIAN",
"022B00": "CHAO",
"022C00": "CHU CHU",
"022D00": "KAPU KAPU",
"022E00": "ANGEL'S WING",
"022F00": "DEVIL'S WING",
"023000": "ELENOR",
"023100": "MARK3",
"023200": "MASTER SYSTEM",
"023300": "GENESIS",
"023400": "SEGA SATURN",
"023500": "DREAMCAST",
"023600": "HAMBURGER",
"023700": "PANZER'S TAIL",
"023800": "DAVIL'S TAIL",
"030000": "Monomate",
"030001": "Dimate",
"030002": "Trimate",
"030100": "Monofluid",
"030101": "Difluid",
"030102": "Trifluid",
"030200": "<TECH-DISK>",
"030300": "Sol Atomizer",
"030400": "Moon Atomizer",
"030500": "Star Atomizer",
"030600": "Antidote",
"030601": "Antiparalysis",
"030700": "Telepipe",
"030800": "Trap Vision",
"030900": "Scape Doll",
"030A00": "Monogrinder",
"030A01": "Digrinder",
"030A02": "Trigrinder",
"030B00": "Power Material",
"030B01": "Mind Material",
"030B02": "Evade Material",
"030B03": "HP Material",
"030B04": "TP Material",
"030B05": "Def Material",
"030B06": "Hit Material",
"030B07": "Luck Material",
"030C00": "Cell of MAG 502",
"030C01": "Cell of MAG 213",
"030C02": "Parts of RoboChao",
"030C03": "Heart of Opa Opa",
"030C04": "Heart of Pian",
"030C05": "Heart of Chao",
"030D00": "Sorcerer's Right Arm",
"030D01": "S-beat's Arms",
"030D02": "P-arm's Arms",
"030D03": "Delsaber's Right Arm",
"030D04": "C-bringer's Right Arm",
"030D05": "Delsabre's Left Arm",
"030D06": "Book of KATANA1",
"030D07": "Book of KATANA2",
"030D08": "Book of KATANA3",
"030E00": "S-red's Arms",
"030E01": "Dragon's Claw",
"030E02": "Hildebear's Head",
"030E03": "Hildeblue's Head",
"030E04": "Parts of Baranz",
"030E05": "Belra's Right Arm",
"030E06": "Joint Parts",
"030E07": "Weapons Bronze Badge",
"030E08": "Weapons Silver Badge",
"030E09": "Weapons Gold Badge",
"030E0A": "Weapons Crystal Badge",
"030E0B": "Weapons Steel Badge",
"030E0C": "Weapons Aluminum Badge",
"030E0D": "Weapons Leather Badge",
"030E0E": "Weapons Bone Badge",
"030E0F": "Letter of appreciation",
"030E10": "Autograph Album",
"030E11": "High-level Mag Cell, Eno",
"030E12": "High-level Mag Armor, Uru",
"030E13": "Special Gene Flou",
"030E14": "Sound Source FM",
"030E15": "Parts of \"68000\"",
"030E16": "SH2",
"030E17": "SH4",
"030E18": "Modem",
"030E19": "Power VR",
"030E1A": "Glory in the past",
"030E1B": "Valentine's Chocolate",
"030E1C": "New Year's Card",
"030E1D": "Christmas Card",
"030E1E": "Birthday Card",
"030E1F": "Proof of Sonic Team",
"030E20": "Special Event Ticket",
"030E21": "Flower Bouquet",
"030E22": "Cake",
"030E23": "Accessories",
"030E24": "Mr.Naka's Business Card",
"030F00": "????"
}
+1264
View File
File diff suppressed because it is too large Load Diff
+1518
View File
File diff suppressed because it is too large Load Diff