rewrite unit generation logic to fix v2/bb behavior
This commit is contained in:
@@ -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<Offsets<false>>(r.pget_u32l(this->r.size() - 0x10));
|
||||
@@ -171,8 +171,8 @@ const parray<parray<CommonItemSet::Table::Range<uint8_t>, 0x0A>, 0x13>& CommonIt
|
||||
uint8_t CommonItemSet::Table::armor_or_shield_type_bias() const {
|
||||
return this->offsets.armor_or_shield_type_bias;
|
||||
}
|
||||
const parray<uint8_t, 0x0A>& CommonItemSet::Table::unit_maxes_table() const {
|
||||
return this->r.pget<parray<uint8_t, 0x0A>>(this->offsets.unit_maxes_offset);
|
||||
const parray<uint8_t, 0x0A>& CommonItemSet::Table::unit_max_stars_table() const {
|
||||
return this->r.pget<parray<uint8_t, 0x0A>>(this->offsets.unit_max_stars_offset);
|
||||
}
|
||||
const parray<parray<uint8_t, 10>, 7>& CommonItemSet::Table::box_item_class_prob_table() const {
|
||||
return this->r.pget<parray<parray<uint8_t, 10>, 7>>(this->offsets.box_item_class_prob_table_offset);
|
||||
|
||||
+13
-13
@@ -41,7 +41,7 @@ public:
|
||||
const parray<parray<uint8_t, 0x0A>, 0x13>& technique_index_prob_table() const;
|
||||
const parray<parray<Range<uint8_t>, 0x0A>, 0x13>& technique_level_ranges() const;
|
||||
uint8_t armor_or_shield_type_bias() const;
|
||||
const parray<uint8_t, 0x0A>& unit_maxes_table() const;
|
||||
const parray<uint8_t, 0x0A>& unit_max_stars_table() const;
|
||||
const parray<parray<uint8_t, 10>, 7>& box_item_class_prob_table() const;
|
||||
|
||||
private:
|
||||
@@ -219,21 +219,21 @@ public:
|
||||
/* 48 */ uint8_t armor_or_shield_type_bias;
|
||||
/* 49 */ parray<uint8_t, 3> 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<uint8_t, 0x0A>
|
||||
/* 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
|
||||
|
||||
+40
-75
@@ -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<uint32_t>(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<uint32_t>(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
|
||||
|
||||
+7
-4
@@ -64,8 +64,11 @@ private:
|
||||
std::shared_ptr<const CommonItemSet::Table> pt;
|
||||
std::shared_ptr<const BattleRules> restrictions;
|
||||
|
||||
std::vector<uint8_t> unit_weights_table1;
|
||||
parray<int8_t, 0x0D> unit_weights_table2;
|
||||
struct UnitResult {
|
||||
uint8_t unit;
|
||||
int8_t modifier;
|
||||
} __attribute__((packed));
|
||||
std::array<std::vector<UnitResult>, 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;
|
||||
|
||||
Reference in New Issue
Block a user