diff --git a/notes/psobb/ItemPT.gsl b/notes/psobb/ItemPT.gsl deleted file mode 100755 index a0ffa9b6..00000000 Binary files a/notes/psobb/ItemPT.gsl and /dev/null differ diff --git a/src/CommonItemSet.cc b/src/CommonItemSet.cc index ba10c842..70e347d5 100644 --- a/src/CommonItemSet.cc +++ b/src/CommonItemSet.cc @@ -1,28 +1,267 @@ #include "CommonItemSet.hh" +#include "AFSArchive.hh" +#include "GSLArchive.hh" #include "StaticGameData.hh" using namespace std; -CommonItemSet::CommonItemSet(shared_ptr data) - : gsl(data, true) {} - -const CommonItemSet::Table& CommonItemSet::get_table( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { - // TODO: What should we do for Ep4? - string filename = string_printf( - "ItemPT%s%s%c%1d.rel", - ((mode == GameMode::CHALLENGE) ? "c" : ""), - ((episode == Episode::EP2) ? "l" : ""), - tolower(abbreviation_for_difficulty(difficulty)), - (mode == GameMode::CHALLENGE) ? 0 : secid); - auto data = this->gsl.get(filename); - if (data.second < sizeof(Table)) { - throw runtime_error(string_printf( - "ItemPT entry %s is too small (received %zX bytes, expected %zX bytes)", - filename.c_str(), data.second, sizeof(Table))); +CommonItemSet::Table::Table( + shared_ptr owned_data, const StringReader& r, bool is_big_endian, bool is_v3) + : owned_data(owned_data), + r(r), + is_big_endian(is_big_endian), + is_v3(is_v3) { + if (is_big_endian) { + const auto& be_offsets = r.pget>(r.pget_u32b(this->r.size() - 0x10)); + this->offsets.base_weapon_type_prob_table_offset = be_offsets.base_weapon_type_prob_table_offset.load(); + this->offsets.subtype_base_table_offset = be_offsets.subtype_base_table_offset.load(); + this->offsets.subtype_area_length_table_offset = be_offsets.subtype_area_length_table_offset.load(); + this->offsets.grind_prob_table_offset = be_offsets.grind_prob_table_offset.load(); + this->offsets.armor_shield_type_index_prob_table_offset = be_offsets.armor_shield_type_index_prob_table_offset.load(); + this->offsets.armor_slot_count_prob_table_offset = be_offsets.armor_slot_count_prob_table_offset.load(); + this->offsets.enemy_meseta_ranges_offset = be_offsets.enemy_meseta_ranges_offset.load(); + this->offsets.enemy_type_drop_probs_offset = be_offsets.enemy_type_drop_probs_offset.load(); + this->offsets.enemy_item_classes_offset = be_offsets.enemy_item_classes_offset.load(); + this->offsets.box_meseta_ranges_offset = be_offsets.box_meseta_ranges_offset.load(); + this->offsets.bonus_value_prob_table_offset = be_offsets.bonus_value_prob_table_offset.load(); + this->offsets.nonrare_bonus_prob_spec_offset = be_offsets.nonrare_bonus_prob_spec_offset.load(); + this->offsets.bonus_type_prob_table_offset = be_offsets.bonus_type_prob_table_offset.load(); + this->offsets.special_mult_offset = be_offsets.special_mult_offset.load(); + this->offsets.special_percent_offset = be_offsets.special_percent_offset.load(); + this->offsets.tool_class_prob_table_offset = be_offsets.tool_class_prob_table_offset.load(); + this->offsets.technique_index_prob_table_offset = be_offsets.technique_index_prob_table_offset.load(); + this->offsets.technique_level_ranges_offset = be_offsets.technique_level_ranges_offset.load(); + this->offsets.armor_or_shield_type_bias = be_offsets.armor_or_shield_type_bias; + this->offsets.unit_maxes_offset = be_offsets.unit_maxes_offset.load(); + this->offsets.box_item_class_prob_table_offset = be_offsets.box_item_class_prob_table_offset.load(); + } else { + this->offsets = r.pget>(r.pget_u32l(this->r.size() - 0x10)); + } +} + +const parray& CommonItemSet::Table::base_weapon_type_prob_table() const { + return this->r.pget>(this->offsets.base_weapon_type_prob_table_offset); +} +const parray& CommonItemSet::Table::subtype_base_table() const { + return this->r.pget>(this->offsets.subtype_base_table_offset); +} +const parray& CommonItemSet::Table::subtype_area_length_table() const { + return this->r.pget>(this->offsets.subtype_area_length_table_offset); +} +const parray, 9>& CommonItemSet::Table::grind_prob_table() const { + return this->r.pget, 9>>(this->offsets.grind_prob_table_offset); +} +const parray& CommonItemSet::Table::armor_shield_type_index_prob_table() const { + return this->r.pget>(this->offsets.armor_shield_type_index_prob_table_offset); +} +const parray& CommonItemSet::Table::armor_slot_count_prob_table() const { + return this->r.pget>(this->offsets.armor_slot_count_prob_table_offset); +} +const parray, 0x64>& CommonItemSet::Table::enemy_meseta_ranges() const { + if (!this->parsed_enemy_meseta_ranges_populated) { + if (this->is_big_endian) { + const auto& data = r.pget, 0x64>>(this->offsets.enemy_meseta_ranges_offset); + for (size_t z = 0; z < data.size(); z++) { + this->parsed_enemy_meseta_ranges[z] = Range{data[z].min, data[z].max}; + } + } else { + const auto& data = r.pget, 0x64>>(this->offsets.enemy_meseta_ranges_offset); + for (size_t z = 0; z < data.size(); z++) { + this->parsed_enemy_meseta_ranges[z] = Range{data[z].min, data[z].max}; + } + } + this->parsed_enemy_meseta_ranges_populated = true; + } + return this->parsed_enemy_meseta_ranges; +} +const parray& CommonItemSet::Table::enemy_type_drop_probs() const { + return this->r.pget>(this->offsets.enemy_type_drop_probs_offset); +} +const parray& CommonItemSet::Table::enemy_item_classes() const { + return this->r.pget>(this->offsets.enemy_item_classes_offset); +} +const parray, 0x0A>& CommonItemSet::Table::box_meseta_ranges() const { + if (!this->parsed_box_meseta_ranges_populated) { + if (this->is_big_endian) { + const auto& data = r.pget, 0x0A>>(this->offsets.box_meseta_ranges_offset); + for (size_t z = 0; z < data.size(); z++) { + this->parsed_box_meseta_ranges[z] = Range{data[z].min, data[z].max}; + } + } else { + const auto& data = r.pget, 0x0A>>(this->offsets.box_meseta_ranges_offset); + for (size_t z = 0; z < data.size(); z++) { + this->parsed_box_meseta_ranges[z] = Range{data[z].min, data[z].max}; + } + } + this->parsed_box_meseta_ranges_populated = true; + } + return this->parsed_box_meseta_ranges; +} +bool CommonItemSet::Table::has_rare_bonus_value_prob_table() const { + return this->is_v3; +} +const parray, 0x17>& CommonItemSet::Table::bonus_value_prob_table() const { + if (!this->parsed_bonus_value_prob_table_populated) { + if (!this->is_v3) { // V2 + const auto& data = r.pget, 0x17>>(this->offsets.bonus_value_prob_table_offset); + for (size_t z = 0; z < data.size(); z++) { + for (size_t x = 0; x < data[z].size(); x++) { + this->parsed_bonus_value_prob_table[z][x] = data[z][x]; + } + } + } else if (this->is_big_endian) { // BE V3 + const auto& data = r.pget, 0x17>>(this->offsets.bonus_value_prob_table_offset); + for (size_t z = 0; z < data.size(); z++) { + for (size_t x = 0; x < data[z].size(); x++) { + this->parsed_bonus_value_prob_table[z][x] = data[z][x]; + } + } + } else { // LE V3 + const auto& data = r.pget, 0x17>>(this->offsets.bonus_value_prob_table_offset); + for (size_t z = 0; z < data.size(); z++) { + for (size_t x = 0; x < data[z].size(); x++) { + this->parsed_bonus_value_prob_table[z][x] = data[z][x]; + } + } + } + this->parsed_bonus_value_prob_table_populated = true; + } + return this->parsed_bonus_value_prob_table; +} +const parray, 3>& CommonItemSet::Table::nonrare_bonus_prob_spec() const { + return this->r.pget, 3>>(this->offsets.nonrare_bonus_prob_spec_offset); +} +const parray, 6>& CommonItemSet::Table::bonus_type_prob_table() const { + return this->r.pget, 6>>(this->offsets.bonus_type_prob_table_offset); +} +const parray& CommonItemSet::Table::special_mult() const { + return this->r.pget>(this->offsets.special_mult_offset); +} +const parray& CommonItemSet::Table::special_percent() const { + return this->r.pget>(this->offsets.special_percent_offset); +} +const parray, 0x1C>& CommonItemSet::Table::tool_class_prob_table() const { + if (!this->parsed_tool_class_prob_table_populated) { + if (this->is_big_endian) { + const auto& data = r.pget, 0x1C>>(this->offsets.tool_class_prob_table_offset); + for (size_t z = 0; z < data.size(); z++) { + for (size_t x = 0; x < data[z].size(); x++) { + this->parsed_tool_class_prob_table[z][x] = data[z][x]; + } + } + } else { + const auto& data = r.pget, 0x1C>>(this->offsets.tool_class_prob_table_offset); + for (size_t z = 0; z < data.size(); z++) { + for (size_t x = 0; x < data[z].size(); x++) { + this->parsed_tool_class_prob_table[z][x] = data[z][x]; + } + } + } + this->parsed_tool_class_prob_table_populated = true; + } + return this->parsed_tool_class_prob_table; +} +const parray, 0x13>& CommonItemSet::Table::technique_index_prob_table() const { + return this->r.pget, 0x13>>(this->offsets.technique_index_prob_table_offset); +} +const parray, 0x0A>, 0x13>& CommonItemSet::Table::technique_level_ranges() const { + return this->r.pget, 0x0A>, 0x13>>(this->offsets.technique_level_ranges_offset); +} +uint8_t CommonItemSet::Table::armor_or_shield_type_bias() const { + return this->offsets.armor_or_shield_type_bias; +} +const parray& CommonItemSet::Table::unit_maxes_table() const { + return this->r.pget>(this->offsets.unit_maxes_offset); +} +const parray, 7>& CommonItemSet::Table::box_item_class_prob_table() const { + return this->r.pget, 7>>(this->offsets.box_item_class_prob_table_offset); +} + +uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) { + // Bits: -----EEEMMDDSSSS + return (((static_cast(episode) << 8) & 0x0700) | + ((static_cast(mode) << 6) & 0x00C0) | + ((static_cast(difficulty) << 4) & 0x0030) | + (static_cast(secid) & 0x000F)); +} + +shared_ptr CommonItemSet::get_table( + Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { + try { + return this->tables.at(this->key_for_table(episode, mode, difficulty, secid)); + } catch (const out_of_range&) { + throw runtime_error(string_printf("common item table not available for episode=%s, mode=%s, difficulty=%hu, secid=%hu", + name_for_episode(episode), name_for_mode(mode), difficulty, secid)); + } +} + +AFSV2CommonItemSet::AFSV2CommonItemSet( + std::shared_ptr pt_afs_data, + std::shared_ptr ct_afs_data) { + // ItemPT.afs has 40 entries; the first 10 are for Normal, then Hard, etc. + AFSArchive pt_afs(pt_afs_data); + for (size_t difficulty = 0; difficulty < 4; difficulty++) { + for (size_t section_id = 0; section_id < 10; section_id++) { + auto entry = pt_afs.get(difficulty * 10 + section_id); + StringReader r(entry.first, entry.second); + shared_ptr table(new Table(pt_afs_data, r, false, false)); + this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table); + this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table); + this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::SOLO, difficulty, section_id), table); + } + } + + // ItemCT.afs also has 40 entries, but only the 0th, 10th, 20th, and 30th are + // used (section_id is ignored) + AFSArchive ct_afs(ct_afs_data); + for (size_t difficulty = 0; difficulty < 4; difficulty++) { + auto r = ct_afs.get_reader(difficulty * 10); + shared_ptr
table(new Table(ct_afs_data, r, false, false)); + for (size_t section_id = 0; section_id < 10; section_id++) { + this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table); + } + } +} + +GSLV3CommonItemSet::GSLV3CommonItemSet(std::shared_ptr gsl_data, bool is_big_endian) { + GSLArchive gsl(gsl_data, is_big_endian); + + vector episodes = {Episode::EP1, Episode::EP2}; + for (Episode episode : episodes) { + for (size_t difficulty = 0; difficulty < 4; difficulty++) { + for (size_t section_id = 0; section_id < 10; section_id++) { + string filename = string_printf( + "ItemPT%s%c%1zu.rel", + ((episode == Episode::EP2) ? "l" : ""), + tolower(abbreviation_for_difficulty(difficulty)), + section_id); + auto r = gsl.get_reader(filename); + shared_ptr
table(new Table(gsl_data, r, is_big_endian, true)); + this->tables.emplace(this->key_for_table(episode, GameMode::NORMAL, difficulty, section_id), table); + this->tables.emplace(this->key_for_table(episode, GameMode::BATTLE, difficulty, section_id), table); + this->tables.emplace(this->key_for_table(episode, GameMode::SOLO, difficulty, section_id), table); + // TODO: These tables don't exist for Episode 4, and the GC version is + // the closest we have, so we use the Ep1 data from GC for Ep4. + if (episode == Episode::EP1) { + this->tables.emplace(this->key_for_table(Episode::EP4, GameMode::NORMAL, difficulty, section_id), table); + this->tables.emplace(this->key_for_table(Episode::EP4, GameMode::BATTLE, difficulty, section_id), table); + this->tables.emplace(this->key_for_table(Episode::EP4, GameMode::SOLO, difficulty, section_id), table); + } + } + } + for (size_t difficulty = 0; difficulty < 4; difficulty++) { + string filename = string_printf( + "ItemPTc%s%c0.rel", + ((episode == Episode::EP2) ? "l" : ""), + tolower(abbreviation_for_difficulty(difficulty))); + auto r = gsl.get_reader(filename); + shared_ptr
table(new Table(gsl_data, r, is_big_endian, true)); + for (size_t section_id = 0; section_id < 10; section_id++) { + this->tables.emplace(this->key_for_table(episode, GameMode::CHALLENGE, difficulty, section_id), table); + } + } } - return *reinterpret_cast*>(data.first); } RELFileSet::RELFileSet(std::shared_ptr data) diff --git a/src/CommonItemSet.hh b/src/CommonItemSet.hh index 87725da2..17db9b35 100644 --- a/src/CommonItemSet.hh +++ b/src/CommonItemSet.hh @@ -8,6 +8,284 @@ #include "StaticGameData.hh" #include "Text.hh" +class CommonItemSet { +public: + class Table { + public: + Table() = delete; + Table(std::shared_ptr owned_data, const StringReader& r, bool big_endian, bool is_v3); + + template + struct Range { + IntT min; + IntT max; + } __attribute__((packed)); + + const parray& base_weapon_type_prob_table() const; + const parray& subtype_base_table() const; + const parray& subtype_area_length_table() const; + const parray, 9>& grind_prob_table() const; + const parray& armor_shield_type_index_prob_table() const; + const parray& armor_slot_count_prob_table() const; + const parray, 0x64>& enemy_meseta_ranges() const; + const parray& enemy_type_drop_probs() const; + const parray& enemy_item_classes() const; + const parray, 0x0A>& box_meseta_ranges() const; + bool has_rare_bonus_value_prob_table() const; + const parray, 0x17>& bonus_value_prob_table() const; + const parray, 3>& nonrare_bonus_prob_spec() const; + const parray, 6>& bonus_type_prob_table() const; + const parray& special_mult() const; + const parray& special_percent() const; + const parray, 0x1C>& tool_class_prob_table() const; + const parray, 0x13>& technique_index_prob_table() const; + const parray, 0x0A>, 0x13>& technique_level_ranges() const; + uint8_t armor_or_shield_type_bias() const; + const parray& unit_maxes_table() const; + const parray, 7>& box_item_class_prob_table() const; + + private: + template + struct Offsets { + using U16T = typename std::conditional::type; + using U32T = typename std::conditional::type; + + // This data structure uses index probability tables in multiple places. An + // index probability table is a table where each entry holds the probability + // that that entry's index is used. For example, if the armor slot count + // probability table contains [77, 17, 5, 1, 0], this means there is a 77% + // chance of no slots, 17% chance of 1 slot, 5% chance of 2 slots, 1% chance + // of 3 slots, and no chance of 4 slots. The values in index probability + // tables do not have to add up to 100; the game sums all of them and + // chooses a random number less than that maximum. + + // The area (floor) number is used in many places as well. Unlike the normal + // area numbers, which start with Pioneer 2, the area numbers in this + // structure start with Forest 1, and boss areas are treated as the first + // area of the next section (so De Rol Le has Mines 1 drops, for example). + // Final boss areas are treated as the last non-boss area (so Dark Falz + // boxes are like Ruins 3 boxes). We refer to these adjusted area numbers as + // (area - 1). + + // This index probability table determines the types of non-rare weapons. + // The indexes in this table correspond to the non-rare weapon types 01 + // through 0C (Saber through Wand). + // V2/GC: -> parray + /* 00 */ U32T base_weapon_type_prob_table_offset; + + // This table specifies the base subtype for each weapon type. Negative + // values here mean that the weapon cannot be found in the first N areas (so + // -2, for example, means that the weapon never appears in Forest 1 or 2 at + // all). Nonnegative values here mean the subtype can be found in all areas, + // and specify the base subtype (usually in the range [0, 4]). The subtype + // of weapon that actually appears depends on this value and a value from + // the following table. + // V2/GC: -> parray + /* 04 */ U32T subtype_base_table_offset; + + // This table specifies how many areas each weapon subtype appears in. For + // example, if Sword (subtype 02, which is index 1 in this table and the + // table above) has a subtype base of -2 and a subtype area length of 4, + // then Sword items can be found when area - 1 is 2, 3, 4, or 5 (Cave 1 + // through Mine 1), and Gigush (the next sword subtype) can be found in Mine + // 1 through Ruins 3. + // V2/GC: -> parray + /* 08 */ U32T subtype_area_length_table_offset; + + // This index probability table specifies how likely each possible grind + // value is. The table is indexed as [grind][subtype_area_index], where the + // subtype area index is how many areas the player is beyond the first area + // in which the subtype can first be found (clamped to [0, 3]). To continue + // the example above, in Cave 3, subtype_area_index would be 2, since Swords + // can first be found two areas earlier in Cave 1. + // For example, this table could look like this: + // [64 1E 19 14] // Chance of getting a grind +0 + // [00 1E 17 0F] // Chance of getting a grind +1 + // [00 14 14 0E] // Chance of getting a grind +2 + // ... + // C1 C2 C3 M1 // (Episode 1 area values from the example for reference) + // V2/GC: -> parray, 9> + /* 0C */ U32T grind_prob_table_offset; + + // TODO: Figure out exactly how this table is used. Anchor: 80106D34 + // V2/GC: -> parray + /* 10 */ U32T armor_shield_type_index_prob_table_offset; + + // This index probability table specifies how common each possible slot + // count is for armor drops. + // V2/GC: -> parray + /* 14 */ U32T armor_slot_count_prob_table_offset; + + // This array (indexed by enemy_id) specifies the range of meseta values + // that each enemy can drop. + // V2/GC: -> parray, 0x64> + /* 18 */ U32T enemy_meseta_ranges_offset; + + // Each byte in this table (indexed by enemy_type) represents the percent + // chance that the enemy drops anything at all. (This check is done before + // the rare drop check, so the chance of getting a rare item from an enemy + // is essentially this probability multiplied by the rare drop rate.) + // V2/GC: -> parray + /* 1C */ U32T enemy_type_drop_probs_offset; + + // Each byte in this table (indexed by enemy_type) represents the class of + // item that the enemy can drop. The values are: + // 00 = weapon + // 01 = armor + // 02 = shield + // 03 = unit + // 04 = tool + // 05 = meseta + // Anything else = no item + // V2/GC: -> parray + /* 20 */ U32T enemy_item_classes_offset; + + // This table (indexed by area - 1) specifies the ranges of meseta values + // that can drop from boxes. + // V2/GC: -> parray, 0x0A> + /* 24 */ U32T box_meseta_ranges_offset; + + // This array specifies the chance that a rare weapon will have each + // possible bonus value. This is indexed as [(bonus_value - 10 / 5)][spec], + // so the first row refers the probability of getting a -10% bonus, the next + // row is the chance of getting -5%, etc., all the way up to +100%. For + // non-rare items, spec is determined randomly based on the following field; + // for rare items, spec is always 5. + // V2: -> parray, 0x17> + // GC: -> parray, 0x17> + /* 28 */ U32T bonus_value_prob_table_offset; + + // This array specifies the value of spec to be used in the above lookup for + // non-rare items. This is NOT an index probability table; this is a direct + // lookup with indexes [bonus_index][area - 1]. A value of 0xFF in any byte + // of this array prevents any weapon from having a bonus in that slot. + // For example, the array might look like this: + // [00 00 00 01 01 01 01 02 02 02] + // [FF FF FF 00 00 00 01 01 01 01] + // [FF FF FF FF FF FF FF FF FF 00] + // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) + // In this example, spec is 0, 1, or 2 in all cases where a weapon can have + // a bonus. In Forest 1 and 2 and Cave 1, weapons may have at most one + // bonus; in all other areas except Ruins 3, they can have at most two + // bonuses, and in Ruins 3, they can have up to three bonuses. + // V2/GC: // -> parray, 3> + /* 2C */ U32T nonrare_bonus_prob_spec_offset; + + // This array specifies the chance that a weapon will have each bonus type. + // The table is indexed as [bonus_type][area - 1] for non-rare items; for + // rare items, a random value in the range [0, 9] is used instead of + // (area - 1). + // For example, the table might look like this: + // [46 46 3F 3E 3E 3D 3C 3C 3A 3A] // Chance of getting no bonus + // [14 14 0A 0A 09 02 02 04 05 05] // Chance of getting Native bonus + // [0A 0A 12 11 11 09 09 08 08 08] // Chance of getting A.Beast bonus + // [00 00 09 0A 0B 13 12 08 09 09] // Chance of getting Machine bonus + // [00 00 00 01 01 08 0A 13 13 13] // Chance of getting Dark bonus + // [00 00 00 00 00 01 01 01 01 01] // Chance of getting Hit bonus + // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) + // V2/GC: -> parray, 6> + /* 30 */ U32T bonus_type_prob_table_offset; + + // This array (indexed by area - 1) specifies a multiplier of used in + // special ability determination. It seems this uses the star values from + // ItemPMT, but not yet clear exactly in what way. + // TODO: Figure out exactly what this does. Anchor: 80106FEC + // V2/GC: -> parray + /* 34 */ U32T special_mult_offset; + + // This array (indexed by area - 1) specifies the probability that any + // non-rare weapon will have a special ability. + // V2/GC: -> parray + /* 38 */ U32T special_percent_offset; + + // This index probability table is indexed by [tool_class][area - 1]. The + // tool class refers to an entry in ItemPMT, which links it to the actual + // item code. + // V2/GC: -> parray, 0x1C> + /* 3C */ U32T tool_class_prob_table_offset; + + // This index probability table determines how likely each technique is to + // appear. The table is indexed as [technique_num][area - 1]. + // V2/GC: -> parray, 0x13> + /* 40 */ U32T technique_index_prob_table_offset; + + // This table specifies the ranges for technique disk levels. The table is + // indexed as [technique_num][area - 1]. If either min or max in the range + // is 0xFF, or if max < min, technique disks are not dropped for that + // technique and area pair. + // V2/GC: -> parray, 0x0A>, 0x13> + /* 44 */ U32T technique_level_ranges_offset; + + /* 48 */ uint8_t armor_or_shield_type_bias; + /* 49 */ parray unused1; + + // These values specify maximum indexes into another array which is + // generated at runtime. The values here are multiplied by a random float in + // the range [0, n] to look up the value in the secondary array, which is + // what ends up determining the unit type. + // TODO: Figure out and document the exact logic here. Anchor: 80106364 + // V2/GC: -> parray + /* 4C */ U32T unit_maxes_offset; + + // This index probability table determines which type of items drop from + // boxes. The table is indexed as [item_class][area - 1], with item_class as + // the result value (that is, in the example below, the game looks at a + // single column and sums the values going down, then the chosen item class + // is one of the row indexes based on the weight values in the column.) The + // resulting item_class value has the same meaning as in enemy_item_classes + // above. + // For example, this array might look like the following: + // [07 07 08 08 06 07 08 09 09 0A] // Chances per area of a weapon drop + // [02 02 02 02 03 02 02 02 03 03] // Chances per area of an armor drop + // [02 02 02 02 03 02 02 02 03 03] // Chances per area of a shield drop + // [00 00 03 03 03 04 03 04 05 05] // Chances per area of a unit drop + // [11 11 12 12 12 12 12 12 12 12] // Chances per area of a tool drop + // [32 32 32 32 32 32 32 32 32 32] // Chances per area of a meseta drop + // [16 16 11 11 11 11 11 0F 0C 0B] // Chances per area of an empty box + // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) + // V2/GC: -> parray, 7> + /* 50 */ U32T box_item_class_prob_table_offset; + + // There are several unused fields here. + } __attribute__((packed)); + + std::shared_ptr owned_data; + StringReader r; + bool is_big_endian; + bool is_v3; + + Offsets offsets; + + mutable parray, 0x64> parsed_enemy_meseta_ranges; + mutable bool parsed_enemy_meseta_ranges_populated = false; + mutable parray, 0x0A> parsed_box_meseta_ranges; + mutable bool parsed_box_meseta_ranges_populated = false; + mutable parray, 0x17> parsed_bonus_value_prob_table; + mutable bool parsed_bonus_value_prob_table_populated = false; + mutable parray, 0x1C> parsed_tool_class_prob_table; + mutable bool parsed_tool_class_prob_table_populated = false; + }; + + std::shared_ptr get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; + +protected: + CommonItemSet() = default; + + static uint16_t key_for_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid); + + std::unordered_map> tables; +}; + +class AFSV2CommonItemSet : public CommonItemSet { +public: + AFSV2CommonItemSet(std::shared_ptr pt_afs_data, std::shared_ptr ct_afs_data); +}; + +class GSLV3CommonItemSet : public CommonItemSet { +public: + GSLV3CommonItemSet(std::shared_ptr gsl_data, bool is_big_endian); +}; + // Note: There are clearly better ways of doing this, but this implementation // closely follows what the original code in the client does. template @@ -51,248 +329,6 @@ struct ProbabilityTable { } }; -class CommonItemSet { -public: - template - struct Range { - IntT min; - IntT max; - } __attribute__((packed)); - - // The Table structure below describes the format of a single ItemPT entry - // (which corresponds to a single difficulty, episode, section ID, and - // challenge flag). ItemPT entries (within ItemPT.gsl) are named like this: - // string_printf("ItemPT%s%s%c%1d.rel", - // (is_challenge ? "c" : ""), - // (is_ep2 ? "l" : ""), - // char_for_difficulty(difficulty), // One of "nhvu" - // section_id); - template - struct Table { - using U16T = typename std::conditional::type; - using U32T = typename std::conditional::type; - // This data structure uses index probability tables in multiple places. An - // index probability table is a table where each entry holds the probability - // that that entry's index is used. For example, if the armor slot count - // probability table contains [77, 17, 5, 1, 0], this means there is a 77% - // chance of no slots, 17% chance of 1 slot, 5% chance of 2 slots, 1% chance - // of 3 slots, and no chance of 4 slots. The values in index probability - // tables do not have to add up to 100; the game sums all of them and - // chooses a random number less than that maximum. - - // The area (floor) number is used in many places as well. Unlike the normal - // area numbers, which start with Pioneer 2, the area numbers in this - // structure start with Forest 1, and boss areas are treated as the first - // area of the next section (so De Rol Le has Mines 1 drops, for example). - // Final boss areas are treated as the last non-boss area (so Dark Falz - // boxes are like Ruins 3 boxes). We refer to these adjusted area numbers as - // (area - 1). - - // This index probability table determines the types of non-rare weapons. - // The indexes in this table correspond to the non-rare weapon types 01 - // through 0C (Saber through Wand). - /* 0000 */ parray base_weapon_type_prob_table; - - // This table specifies the base subtype for each weapon type. Negative - // values here mean that the weapon cannot be found in the first N areas (so - // -2, for example, means that the weapon never appears in Forest 1 or 2 at - // all). Nonnegative values here mean the subtype can be found in all areas, - // and specify the base subtype (usually in the range [0, 4]). The subtype - // of weapon that actually appears depends on this value and a value from - // the following table. - /* 000C */ parray subtype_base_table; - - // This table specifies how many areas each weapon subtype appears in. For - // example, if Sword (subtype 02, which is index 1 in this table and the - // table above) has a subtype base of -2 and a subtype area length of 4, - // then Sword items can be found when area - 1 is 2, 3, 4, or 5 (Cave 1 - // through Mine 1), and Gigush (the next sword subtype) can be found in Mine - // 1 through Ruins 3. - /* 0018 */ parray subtype_area_length_table; - - // This index probability table specifies how likely each possible grind - // value is. The table is indexed as [grind][subtype_area_index], where the - // subtype area index is how many areas the player is beyond the first area - // in which the subtype can first be found (clamped to [0, 3]). To continue - // the example above, in Cave 3, subtype_area_index would be 2, since Swords - // can first be found two areas earlier in Cave 1. - // For example, this table could look like this: - // [64 1E 19 14] // Chance of getting a grind +0 - // [00 1E 17 0F] // Chance of getting a grind +1 - // [00 14 14 0E] // Chance of getting a grind +2 - // ... - // C1 C2 C3 M1 // (Episode 1 area values from the example for reference) - /* 0024 */ parray, 9> grind_prob_tables; - - // This array specifies the chance that a rare weapon will have each - // possible bonus value. This is indexed as [(bonus_value - 10 / 5)][spec], - // so the first row refers the probability of getting a -10% bonus, the next - // row is the chance of getting -5%, etc., all the way up to +100%. For - // non-rare items, spec is determined randomly based on the following field; - // for rare items, spec is always 5. - /* 0048 */ parray, 0x17> bonus_value_prob_tables; - - // This array specifies the value of spec to be used in the above lookup for - // non-rare items. This is NOT an index probability table; this is a direct - // lookup with indexes [bonus_index][area - 1]. A value of 0xFF in any byte - // of this array prevents any weapon from having a bonus in that slot. - // For example, the array might look like this: - // [00 00 00 01 01 01 01 02 02 02] - // [FF FF FF 00 00 00 01 01 01 01] - // [FF FF FF FF FF FF FF FF FF 00] - // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) - // In this example, spec is 0, 1, or 2 in all cases where a weapon can have - // a bonus. In Forest 1 and 2 and Cave 1, weapons may have at most one - // bonus; in all other areas except Ruins 3, they can have at most two - // bonuses, and in Ruins 3, they can have up to three bonuses. - /* 015C */ parray, 3> nonrare_bonus_prob_spec; - - // This array specifies the chance that a weapon will have each bonus type. - // The table is indexed as [bonus_type][area - 1] for non-rare items; for - // rare items, a random value in the range [0, 9] is used instead of - // (area - 1). - // For example, the table might look like this: - // [46 46 3F 3E 3E 3D 3C 3C 3A 3A] // Chance of getting no bonus - // [14 14 0A 0A 09 02 02 04 05 05] // Chance of getting Native bonus - // [0A 0A 12 11 11 09 09 08 08 08] // Chance of getting A.Beast bonus - // [00 00 09 0A 0B 13 12 08 09 09] // Chance of getting Machine bonus - // [00 00 00 01 01 08 0A 13 13 13] // Chance of getting Dark bonus - // [00 00 00 00 00 01 01 01 01 01] // Chance of getting Hit bonus - // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) - /* 017A */ parray, 6> bonus_type_prob_tables; - - // This array (indexed by area - 1) specifies a multiplier of used in - // special ability determination. It seems this uses the star values from - // ItemPMT, but not yet clear exactly in what way. - // TODO: Figure out exactly what this does. Anchor: 80106FEC - /* 01B6 */ parray special_mult; - - // This array (indexed by area - 1) specifies the probability that any - // non-rare weapon will have a special ability. - /* 01C0 */ parray special_percent; - - // TODO: Figure out exactly how this table is used. Anchor: 80106D34 - /* 01CA */ parray armor_shield_type_index_prob_table; - - // This index probability table specifies how common each possible slot - // count is for armor drops. - /* 01CF */ parray armor_slot_count_prob_table; - - // These values specify maximum indexes into another array which is - // generated at runtime. The values here are multiplied by a random float in - // the range [0, n] to look up the value in the secondary array, which is - // what ends up determining the unit type. - // TODO: Figure out and document the exact logic here. Anchor: 80106364 - /* 01D4 */ parray unit_maxes; - - // This index probability table is indexed by [tool_class][area - 1]. The - // tool class refers to an entry in ItemPMT, which links it to the actual - // item code. - /* 01DE */ parray, 0x1C> tool_class_prob_table; - - // This index probability table determines how likely each technique is to - // appear. The table is indexed as [technique_num][area - 1]. - /* 040E */ parray, 0x13> technique_index_prob_table; - - // This table specifies the ranges for technique disk levels. The table is - // indexed as [technique_num][area - 1]. If either min or max in the range - // is 0xFF, or if max < min, technique disks are not dropped for that - // technique and area pair. - /* 04CC */ parray, 0x0A>, 0x13> technique_level_ranges; - - // Each byte in this table (indexed by enemy_type) represents the percent - // chance that the enemy drops anything at all. (This check is done before - // the rare drop check, so the chance of getting a rare item from an enemy - // is essentially this probability multiplied by the rare drop rate.) - /* 0648 */ parray enemy_type_drop_probs; - - // This array (indexed by enemy_id) specifies the range of meseta values - // that each enemy can drop. - /* 06AC */ parray, 0x64> enemy_meseta_ranges; - - // Each byte in this table (indexed by enemy_type) represents the class of - // item that the enemy can drop. The values are: - // 00 = weapon - // 01 = armor - // 02 = shield - // 03 = unit - // 04 = tool - // 05 = meseta - // Anything else = no item - /* 083C */ parray enemy_item_classes; - - // This table (indexed by area - 1) specifies the ranges of meseta values - // that can drop from boxes. - /* 08A0 */ parray, 0x0A> box_meseta_ranges; - - // This index probability table determines which type of items drop from - // boxes. The table is indexed as [item_class][area - 1], with item_class as - // the result value (that is, in the example below, the game looks at a - // single column and sums the values going down, then the chosen item class - // is one of the row indexes based on the weight values in the column.) The - // resulting item_class value has the same meaning as in enemy_item_classes - // above. - // For example, this array might look like the following: - // [07 07 08 08 06 07 08 09 09 0A] // Chances per area of a weapon drop - // [02 02 02 02 03 02 02 02 03 03] // Chances per area of an armor drop - // [02 02 02 02 03 02 02 02 03 03] // Chances per area of a shield drop - // [00 00 03 03 03 04 03 04 05 05] // Chances per area of a unit drop - // [11 11 12 12 12 12 12 12 12 12] // Chances per area of a tool drop - // [32 32 32 32 32 32 32 32 32 32] // Chances per area of a meseta drop - // [16 16 11 11 11 11 11 0F 0C 0B] // Chances per area of an empty box - // F1 F2 C1 C2 C3 M1 M2 R1 R2 R3 // (Episode 1 areas, for reference) - /* 08C8 */ parray, 7> box_item_class_prob_tables; - - /* 090E */ parray unused1; - - /* 0910 */ U32T base_weapon_type_prob_table_offset; - /* 0914 */ U32T subtype_base_table_offset; - /* 0918 */ U32T subtype_area_length_table_offset; - /* 091C */ U32T grind_prob_tables_offset; - /* 0920 */ U32T armor_shield_type_index_prob_table_offset; - /* 0924 */ U32T armor_slot_count_prob_table_offset; - /* 0928 */ U32T enemy_meseta_ranges_offset; - /* 092C */ U32T enemy_type_drop_probs_offset; - /* 0930 */ U32T enemy_item_classes_offset; - /* 0934 */ U32T box_meseta_ranges_offset; - /* 0938 */ U32T bonus_value_prob_tables_offset; - /* 093C */ U32T nonrare_bonus_prob_spec_offset; - /* 0940 */ U32T bonus_type_prob_tables_offset; - /* 0944 */ U32T special_mult_offset; - /* 0948 */ U32T special_percent_offset; - /* 094C */ U32T tool_class_prob_table_offset; - /* 0950 */ U32T technique_index_prob_table_offset; - /* 0954 */ U32T technique_level_ranges_offset; - /* 0958 */ uint8_t armor_or_shield_type_bias; - /* 0959 */ parray unused2; - /* 095C */ U32T unit_maxes_offset; - /* 0960 */ U32T box_item_class_prob_tables_offset; - /* 0964 */ U32T unused_offset2; - /* 0968 */ U32T unused_offset3; - /* 096C */ U32T unused_offset4; - /* 0970 */ U32T unused_offset5; - /* 0974 */ U32T unused_offset6; - /* 0978 */ U32T unused_offset7; - /* 097C */ U32T unused_offset8; - /* 0980 */ U16T unknown_f1[0x20]; - /* 09C0 */ U32T unknown_f1_offset; - /* 09C4 */ U32T unknown_f2[3]; - /* 09D0 */ U32T offset_table_offset; - /* 09D4 */ U32T unknown_f3[3]; - /* 09E0 (end of structure) */ - - void print(FILE* stream) const; - } __attribute__((packed)); - - CommonItemSet(std::shared_ptr data); - - const Table& get_table( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; - -private: - GSLArchive gsl; -}; - class RELFileSet { public: template diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index 367995d2..cb293aec 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -27,15 +27,13 @@ ItemCreator::ItemCreator( mode(mode), difficulty(difficulty), section_id(section_id), - common_item_set(common_item_set), rare_item_set(rare_item_set), armor_random_set(armor_random_set), tool_random_set(tool_random_set), weapon_random_set(weapon_random_set), tekker_adjustment_set(tekker_adjustment_set), item_parameter_table(item_parameter_table), - pt(&this->common_item_set->get_table( - this->episode, this->mode, this->difficulty, this->section_id)), + pt(common_item_set->get_table(this->episode, this->mode, this->difficulty, this->section_id)), restrictions(restrictions), random_crypt(random_seed) {} @@ -118,7 +116,7 @@ ItemData ItemCreator::on_box_item_drop_with_norm_area(uint8_t area_norm) { ItemData item = this->check_rare_specs_and_create_rare_box_item(area_norm); if (item.empty()) { uint8_t item_class = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->box_item_class_prob_tables, area_norm); + this->pt->box_item_class_prob_table(), area_norm); switch (item_class) { case 0: // Weapon item.data1[0] = 0; @@ -151,21 +149,21 @@ ItemData ItemCreator::on_box_item_drop_with_norm_area(uint8_t area_norm) { return item; } -ItemData ItemCreator::on_monster_item_drop_with_norm_area(uint32_t enemy_type, uint8_t norm_area) { +ItemData ItemCreator::on_monster_item_drop_with_norm_area(uint32_t enemy_type, uint8_t area_norm) { if (enemy_type > 0x58) { this->log.warning("Invalid enemy type: %" PRIX32, enemy_type); return ItemData(); } this->log.info("Enemy type: %" PRIX32, enemy_type); - uint8_t type_drop_prob = this->pt->enemy_type_drop_probs[enemy_type]; + uint8_t type_drop_prob = this->pt->enemy_type_drop_probs().at(enemy_type); uint8_t drop_sample = this->rand_int(100); if (drop_sample >= type_drop_prob) { this->log.info("Drop not chosen (%hhu vs. %hhu)", drop_sample, type_drop_prob); return ItemData(); } - ItemData item = this->check_rare_spec_and_create_rare_enemy_item(enemy_type); + ItemData item = this->check_rare_spec_and_create_rare_enemy_item(enemy_type, area_norm); if (item.empty()) { uint32_t item_class_determinant = this->should_allow_meseta_drops() @@ -181,7 +179,7 @@ ItemData ItemCreator::on_monster_item_drop_with_norm_area(uint32_t enemy_type, u item_class = 4; break; case 2: - item_class = this->pt->enemy_item_classes[enemy_type]; + item_class = this->pt->enemy_item_classes().at(enemy_type); break; default: throw logic_error("invalid item class determinant"); @@ -208,14 +206,14 @@ ItemData ItemCreator::on_monster_item_drop_with_norm_area(uint32_t enemy_type, u break; case 5: // Meseta item.data1[0] = 0x04; - item.data2d = this->choose_meseta_amount(this->pt->enemy_meseta_ranges, enemy_type) & 0xFFFF; + item.data2d = this->choose_meseta_amount(this->pt->enemy_meseta_ranges(), enemy_type) & 0xFFFF; break; default: return item; } if (item.data1[0] != 0x04) { - this->generate_common_item_variances(norm_area, item); + this->generate_common_item_variances(area_norm, item); } } @@ -231,7 +229,7 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area_nor 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); + item = this->check_rate_and_create_rare_item(spec, area_norm); if (!item.empty()) { this->log.info("Box spec %08" PRIX32 " produced item %02hhX%02hhX%02hhX", spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); @@ -254,7 +252,7 @@ float ItemCreator::rand_float_0_1_from_crypt() { template uint32_t ItemCreator::choose_meseta_amount( - const parray, NumRanges> ranges, + const parray, NumRanges> ranges, size_t table_index) { uint16_t min = ranges[table_index].min; uint16_t max = ranges[table_index].max; @@ -273,7 +271,7 @@ bool ItemCreator::should_allow_meseta_drops() const { return (this->mode != GameMode::CHALLENGE); } -ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type) { +ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type, uint8_t area_norm) { ItemData item; if (this->are_rare_drops_allowed() && (enemy_type > 0) && (enemy_type < 0x58)) { // Note: In the original implementation, enemies can only have one possible @@ -283,7 +281,7 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_ 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); + item = this->check_rate_and_create_rare_item(spec, area_norm); if (!item.empty()) { this->log.info("Enemy spec %08" PRIX32 " produced item %02hhX%02hhX%02hhX", spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]); @@ -296,8 +294,7 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_ return item; } -ItemData ItemCreator::check_rate_and_create_rare_item( - const RareItemSet::ExpandedDrop& drop) { +ItemData ItemCreator::check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop, uint8_t area_norm) { if (drop.probability == 0) { return ItemData(); } @@ -314,7 +311,11 @@ ItemData ItemCreator::check_rate_and_create_rare_item( item.data1[2] = drop.item_code[2]; switch (item.data1[0]) { case 0: - this->generate_rare_weapon_bonuses(item, this->rand_int(10)); + if (this->pt->has_rare_bonus_value_prob_table()) { + this->generate_rare_weapon_bonuses(item, this->rand_int(10)); + } else { + this->generate_common_weapon_bonuses(item, area_norm); + } this->set_item_unidentified_flag_if_not_challenge(item); break; case 1: @@ -338,17 +339,18 @@ ItemData ItemCreator::check_rate_and_create_rare_item( return item; } -void ItemCreator::generate_rare_weapon_bonuses( - ItemData& item, uint32_t random_sample) { +void ItemCreator::generate_rare_weapon_bonuses(ItemData& item, uint32_t random_sample) { if (item.data1[0] != 0) { return; } + if (!this->pt->has_rare_bonus_value_prob_table()) { + throw logic_error("generate_rare_weapon_bonuses called for common item table without rare bonus value probability table"); + } + for (size_t z = 0; z < 6; z += 2) { - uint8_t bonus_type = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->bonus_type_prob_tables, random_sample); - int16_t bonus_value = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->bonus_value_prob_tables, 5); + uint8_t bonus_type = this->get_rand_from_weighted_tables_2d_vertical(this->pt->bonus_type_prob_table(), random_sample); + int16_t bonus_value = this->get_rand_from_weighted_tables_2d_vertical(this->pt->bonus_value_prob_table(), 5); item.data1[z + 6] = bonus_type; item.data1[z + 7] = bonus_value * 5 - 10; // Note: The original code has a special case here, which divides @@ -362,23 +364,24 @@ void ItemCreator::generate_rare_weapon_bonuses( void ItemCreator::generate_common_weapon_bonuses( ItemData& item, uint8_t area_norm) { - if (item.data1[0] == 0) { - for (size_t row = 0; row < 3; row++) { - uint8_t spec = this->pt->nonrare_bonus_prob_spec[row][area_norm]; - if (spec != 0xFF) { - item.data1[(row * 2) + 6] = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->bonus_type_prob_tables, area_norm); - int16_t amount = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->bonus_value_prob_tables, spec); - item.data1[(row * 2) + 7] = amount * 5 - 10; - } - // Note: The original code has a special case here, which divides - // item.data1[z + 7] by 5 and multiplies it by 5 again if bonus_type is 5 - // (Hit). Why this is done is unclear, because item.data1[z + 7] must - // already be a multiple of 5. - } - this->deduplicate_weapon_bonuses(item); + if (item.data1[0] != 0) { + return; } + + for (size_t row = 0; row < 3; row++) { + uint8_t spec = this->pt->nonrare_bonus_prob_spec().at(row).at(area_norm); + if (spec != 0xFF) { + item.data1[(row * 2) + 6] = this->get_rand_from_weighted_tables_2d_vertical(this->pt->bonus_type_prob_table(), area_norm); + int16_t amount = this->get_rand_from_weighted_tables_2d_vertical(this->pt->bonus_value_prob_table(), spec); + item.data1[(row * 2) + 7] = amount * 5 - 10; + } + // Note: The original code has a special case here, which divides + // item.data1[z + 7] by 5 and multiplies it by 5 again if bonus_type is 5 + // (Hit). Why this is done is unclear, because item.data1[z + 7] must + // already be a multiple of 5. + } + + this->deduplicate_weapon_bonuses(item); } void ItemCreator::deduplicate_weapon_bonuses(ItemData& item) const { @@ -523,7 +526,7 @@ void ItemCreator::generate_common_item_variances(uint32_t norm_area, ItemData& i break; case 1: if (item.data1[1] == 3) { - float f1 = 1.0 + this->pt->unit_maxes[norm_area]; + float f1 = 1.0 + this->pt->unit_maxes_table().at(norm_area); float f2 = this->rand_float_0_1_from_crypt(); this->generate_common_unit_variances(static_cast(f1 * f2) & 0xFF, item); if (item.data1[2] == 0xFF) { @@ -540,7 +543,7 @@ void ItemCreator::generate_common_item_variances(uint32_t norm_area, ItemData& i this->generate_common_tool_variances(norm_area, item); break; case 4: - item.data2d = this->choose_meseta_amount(this->pt->box_meseta_ranges, norm_area) & 0xFFFF; + item.data2d = this->choose_meseta_amount(this->pt->box_meseta_ranges(), norm_area) & 0xFFFF; break; default: // Note: The original code does the following here: @@ -557,9 +560,8 @@ void ItemCreator::generate_common_armor_or_shield_type_and_variances( char area_norm, ItemData& item) { this->generate_common_armor_slots_and_bonuses(item); - uint8_t type = this->get_rand_from_weighted_tables_1d( - this->pt->armor_shield_type_index_prob_table); - item.data1[2] = area_norm + type + this->pt->armor_or_shield_type_bias; + uint8_t type = this->get_rand_from_weighted_tables_1d(this->pt->armor_shield_type_index_prob_table()); + item.data1[2] = area_norm + type + this->pt->armor_or_shield_type_bias(); if (item.data1[2] < 3) { item.data1[2] = 0; } else { @@ -582,22 +584,20 @@ void ItemCreator::generate_common_armor_slots_and_bonuses(ItemData& item) { } void ItemCreator::generate_common_armor_slot_count(ItemData& item) { - item.data1[5] = this->get_rand_from_weighted_tables_1d(this->pt->armor_slot_count_prob_table); + item.data1[5] = this->get_rand_from_weighted_tables_1d(this->pt->armor_slot_count_prob_table()); } void ItemCreator::generate_common_tool_variances(uint32_t area_norm, ItemData& item) { item.clear(); - uint8_t tool_class = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->tool_class_prob_table, area_norm); + uint8_t tool_class = this->get_rand_from_weighted_tables_2d_vertical(this->pt->tool_class_prob_table(), area_norm); if (tool_class == 0x1A) { tool_class = 0x73; } this->generate_common_tool_type(tool_class, item); if (item.data1[1] == 0x02) { // Tech disk - item.data1[4] = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->technique_index_prob_table, area_norm); + item.data1[4] = this->get_rand_from_weighted_tables_2d_vertical(this->pt->technique_index_prob_table(), area_norm); item.data1[2] = this->generate_tech_disk_level(item.data1[4], area_norm); this->clear_tool_item_if_invalid(item); } @@ -605,15 +605,13 @@ void ItemCreator::generate_common_tool_variances(uint32_t area_norm, ItemData& i } uint8_t ItemCreator::generate_tech_disk_level(uint32_t tech_num, uint32_t area_norm) { - uint8_t min = this->pt->technique_level_ranges[tech_num][area_norm].min; - uint8_t max = this->pt->technique_level_ranges[tech_num][area_norm].max; - - if (((min == 0xFF) || (max == 0xFF)) || (max < min)) { + const auto& range = this->pt->technique_level_ranges().at(tech_num).at(area_norm); + if (((range.min == 0xFF) || (range.max == 0xFF)) || (range.max < range.min)) { return 0xFF; - } else if (min != max) { - return this->rand_int((max - min) + 1) + min; + } else if (range.min != range.max) { + return this->rand_int((range.max - range.min) + 1) + range.min; } - return min; + return range.min; } void ItemCreator::generate_common_tool_type(uint8_t tool_class, ItemData& item) const { @@ -638,12 +636,12 @@ void ItemCreator::generate_common_weapon_variances(uint8_t area_norm, ItemData& weapon_type_prob_table[0] = 0; memmove( weapon_type_prob_table.data() + 1, - this->pt->base_weapon_type_prob_table.data(), + this->pt->base_weapon_type_prob_table().data(), 0x0C); for (size_t z = 1; z < 13; z++) { // Technically this should be `if (... < 0)`, but whatever - if ((area_norm + this->pt->subtype_base_table[z - 1]) & 0x80) { + if ((area_norm + this->pt->subtype_base_table().at(z - 1)) & 0x80) { weapon_type_prob_table[z] = 0; } } @@ -652,8 +650,8 @@ void ItemCreator::generate_common_weapon_variances(uint8_t area_norm, ItemData& if (item.data1[1] == 0) { item.clear(); } else { - int8_t subtype_base = this->pt->subtype_base_table[item.data1[1] - 1]; - uint8_t area_length = this->pt->subtype_area_length_table[item.data1[1] - 1]; + int8_t subtype_base = this->pt->subtype_base_table().at(item.data1[1] - 1); + uint8_t area_length = this->pt->subtype_area_length_table().at(item.data1[1] - 1); if (subtype_base < 0) { item.data1[2] = (area_norm + subtype_base) / area_length; this->generate_common_weapon_grind( @@ -673,8 +671,7 @@ void ItemCreator::generate_common_weapon_grind( ItemData& item, uint8_t offset_within_subtype_range) { if (item.data1[0] == 0) { uint8_t offset = clamp(offset_within_subtype_range, 0, 3); - item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical( - this->pt->grind_prob_tables, offset); + item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical(this->pt->grind_prob_table(), offset); } } @@ -686,11 +683,11 @@ void ItemCreator::generate_common_weapon_special( if (this->item_parameter_table->is_item_rare(item)) { return; } - uint8_t special_mult = this->pt->special_mult[area_norm]; + uint8_t special_mult = this->pt->special_mult().at(area_norm); if (special_mult == 0) { return; } - if (this->rand_int(100) >= this->pt->special_percent[area_norm]) { + if (this->rand_int(100) >= this->pt->special_percent().at(area_norm)) { return; } item.data1[4] = this->choose_weapon_special( @@ -807,8 +804,7 @@ void ItemCreator::generate_common_unit_variances(uint8_t det, ItemData& item) { // 0, 1, 2, or 3. An input table of 40 40 80 would return 2 50% of the time, and // 0 or 1 each 25% of the time. template -IntT ItemCreator::get_rand_from_weighted_tables( - const IntT* tables, size_t offset, size_t num_values, size_t stride) { +IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offset, size_t num_values, size_t stride) { uint64_t rand_max = 0; for (size_t x = 0; x != num_values; x++) { rand_max += tables[x * stride + offset]; @@ -834,10 +830,8 @@ IntT ItemCreator::get_rand_from_weighted_tables_1d(const parray& tables } template -IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical( - const parray, Y>& tables, size_t offset) { - return ItemCreator::get_rand_from_weighted_tables(tables[0].data(), - offset, Y, X); +IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical(const parray, Y>& tables, size_t offset) { + return ItemCreator::get_rand_from_weighted_tables(tables[0].data(), offset, Y, X); } vector ItemCreator::generate_armor_shop_contents(size_t player_level) { diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index 2ca0bfc4..69314ea9 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -49,14 +49,13 @@ private: GameMode mode; uint8_t difficulty; uint8_t section_id; - std::shared_ptr common_item_set; std::shared_ptr rare_item_set; std::shared_ptr armor_random_set; std::shared_ptr tool_random_set; std::shared_ptr weapon_random_set; std::shared_ptr tekker_adjustment_set; std::shared_ptr item_parameter_table; - const CommonItemSet::Table* pt; + std::shared_ptr pt; std::shared_ptr restrictions; parray unit_weights_table1; @@ -78,14 +77,14 @@ private: template uint32_t choose_meseta_amount( - const parray, NumRanges> ranges, + const parray, NumRanges> ranges, size_t table_index); bool should_allow_meseta_drops() const; - ItemData check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type); + ItemData check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type, uint8_t area_norm); ItemData check_rare_specs_and_create_rare_box_item(uint8_t area_norm); - ItemData check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop); + ItemData check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop, uint8_t area_norm); void generate_rare_weapon_bonuses(ItemData& item, uint32_t random_sample); void deduplicate_weapon_bonuses(ItemData& item) const; diff --git a/src/Lobby.cc b/src/Lobby.cc index d8e4f62e..9f3c5224 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -47,16 +47,20 @@ void Lobby::create_item_creator() { auto s = this->require_server_state(); shared_ptr rare_item_set; + shared_ptr common_item_set; if (this->base_version == GameVersion::BB) { + common_item_set = s->common_item_set_v3; rare_item_set = s->rare_item_sets.at("rare-table-v4"); } else if (this->base_version == GameVersion::GC || this->base_version == GameVersion::XB) { + common_item_set = s->common_item_set_v3; rare_item_set = s->rare_item_sets.at("rare-table-v3"); } else { - // TODO: Should there be a separate table for V1 eventually? + // TODO: Should there be separate tables for V1 eventually? + common_item_set = s->common_item_set_v2; rare_item_set = s->rare_item_sets.at("rare-table-v2"); } this->item_creator.reset(new ItemCreator( - s->common_item_set, + common_item_set, rare_item_set, s->armor_random_set, s->tool_random_set, diff --git a/src/ServerState.cc b/src/ServerState.cc index 16a24de2..4449c11e 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -968,13 +968,16 @@ void ServerState::load_item_tables() { this->rare_item_sets.emplace("rare-table-v4", new RELRareItemSet(data)); } - // Note: These files don't exist in BB, so we use the GC versions of them + config_log.info("Loading v2 common item table"); + shared_ptr ct_data_v2(new string(load_file("system/item-tables/ItemCT-v2.afs"))); + shared_ptr pt_data_v2(new string(load_file("system/item-tables/ItemPT-v2.afs"))); + this->common_item_set_v2.reset(new AFSV2CommonItemSet(pt_data_v2, ct_data_v2)); + config_log.info("Loading v3 common item table"); + shared_ptr pt_data_v3(new string(load_file("system/item-tables/ItemPT-gc.gsl"))); + this->common_item_set_v3.reset(new GSLV3CommonItemSet(pt_data_v3, true)); + // Note: The ItemPT files don't exist in BB, so we use the GC versions of them // instead. This doesn't include Episode 4 of course, so we use Episode 1 // parameters for Episode 4 implicitly. - config_log.info("Loading common item table"); - shared_ptr pt_data(new string(load_file( - "system/item-tables/ItemPT-gc.gsl"))); - this->common_item_set.reset(new CommonItemSet(pt_data)); config_log.info("Loading armor table"); shared_ptr armor_data(new string(load_file( diff --git a/src/ServerState.hh b/src/ServerState.hh index 91aae008..47fa24a9 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -100,7 +100,8 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr battle_params; std::shared_ptr bb_data_gsl; std::unordered_map> rare_item_sets; - std::shared_ptr common_item_set; + std::shared_ptr common_item_set_v2; + std::shared_ptr common_item_set_v3; std::shared_ptr armor_random_set; std::shared_ptr tool_random_set; std::array, 4> weapon_random_sets; diff --git a/system/item-tables/ItemCT-v2.afs b/system/item-tables/ItemCT-v2.afs new file mode 100644 index 00000000..eb1718aa Binary files /dev/null and b/system/item-tables/ItemCT-v2.afs differ diff --git a/system/item-tables/ItemPT-v2.afs b/system/item-tables/ItemPT-v2.afs new file mode 100644 index 00000000..47a15b0a Binary files /dev/null and b/system/item-tables/ItemPT-v2.afs differ