support v2 and v3 ItemPMT files
This commit is contained in:
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Executable
BIN
Binary file not shown.
Binary file not shown.
Executable
+549
@@ -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": "????"
|
||||
}
|
||||
Executable
+1264
File diff suppressed because it is too large
Load Diff
Executable
+1518
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user