From 7ac7d7c360fe33c36f3d168409ae210bcbd46df3 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 18 Jun 2023 22:51:08 -0700 Subject: [PATCH] implement JSON rare tables --- src/ItemCreator.cc | 46 ++++++--- src/ItemCreator.hh | 4 +- src/Main.cc | 76 +++------------ src/RareItemSet.cc | 197 ++++++++++++++++++++++++++++++++++---- src/RareItemSet.hh | 61 +++++++++--- src/ReceiveSubcommands.cc | 10 +- 6 files changed, 278 insertions(+), 116 deletions(-) diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index c2580019..39247e1b 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -31,8 +31,6 @@ ItemCreator::ItemCreator( item_parameter_table(item_parameter_table), pt(&this->common_item_set->get_table( this->episode, this->mode, this->difficulty, this->section_id)), - rt(&this->rare_item_set->get_table( - this->episode, this->mode, this->difficulty, this->section_id)), restrictions(restrictions), random_crypt(random_seed) { print_data(stderr, this->pt, sizeof(*this->pt)); @@ -232,13 +230,17 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item( return item; } - for (size_t z = 0; z < this->rt->box_count; z++) { - if (area_norm + 1 == this->rt->box_areas[z]) { - item = this->check_rate_and_create_rare_item(this->rt->box_rares[z]); - if (!item.empty()) { - break; - } + auto rare_specs = this->rare_item_set->get_box_specs( + this->mode, this->episode, this->difficulty, this->section_id, area_norm + 1); + for (const auto& spec : rare_specs) { + item = this->check_rate_and_create_rare_item(spec); + if (!item.empty()) { + this->log.info("Box spec %08" PRIX32 " => %02hhX%02hhX%02hhX produced an item", + spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); + break; } + this->log.info("Box spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce", + spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); } return item; } @@ -275,24 +277,38 @@ bool ItemCreator::should_allow_meseta_drops() const { ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item( uint32_t enemy_type) { + ItemData item; if (this->are_rare_drops_allowed() && (enemy_type > 0) && (enemy_type < 0x58)) { - return this->check_rate_and_create_rare_item( - this->rt->monster_rares[enemy_type]); - } else { - return ItemData(); + // Note: In the original implementation, enemies can only have one possible + // rare drop. In our implementation, they can have multiple rare drops if + // JSONRareItemSet is used (the other RareItemSet implementations never + // return multiple drops for an enemy type). + auto rare_specs = this->rare_item_set->get_enemy_specs( + this->mode, this->episode, this->difficulty, this->section_id, enemy_type); + for (const auto& spec : rare_specs) { + item = this->check_rate_and_create_rare_item(spec); + if (!item.empty()) { + this->log.info("Enemy spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce", + spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); + break; + } + this->log.info("Enemy spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce", + spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); + } } + return item; } ItemData ItemCreator::check_rate_and_create_rare_item( - const RareItemSet::Table::Drop& drop) { + const RareItemSet::ExpandedDrop& drop) { if (drop.probability == 0) { return ItemData(); } // Note: The original code uses 0xFFFFFFFF as the maximum here. We use // 0x100000000 instead, which makes all rare items SLIGHTLY more rare. - if (this->rand_int(0x100000000) >= this->rare_item_set->expand_rate(drop.probability)) { + if (this->rand_int(0x100000000) >= drop.probability) { return ItemData(); } @@ -1689,4 +1705,6 @@ ItemData ItemCreator::on_specialized_box_item_drop(uint32_t def0, uint32_t def1, default: throw runtime_error("invalid item class"); } + + return item; } diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index 4e1cb3d5..da33e4f4 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -86,7 +86,6 @@ private: std::shared_ptr weapon_random_set; std::shared_ptr item_parameter_table; const CommonItemSet::Table* pt; - const RareItemSet::Table* rt; std::shared_ptr restrictions; std::shared_ptr item_drop_sub; @@ -116,8 +115,7 @@ private: ItemData check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type); ItemData check_rare_specs_and_create_rare_box_item(uint8_t area_norm); - ItemData check_rate_and_create_rare_item( - const RareItemSet::Table::Drop& drop); + ItemData check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop); void generate_rare_weapon_bonuses(ItemData& item, uint32_t random_sample); void deduplicate_weapon_bonuses(ItemData& item) const; diff --git a/src/Main.cc b/src/Main.cc index fb68188d..c727c753 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -221,7 +221,6 @@ enum class Behavior { DECODE_SJIS, EXTRACT_GSL, EXTRACT_BML, - FORMAT_ITEMRT_ENTRY, FORMAT_ITEMRT_REL, SHOW_EP3_DATA, DESCRIBE_ITEM, @@ -251,7 +250,6 @@ static bool behavior_takes_input_filename(Behavior b) { (b == Behavior::ENCRYPT_GCI_SAVE) || (b == Behavior::DECODE_QUEST_FILE) || (b == Behavior::DECODE_SJIS) || - (b == Behavior::FORMAT_ITEMRT_ENTRY) || (b == Behavior::FORMAT_ITEMRT_REL) || (b == Behavior::EXTRACT_GSL) || (b == Behavior::EXTRACT_BML) || @@ -429,8 +427,6 @@ int main(int argc, char** argv) { quest_file_type = QuestFileFormat::QST; } else if (!strcmp(argv[x], "cat-client")) { behavior = Behavior::CAT_CLIENT; - } else if (!strcmp(argv[x], "format-itemrt-entry")) { - behavior = Behavior::FORMAT_ITEMRT_ENTRY; } else if (!strcmp(argv[x], "format-itemrt-rel")) { behavior = Behavior::FORMAT_ITEMRT_REL; } else if (!strcmp(argv[x], "show-ep3-data")) { @@ -1009,70 +1005,24 @@ int main(int argc, char** argv) { break; } - case Behavior::FORMAT_ITEMRT_ENTRY: { - string data = read_input_data(); - if (data.size() < sizeof(RareItemSet::Table)) { - throw runtime_error("input data too small"); - } - const auto& table = *reinterpret_cast(data.data()); - - auto format_drop = +[](const RareItemSet::Table::Drop& 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); - - uint32_t expanded_probability = RareItemSet::expand_rate(r.probability); - auto frac = reduce_fraction(expanded_probability, 0x100000000); - return string_printf( - "(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)", - r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str()); - }; - - fprintf(stdout, "Monster rares:\n"); - for (size_t z = 0; z < 0x65; z++) { - const auto& r = table.monster_rares[z]; - if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { - continue; - } - string s = format_drop(r); - fprintf(stdout, " %02zX: %s\n", z, s.c_str()); - } - - fprintf(stdout, "Box rares:\n"); - for (size_t z = 0; z < 0x1E; z++) { - const auto& r = table.box_rares[z]; - if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { - continue; - } - string s = format_drop(r); - fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str()); - } - break; - } - case Behavior::FORMAT_ITEMRT_REL: { shared_ptr data(new string(read_input_data())); RELRareItemSet rs(data); - auto format_drop = +[](const RareItemSet::Table::Drop& 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); - uint32_t expanded_probability = RareItemSet::expand_rate(r.probability); - auto frac = reduce_fraction(expanded_probability, 0x100000000); + auto frac = reduce_fraction(r.probability, 0x100000000); return string_printf( - "(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)", - r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str()); + "(%08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)", + r.probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str()); }; auto print_collection = [&](GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id) -> void { - const auto& table = rs.get_table(episode, mode, difficulty, section_id); - string secid_name = name_for_section_id(section_id); fprintf(stdout, "%s %s %s %s\n", name_for_mode(mode), @@ -1082,22 +1032,18 @@ int main(int argc, char** argv) { fprintf(stdout, " Monster rares:\n"); for (size_t z = 0; z < 0x65; z++) { - const auto& r = table.monster_rares[z]; - if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { - continue; + for (const auto& spec : rs.get_enemy_specs(mode, episode, difficulty, section_id, z)) { + string s = format_drop(spec); + fprintf(stdout, " %02zX: %s\n", z, s.c_str()); } - string s = format_drop(r); - fprintf(stdout, " %02zX: %s\n", z, s.c_str()); } fprintf(stdout, " Box rares:\n"); - for (size_t z = 0; z < 0x1E; z++) { - const auto& r = table.box_rares[z]; - if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) { - continue; + for (size_t area = 0; area < 0x12; area++) { + for (const auto& spec : rs.get_box_specs(mode, episode, difficulty, section_id, area)) { + string s = format_drop(spec); + fprintf(stdout, " (area %02zX) %s\n", area, s.c_str()); } - string s = format_drop(r); - fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str()); } }; diff --git a/src/RareItemSet.cc b/src/RareItemSet.cc index 0946c797..65f60065 100644 --- a/src/RareItemSet.cc +++ b/src/RareItemSet.cc @@ -3,11 +3,12 @@ #include #include +#include "BattleParamsIndex.hh" #include "StaticGameData.hh" using namespace std; -uint32_t RareItemSet::expand_rate(uint8_t pc) { +uint32_t RareItemSet::PackedDrop::expand_rate(uint8_t pc) { int8_t shift = ((pc >> 3) & 0x1F) - 4; if (shift < 0) { shift = 0; @@ -15,15 +16,40 @@ uint32_t RareItemSet::expand_rate(uint8_t pc) { return ((2 << shift) * ((pc & 7) + 7)); } -bool RareItemSet::sample(mt19937& random, uint8_t pc) { - return (random() < RareItemSet::expand_rate(pc)); +RareItemSet::ExpandedDrop::ExpandedDrop() : probability(0) { + this->item_code[0] = 0; + this->item_code[1] = 0; + this->item_code[2] = 0; } -GSLRareItemSet::GSLRareItemSet(shared_ptr data, bool is_big_endian) - : gsl(data, is_big_endian) {} +RareItemSet::ExpandedDrop::ExpandedDrop(const PackedDrop& d) + : probability(PackedDrop::expand_rate(d.probability)) { + this->item_code[0] = d.item_code[0]; + this->item_code[1] = d.item_code[1]; + this->item_code[2] = d.item_code[2]; +} -const GSLRareItemSet::Table& GSLRareItemSet::get_table( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { +std::vector GSLRareItemSet::Table::get_enemy_specs(uint8_t enemy_type) const { + vector ret; + if (this->monster_rares[enemy_type].item_code[0] != 0 || + this->monster_rares[enemy_type].item_code[1] != 0 || + this->monster_rares[enemy_type].item_code[2] != 0) { + ret.emplace_back(this->monster_rares[enemy_type]); + } + return ret; +} + +std::vector GSLRareItemSet::Table::get_box_specs(uint8_t area) const { + vector ret; + for (size_t z = 0; z < 0x1E; z++) { + if (this->box_areas[z] == area) { + ret.emplace_back(this->box_rares[z]); + } + } + return ret; +} + +uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) { if (difficulty > 3) { throw logic_error("incorrect difficulty"); } @@ -31,20 +57,71 @@ const GSLRareItemSet::Table& GSLRareItemSet::get_table( throw logic_error("incorrect section id"); } - if ((episode != Episode::EP1) && (episode != Episode::EP2)) { - throw runtime_error("invalid episode"); + uint16_t key = ((difficulty & 3) << 4) | (secid & 0x0F); + switch (mode) { + case GameMode::NORMAL: + break; + case GameMode::BATTLE: + key |= 0x0040; + break; + case GameMode::CHALLENGE: + key |= 0x0080; + break; + case GameMode::SOLO: + key |= 0x00C0; + break; + default: + throw logic_error("invalid episode in RareItemSet"); } + switch (episode) { + case Episode::EP1: + break; + case Episode::EP2: + key |= 0x0100; + break; + case Episode::EP4: + key |= 0x0200; + break; + default: + throw logic_error("invalid episode in RareItemSet"); + } + return key; +} - string filename = string_printf("ItemRT%s%s%c%1d.rel", - ((mode == GameMode::CHALLENGE) ? "c" : ""), - ((episode == Episode::EP2) ? "l" : ""), - tolower(abbreviation_for_difficulty(difficulty)), // One of "nhvu" - secid); - auto entry = this->gsl.get(filename); - if (entry.second < sizeof(Table)) { - throw runtime_error(string_printf("table %s is too small", filename.c_str())); +GSLRareItemSet::GSLRareItemSet(shared_ptr data, bool is_big_endian) + : gsl(data, is_big_endian) { + const array episodes = {Episode::EP1, Episode::EP2}; + const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; + for (GameMode mode : modes) { + for (Episode episode : episodes) { + for (size_t difficulty = 0; difficulty < 3; difficulty++) { + for (size_t section_id = 0; section_id < 10; section_id++) { + string filename = string_printf("ItemRT%s%s%c%1zu.rel", + ((mode == GameMode::CHALLENGE) ? "c" : ""), + ((episode == Episode::EP2) ? "l" : ""), + tolower(abbreviation_for_difficulty(difficulty)), // One of "nhvu" + section_id); + auto entry = this->gsl.get(filename); + if (entry.second < sizeof(Table)) { + throw runtime_error(string_printf("table %s is too small", filename.c_str())); + } + this->tables.emplace( + this->key_for_params(mode, episode, difficulty, section_id), + reinterpret_cast(entry.first)); + } + } + } } - return *reinterpret_cast(entry.first); +} + +std::vector GSLRareItemSet::get_enemy_specs( + GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const { + return this->tables.at(this->key_for_params(mode, episode, difficulty, secid))->get_enemy_specs(enemy_type); +} + +std::vector GSLRareItemSet::get_box_specs( + GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const { + return this->tables.at(this->key_for_params(mode, episode, difficulty, secid))->get_box_specs(area); } RELRareItemSet::RELRareItemSet(shared_ptr data) : data(data) { @@ -53,8 +130,18 @@ RELRareItemSet::RELRareItemSet(shared_ptr data) : data(data) { } } +std::vector RELRareItemSet::get_enemy_specs( + GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const { + return this->get_table(mode, episode, difficulty, secid).get_enemy_specs(enemy_type); +} + +std::vector RELRareItemSet::get_box_specs( + GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const { + return this->get_table(mode, episode, difficulty, secid).get_box_specs(area); +} + const RELRareItemSet::Table& RELRareItemSet::get_table( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { + GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const { (void)mode; // TODO: Shouldn't we check for challenge mode somewhere? if (difficulty > 3) { @@ -82,3 +169,75 @@ const RELRareItemSet::Table& RELRareItemSet::get_table( const auto* tables = reinterpret_cast(this->data->data()); return tables[(ep_index * 10 * 4) + (difficulty * 10) + secid]; } + +JSONRareItemSet::JSONRareItemSet(std::shared_ptr json) { + for (const auto& mode_it : json->as_dict()) { + static const unordered_map mode_keys( + {{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}}); + GameMode mode = mode_keys.at(mode_it.first); + + for (const auto& episode_it : mode_it.second->as_dict()) { + static const unordered_map episode_keys( + {{"Episode1", Episode::EP1}, {"Episode2", Episode::EP2}, {"Episode4", Episode::EP4}}); + Episode episode = episode_keys.at(episode_it.first); + + for (const auto& difficulty_it : episode_it.second->as_dict()) { + static const unordered_map difficulty_keys( + {{"Normal", 0}, {"Hard", 1}, {"VeryHard", 2}, {"Ultimate", 3}}); + uint8_t difficulty = difficulty_keys.at(difficulty_it.first); + + for (const auto& section_id_it : difficulty_it.second->as_dict()) { + uint8_t section_id = section_id_for_name(section_id_it.first); + + auto& collection = this->collections[this->key_for_params(mode, episode, difficulty, section_id)]; + for (const auto& item_it : difficulty_it.second->as_dict()) { + vector* target; + if (starts_with(item_it.first, "Box-")) { + uint8_t area = drop_area_for_name(item_it.first.substr(4)); + if (collection.box_area_to_specs.size() <= area) { + collection.box_area_to_specs.resize(area + 1); + } + target = &collection.box_area_to_specs[area]; + } else { + size_t index = static_cast(enum_for_name(item_it.first.c_str())); + if (collection.enemy_type_to_specs.size() <= index) { + collection.enemy_type_to_specs.resize(index + 1); + } + target = &collection.enemy_type_to_specs[index]; + } + + for (const auto& spec_json : item_it.second->as_list()) { + auto& spec_list = spec_json->as_list(); + auto& d = target->emplace_back(); + d.probability = spec_list.at(0)->as_int(); + uint32_t item_code = spec_list.at(1)->as_int(); + d.item_code[0] = (item_code >> 16) & 0xFF; + d.item_code[1] = (item_code >> 8) & 0xFF; + d.item_code[2] = item_code & 0xFF; + } + } + } + } + } + } +} + +std::vector JSONRareItemSet::get_enemy_specs( + GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const { + try { + return this->collections.at(this->key_for_params(mode, episode, difficulty, secid)).enemy_type_to_specs.at(enemy_type); + } catch (const out_of_range&) { + static const std::vector empty_vector; + return empty_vector; + } +} + +std::vector JSONRareItemSet::get_box_specs( + GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const { + try { + return this->collections.at(this->key_for_params(mode, episode, difficulty, secid)).box_area_to_specs.at(area); + } catch (const out_of_range&) { + static const std::vector empty_vector; + return empty_vector; + } +} diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index 262453a2..8a992684 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -11,18 +11,29 @@ class RareItemSet { public: + struct PackedDrop { + uint8_t probability; + uint8_t item_code[3]; + + static uint32_t expand_rate(uint8_t pc); + } __attribute__((packed)); + + struct ExpandedDrop { + uint32_t probability; + uint8_t item_code[3]; + + ExpandedDrop(); + explicit ExpandedDrop(const PackedDrop&); + }; + struct Table { // 0x280 in size; describes one difficulty, section ID, and episode // TODO: It looks like this structure can actually vary. In PSOGC, these all // appear to be the same size/format, but that's probably not strictly // required to be the case. - struct Drop { - uint8_t probability; - uint8_t item_code[3]; - } __attribute__((packed)); - /* 0000 */ parray monster_rares; + /* 0000 */ parray monster_rares; /* 0194 */ parray box_areas; - /* 01B2 */ parray box_rares; + /* 01B2 */ parray box_rares; /* 022A */ parray unknown_a1; /* 022C */ be_uint32_t monster_rares_offset; // == 0x0000 /* 0230 */ be_uint32_t box_count; // == 0x1E @@ -37,26 +48,32 @@ public: /* 0270 */ be_uint32_t offset_table_offset; // == 0x022C /* 0274 */ parray unknown_a5; /* 0280 */ + + std::vector get_enemy_specs(uint8_t enemy_type) const; + std::vector get_box_specs(uint8_t area) const; } __attribute__((packed)); virtual ~RareItemSet() = default; - virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const = 0; - - static bool sample(std::mt19937& rand, uint8_t probability); - static uint32_t expand_rate(uint8_t pc); + virtual std::vector get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const = 0; + virtual std::vector get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const = 0; protected: RareItemSet() = default; + + static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid); }; class GSLRareItemSet : public RareItemSet { public: GSLRareItemSet(std::shared_ptr data, bool is_big_endian); virtual ~GSLRareItemSet() = default; - virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; + virtual std::vector get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const; + virtual std::vector get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const; private: + std::unordered_map tables; + GSLArchive gsl; }; @@ -64,8 +81,28 @@ class RELRareItemSet : public RareItemSet { public: RELRareItemSet(std::shared_ptr data); virtual ~RELRareItemSet() = default; - virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; + virtual std::vector get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const; + virtual std::vector get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const; private: std::shared_ptr data; + + const Table& get_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const; +}; + +class JSONRareItemSet : public RareItemSet { +public: + JSONRareItemSet(std::shared_ptr json); + virtual ~JSONRareItemSet() = default; + + virtual std::vector get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const; + virtual std::vector get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const; + +private: + struct SpecCollection { + std::vector> enemy_type_to_specs; + std::vector> box_area_to_specs; + }; + + std::unordered_map collections; }; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 95e496c9..ac39bc8e 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1030,9 +1030,13 @@ static void on_entity_drop_item_request( cmd.def[0], cmd.def[1], cmd.def[2]); } } else { - uint32_t expected_rt_index = l->enemies.at(cmd.entity_id).rt_index; - if (cmd.rt_index != l->enemies.at(cmd.entity_id).rt_index) { - c->log.warning("rt_index %02hhX does not match entity\'s expected index %02" PRIX32, + if (!l->map) { + throw runtime_error("game does not have a map loaded"); + } + const auto& enemy = l->map->enemies.at(cmd.entity_id); + uint32_t expected_rt_index = rare_table_index_for_enemy_type(enemy.type); + if (cmd.rt_index != expected_rt_index) { + c->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32, cmd.rt_index, expected_rt_index); } item.data = l->item_creator->on_monster_item_drop(expected_rt_index, cmd.area);