implement item translation table
This commit is contained in:
@@ -94,6 +94,7 @@ set(SOURCES
|
||||
src/ItemData.cc
|
||||
src/ItemNameIndex.cc
|
||||
src/ItemParameterTable.cc
|
||||
src/ItemTranslationTable.cc
|
||||
src/Items.cc
|
||||
src/LevelTable.cc
|
||||
src/Lobby.cc
|
||||
|
||||
@@ -133,6 +133,50 @@ uint32_t ItemData::primary_identifier() const {
|
||||
}
|
||||
}
|
||||
|
||||
void ItemData::change_primary_identifier(uint32_t primary_identifier) {
|
||||
if (((primary_identifier >> 18) & 0xFF) != this->data1[0]) {
|
||||
throw std::runtime_error("cannot change item class via change_primary_identifier");
|
||||
}
|
||||
switch (this->data1[0]) {
|
||||
case 0x00: { // Weapon
|
||||
bool was_s_rank_weapon = this->is_s_rank_weapon();
|
||||
// Apply data1[1], and data1[2] if it's not an ES weapon
|
||||
this->data1[1] = (primary_identifier >> 16) & 0xFF;
|
||||
if (!this->is_s_rank_weapon()) {
|
||||
this->data1[2] = (primary_identifier >> 8) & 0xFF;
|
||||
} else if (!was_s_rank_weapon) {
|
||||
// If it wasn't an S-rank weapon before and now is, clear its special
|
||||
this->data1[2] = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x01: // Armor/shield/unit
|
||||
// Apply data1[1] and data1[2]
|
||||
this->data1[1] = (primary_identifier >> 16) & 0xFF;
|
||||
this->data1[2] = (primary_identifier >> 8) & 0xFF;
|
||||
break;
|
||||
case 0x02: // Mag
|
||||
// Apply data1[1] only
|
||||
this->data1[1] = (primary_identifier >> 16) & 0xFF;
|
||||
break;
|
||||
case 0x03: // Tool
|
||||
// Apply data1[1] and data1[2] (or data1[4] if it's a tech disk)
|
||||
this->data1[1] = (primary_identifier >> 16) & 0xFF;
|
||||
if (this->data1[1] == 0x02) {
|
||||
this->data1[4] = (primary_identifier >> 8) & 0xFF;
|
||||
this->data1[2] = primary_identifier & 0xFF;
|
||||
} else {
|
||||
this->data1[2] = (primary_identifier >> 8) & 0xFF;
|
||||
}
|
||||
break;
|
||||
case 0x04: // Meseta
|
||||
// Nothing to apply
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("invalid item class");
|
||||
}
|
||||
}
|
||||
|
||||
bool ItemData::is_wrapped(const StackLimits& limits) const {
|
||||
switch (this->data1[0]) {
|
||||
case 0:
|
||||
|
||||
@@ -154,6 +154,7 @@ struct ItemData {
|
||||
std::string hex() const;
|
||||
std::string short_hex() const;
|
||||
uint32_t primary_identifier() const;
|
||||
void change_primary_identifier(uint32_t primary_identifier);
|
||||
|
||||
bool is_wrapped(const StackLimits& limits) const;
|
||||
void wrap(const StackLimits& limits, uint8_t present_color);
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "ItemTranslationTable.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static constexpr bool is_canonical(uint32_t id) {
|
||||
return !(id & 0x80000000);
|
||||
}
|
||||
static constexpr uint32_t make_canonical(uint32_t id) {
|
||||
return (id & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
ItemTranslationTable::ItemTranslationTable(
|
||||
const phosg::JSON& json,
|
||||
const std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS>& item_parameter_tables) {
|
||||
// Parse the table and build the indexes
|
||||
const auto& l = json.as_list();
|
||||
for (size_t z = 0; z < l.size(); z++) {
|
||||
const auto& e = this->entries.emplace_back(*l[z]);
|
||||
|
||||
bool has_any_canonical_id = false;
|
||||
for (size_t v_s = 0; v_s < NUM_NON_PATCH_VERSIONS; v_s++) {
|
||||
uint32_t id = e.id_for_version[v_s];
|
||||
if (is_canonical(id)) {
|
||||
has_any_canonical_id = true;
|
||||
if (!this->entry_index_for_version[v_s].emplace(id, z).second) {
|
||||
throw runtime_error(phosg::string_printf("(row %zu) duplicate canonical ID %08" PRIX32, z, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_any_canonical_id) {
|
||||
throw runtime_error(phosg::string_printf("(row %zu) no canonical ID present in row", z));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the table by checking that:
|
||||
// - Each non-canonical ID points to an existing canonical ID
|
||||
// - Each canonical ID has an entry in the item parameter table
|
||||
// - All items in the parameter tables are represented in the translation table
|
||||
for (size_t v_s = 0; v_s < NUM_NON_PATCH_VERSIONS; v_s++) {
|
||||
Version v = static_cast<Version>(v_s + NUM_PATCH_VERSIONS);
|
||||
auto item_parameter_table = item_parameter_tables.at(v_s + NUM_PATCH_VERSIONS);
|
||||
auto remaining_identifiers = item_parameter_table->compute_all_valid_primary_identifiers();
|
||||
|
||||
const auto& entry_index = this->entry_index_for_version[v_s];
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
uint32_t e_id = this->entries[z].id_for_version[v_s];
|
||||
if (is_canonical(e_id)) {
|
||||
if (!entry_index.count(e_id)) {
|
||||
throw logic_error(phosg::string_printf("(row %zu version %s) canonical ID %" PRIX32 " is missing from the index", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
try {
|
||||
item_parameter_table->definition_for_primary_identifier(e_id);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(phosg::string_printf("(row %zu version %s) ID %" PRIX32 " not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
if (!remaining_identifiers.erase(e_id)) {
|
||||
throw runtime_error(phosg::string_printf("(row %zu version %s) ID %" PRIX32 " not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
} else if (!entry_index.count(make_canonical(e_id))) {
|
||||
throw runtime_error(phosg::string_printf("(row %zu version %s) ID %" PRIX32 " refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
}
|
||||
|
||||
if (!remaining_identifiers.empty()) {
|
||||
string missing_str = phosg::string_printf("(version %s) not all identifiers in the item parameter table are defined in the translation table; missing:", phosg::name_for_enum(v));
|
||||
for (uint32_t id : remaining_identifiers) {
|
||||
missing_str += phosg::string_printf(" %08" PRIX32, id);
|
||||
}
|
||||
throw runtime_error(missing_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
phosg::JSON ItemTranslationTable::json() const {
|
||||
auto ret = phosg::JSON::list();
|
||||
for (const auto& entry : this->entries) {
|
||||
ret.emplace_back(entry.json());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t ItemTranslationTable::translate(uint32_t primary_identifier, Version from_version, Version to_version) const {
|
||||
if (from_version == to_version) {
|
||||
return primary_identifier;
|
||||
}
|
||||
const auto& entry_index = this->entry_index_for_version.at(static_cast<size_t>(from_version) - NUM_PATCH_VERSIONS);
|
||||
size_t entry_num = entry_index.at(primary_identifier);
|
||||
const auto& entry = this->entries.at(entry_num);
|
||||
return make_canonical(entry.id_for_version.at(static_cast<size_t>(to_version) - NUM_PATCH_VERSIONS));
|
||||
}
|
||||
|
||||
ItemTranslationTable::Entry::Entry(const phosg::JSON& json) {
|
||||
const auto& l = json.as_list();
|
||||
if (l.size() != NUM_NON_PATCH_VERSIONS + 1) {
|
||||
throw runtime_error("list length is incorrect");
|
||||
}
|
||||
for (size_t z = 0; z < NUM_NON_PATCH_VERSIONS; z++) {
|
||||
this->id_for_version[z] = l[z]->as_int();
|
||||
}
|
||||
this->name = l[NUM_NON_PATCH_VERSIONS]->as_string();
|
||||
}
|
||||
|
||||
phosg::JSON ItemTranslationTable::Entry::json() const {
|
||||
auto ret = phosg::JSON::list();
|
||||
for (size_t z = 0; z < NUM_NON_PATCH_VERSIONS; z++) {
|
||||
ret.emplace_back(this->id_for_version[z]);
|
||||
}
|
||||
ret.emplace_back(this->name);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "Types.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class ItemTranslationTable {
|
||||
public:
|
||||
ItemTranslationTable(
|
||||
const phosg::JSON& json,
|
||||
const std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS>& item_parameter_tables);
|
||||
~ItemTranslationTable() = default;
|
||||
|
||||
phosg::JSON json() const;
|
||||
|
||||
uint32_t translate(uint32_t primary_identifier, Version from_version, Version to_version) const;
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
std::array<uint32_t, NUM_NON_PATCH_VERSIONS> id_for_version;
|
||||
std::string name;
|
||||
|
||||
explicit Entry(const phosg::JSON& json);
|
||||
phosg::JSON json() const;
|
||||
};
|
||||
std::vector<Entry> entries;
|
||||
std::array<std::unordered_map<uint32_t, size_t>, NUM_NON_PATCH_VERSIONS> entry_index_for_version;
|
||||
};
|
||||
@@ -2081,6 +2081,9 @@ void ServerState::load_item_definitions(bool from_non_event_thread) {
|
||||
new_item_parameter_tables[v_s] = make_shared<ItemParameterTable>(data, v);
|
||||
}
|
||||
|
||||
auto json = phosg::JSON::parse(phosg::load_file("system/item-tables/translation-table.json"));
|
||||
auto new_item_translation_table = make_shared<ItemTranslationTable>(json, new_item_parameter_tables);
|
||||
|
||||
// TODO: We should probably load the tables for other versions too.
|
||||
config_log.info("Loading v1/v2 mag evolution table");
|
||||
auto mag_data_v1_v2 = make_shared<string>(prs_decompress(phosg::load_file("system/item-tables/ItemMagEdit-dc-v2.prs")));
|
||||
@@ -2094,10 +2097,12 @@ void ServerState::load_item_definitions(bool from_non_event_thread) {
|
||||
|
||||
auto set = [s = this->shared_from_this(),
|
||||
new_item_parameter_tables = std::move(new_item_parameter_tables),
|
||||
new_item_translation_table = std::move(new_item_translation_table),
|
||||
new_table_v1_v2 = std::move(new_table_v1_v2),
|
||||
new_table_v3 = std::move(new_table_v3),
|
||||
new_table_v4 = std::move(new_table_v4)]() {
|
||||
s->item_parameter_tables = std::move(new_item_parameter_tables);
|
||||
s->item_translation_table = std::move(new_item_translation_table);
|
||||
s->mag_evolution_table_v1_v2 = std::move(new_table_v1_v2);
|
||||
s->mag_evolution_table_v3 = std::move(new_table_v3);
|
||||
s->mag_evolution_table_v4 = std::move(new_table_v4);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "IPV4RangeSet.hh"
|
||||
#include "ItemNameIndex.hh"
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "ItemTranslationTable.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Menu.hh"
|
||||
@@ -203,6 +204,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
|
||||
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
|
||||
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
|
||||
std::shared_ptr<const ItemTranslationTable> item_translation_table;
|
||||
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v1_v2;
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v3;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user