diff --git a/src/CommonItemSet.cc b/src/CommonItemSet.cc index 70e347d5..b3c3cecc 100644 --- a/src/CommonItemSet.cc +++ b/src/CommonItemSet.cc @@ -33,7 +33,7 @@ CommonItemSet::Table::Table( 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.unit_max_stars_offset = be_offsets.unit_max_stars_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)); @@ -171,8 +171,8 @@ const parray, 0x0A>, 0x13>& CommonIt 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& CommonItemSet::Table::unit_max_stars_table() const { + return this->r.pget>(this->offsets.unit_max_stars_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); diff --git a/src/CommonItemSet.hh b/src/CommonItemSet.hh index b036d6c7..01278d71 100644 --- a/src/CommonItemSet.hh +++ b/src/CommonItemSet.hh @@ -41,7 +41,7 @@ public: 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& unit_max_stars_table() const; const parray, 7>& box_item_class_prob_table() const; private: @@ -219,21 +219,21 @@ public: /* 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 + // These values specify the maximum number of stars any generated unit can + // have in each area. The values here are not inclusive; that is, a value + // of 7 means that only units with 1-6 stars can drop in that area. The + // game uniformly chooses a random number of stars in the acceptable + // range, then uniformly chooses a random unit with that many stars. // V2/V3: -> parray - /* 4C */ U32T unit_maxes_offset; + /* 4C */ U32T unit_max_stars_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. + // 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 diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index ec262db6..a83c5ac1 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -38,7 +38,7 @@ ItemCreator::ItemCreator( pt(common_item_set->get_table(this->episode, this->mode, this->difficulty, this->section_id)), restrictions(restrictions), random_crypt(random_seed) { - this->generate_unit_weights_tables(); + this->generate_unit_stars_tables(); } void ItemCreator::clear_destroyed_entities() { @@ -560,11 +560,11 @@ void ItemCreator::generate_common_item_variances(uint32_t area_norm, ItemData& i break; case 1: if (item.data1[1] == 3) { - float f1 = 1.0 + this->pt->unit_maxes_table().at(area_norm); + float f1 = 1.0 + this->pt->unit_max_stars_table().at(area_norm); float f2 = this->rand_float_0_1_from_crypt(); - uint8_t det = static_cast(f1 * f2) & 0xFF; - this->log.info("Unit variances determinant: %g * %g = %08" PRIX32, f1, f2, det); - this->generate_common_unit_variances(det, item); + uint8_t stars = static_cast(f1 * f2) & 0xFF; + this->log.info("Unit stars: %g * %g = %" PRIu32, f1, f2, stars); + this->generate_common_unit_variances(stars, item); if (item.data1[2] == 0xFF) { this->log.info("Unit subtype not valid; clearing item"); item.clear(); @@ -778,111 +778,76 @@ uint8_t ItemCreator::choose_weapon_special(uint8_t det) { return 0; } -void ItemCreator::generate_unit_weights_tables() { +void ItemCreator::generate_unit_stars_tables() { // Note: This part of the function was originally in a different function, // since it had another callsite. Unlike the original code, we generate these // tables only once at construction time, so we've inlined the function here. size_t star_base_index; + uint8_t num_units; switch (this->version) { case GameVersion::DC: case GameVersion::PC: star_base_index = 0x1D1; - this->unit_weights_table1.resize(0x84); + num_units = 0x44; break; case GameVersion::GC: case GameVersion::XB: star_base_index = 0x2AF; - this->unit_weights_table1.resize(0x88); + num_units = 0x48; break; case GameVersion::BB: star_base_index = 0x37D; - this->unit_weights_table1.resize(0x88); + num_units = 0x64; break; default: throw logic_error("invalid game version"); } - size_t z; - for (z = 0; z < 0x10; z++) { - uint8_t v = this->item_parameter_table->get_item_stars(z + star_base_index); - this->unit_weights_table1.at((z * 5) + 0) = v - 1; - this->unit_weights_table1.at((z * 5) + 1) = v - 1; - this->unit_weights_table1.at((z * 5) + 2) = v; - this->unit_weights_table1.at((z * 5) + 3) = v + 1; - this->unit_weights_table1.at((z * 5) + 4) = v + 1; + for (auto& vec : this->unit_results_by_star_count) { + vec.clear(); } - for (; z < (this->unit_weights_table1.size() - 0x40); z++) { - this->unit_weights_table1.at(z + 0x40) = this->item_parameter_table->get_item_stars(z + star_base_index); - } - // Note: Inlining ends here - this->unit_weights_table2.clear(0); - for (size_t z = 0; z < 0x88; z++) { - uint8_t index = this->unit_weights_table1[z]; - if (index < this->unit_weights_table2.size()) { - this->unit_weights_table2[index]++; + for (uint8_t z = 0; z < num_units; z++) { + uint8_t stars = this->item_parameter_table->get_item_stars(z + star_base_index); + if (z < 0x10) { + // Units 00-0F can have modifiers; others can't + this->unit_results_by_star_count.at(stars - 1).emplace_back(UnitResult{z, -2}); + this->unit_results_by_star_count.at(stars - 1).emplace_back(UnitResult{z, -1}); + this->unit_results_by_star_count.at(stars + 1).emplace_back(UnitResult{z, 1}); + this->unit_results_by_star_count.at(stars + 1).emplace_back(UnitResult{z, 2}); } - z = z + 1; + this->unit_results_by_star_count.at(stars).emplace_back(UnitResult{z, 0}); + } + + for (size_t z = 0; z < this->unit_results_by_star_count.size(); z++) { + fprintf(stderr, "result table %zu\n", z); + print_data(stderr, this->unit_results_by_star_count[z].data(), this->unit_results_by_star_count[z].size() * 2); } } -void ItemCreator::generate_common_unit_variances(uint8_t det, ItemData& item) { - if (det >= 0x0D) { +void ItemCreator::generate_common_unit_variances(uint8_t stars, ItemData& item) { + if (stars >= 0x0D) { return; } item.clear(); - item.data1[0] = 0x01; - item.data1[1] = 0x03; - // Note: The original code calls generate_unit_weights_table1 here (which we - // have inlined into generate_unit_weights_tables above). This call seems - // unnecessary because the contents of the tables don't depend on anything - // except what appears in ItemPMT, which is essentially constant, so we - // don't bother regenerating the table here. - - if (this->unit_weights_table2[det] == 0) { - this->log.info("Unit weights table 2 entry is zero; skipping variances"); + const auto& results = this->unit_results_by_star_count.at(stars); + if (results.empty()) { + this->log.info("There are no available units with %hhu stars", stars); return; } - size_t which = this->rand_int(this->unit_weights_table2[det]); - this->log.info("Unit values: which=%02zX max=%02hhX", which, this->unit_weights_table2[det]); - size_t current_index = 0; - for (size_t z = 0; z < this->unit_weights_table1.size(); z++) { - if (det != this->unit_weights_table1[z]) { - continue; - } - if (current_index != which) { - current_index++; - } else { - if (z >= 0x50) { - if (det <= 0x87) { - item.data1[2] = z + 0xC0; - } - } else { - item.data1[2] = z / 5; - const auto& def = this->item_parameter_table->get_unit(item.data1[2]); - switch (z % 5) { - case 0: - item.set_unit_bonus(-(def.modifier_amount * 2)); - break; - case 1: - item.set_unit_bonus(-def.modifier_amount); - break; - case 2: - break; - case 3: - item.set_unit_bonus(def.modifier_amount); - break; - case 4: - item.set_unit_bonus(def.modifier_amount * 2); - break; - } - } - break; - } + const auto& result = (results.size() == 1) ? results[0] : results[this->rand_int(results.size())]; + item.data1[0] = 0x01; + item.data1[1] = 0x03; + item.data1[2] = result.unit; + if (result.modifier) { + const auto& def = this->item_parameter_table->get_unit(result.unit); + item.set_unit_bonus(def.modifier_amount * result.modifier); } + this->log.info("Generated unit %02hhX with modifier %hhd, from %zu choices with %hhu stars", + result.unit, result.modifier, results.size(), stars); } // Returns a weighted random result, indicating the chosen position in the diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index 9f433155..082cb15c 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -64,8 +64,11 @@ private: std::shared_ptr pt; std::shared_ptr restrictions; - std::vector unit_weights_table1; - parray unit_weights_table2; + struct UnitResult { + uint8_t unit; + int8_t modifier; + } __attribute__((packed)); + std::array, 13> unit_results_by_star_count; // Note: The original implementation uses 17 different random states for some // reason. We forego that and use only one for simplicity. @@ -115,8 +118,8 @@ private: void generate_common_weapon_bonuses(ItemData& item, uint8_t area_norm); void generate_common_weapon_special(ItemData& item, uint8_t area_norm); uint8_t choose_weapon_special(uint8_t det); - void generate_unit_weights_tables(); - void generate_common_unit_variances(uint8_t det, ItemData& item); + void generate_unit_stars_tables(); + void generate_common_unit_variances(uint8_t stars, ItemData& item); void choose_tech_disk_level_for_tool_shop(ItemData& item, size_t player_level, uint8_t tech_num_index); static void clear_tool_item_if_invalid(ItemData& item); void clear_item_if_restricted(ItemData& item) const;