rewrite CommonItemSet to support v2
This commit is contained in:
Binary file not shown.
+257
-18
@@ -1,28 +1,267 @@
|
||||
#include "CommonItemSet.hh"
|
||||
|
||||
#include "AFSArchive.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CommonItemSet::CommonItemSet(shared_ptr<const string> data)
|
||||
: gsl(data, true) {}
|
||||
|
||||
const CommonItemSet::Table<true>& 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<true>)) {
|
||||
throw runtime_error(string_printf(
|
||||
"ItemPT entry %s is too small (received %zX bytes, expected %zX bytes)",
|
||||
filename.c_str(), data.second, sizeof(Table<true>)));
|
||||
CommonItemSet::Table::Table(
|
||||
shared_ptr<const string> 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<Offsets<true>>(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<Offsets<false>>(r.pget_u32l(this->r.size() - 0x10));
|
||||
}
|
||||
}
|
||||
|
||||
const parray<uint8_t, 0x0C>& CommonItemSet::Table::base_weapon_type_prob_table() const {
|
||||
return this->r.pget<parray<uint8_t, 0x0C>>(this->offsets.base_weapon_type_prob_table_offset);
|
||||
}
|
||||
const parray<int8_t, 0x0C>& CommonItemSet::Table::subtype_base_table() const {
|
||||
return this->r.pget<parray<int8_t, 0x0C>>(this->offsets.subtype_base_table_offset);
|
||||
}
|
||||
const parray<uint8_t, 0x0C>& CommonItemSet::Table::subtype_area_length_table() const {
|
||||
return this->r.pget<parray<uint8_t, 0x0C>>(this->offsets.subtype_area_length_table_offset);
|
||||
}
|
||||
const parray<parray<uint8_t, 4>, 9>& CommonItemSet::Table::grind_prob_table() const {
|
||||
return this->r.pget<parray<parray<uint8_t, 4>, 9>>(this->offsets.grind_prob_table_offset);
|
||||
}
|
||||
const parray<uint8_t, 0x05>& CommonItemSet::Table::armor_shield_type_index_prob_table() const {
|
||||
return this->r.pget<parray<uint8_t, 0x05>>(this->offsets.armor_shield_type_index_prob_table_offset);
|
||||
}
|
||||
const parray<uint8_t, 0x05>& CommonItemSet::Table::armor_slot_count_prob_table() const {
|
||||
return this->r.pget<parray<uint8_t, 0x05>>(this->offsets.armor_slot_count_prob_table_offset);
|
||||
}
|
||||
const parray<CommonItemSet::Table::Range<uint16_t>, 0x64>& CommonItemSet::Table::enemy_meseta_ranges() const {
|
||||
if (!this->parsed_enemy_meseta_ranges_populated) {
|
||||
if (this->is_big_endian) {
|
||||
const auto& data = r.pget<parray<Range<be_uint16_t>, 0x64>>(this->offsets.enemy_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->parsed_enemy_meseta_ranges[z] = Range<uint16_t>{data[z].min, data[z].max};
|
||||
}
|
||||
} else {
|
||||
const auto& data = r.pget<parray<Range<le_uint16_t>, 0x64>>(this->offsets.enemy_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->parsed_enemy_meseta_ranges[z] = Range<uint16_t>{data[z].min, data[z].max};
|
||||
}
|
||||
}
|
||||
this->parsed_enemy_meseta_ranges_populated = true;
|
||||
}
|
||||
return this->parsed_enemy_meseta_ranges;
|
||||
}
|
||||
const parray<uint8_t, 0x64>& CommonItemSet::Table::enemy_type_drop_probs() const {
|
||||
return this->r.pget<parray<uint8_t, 0x64>>(this->offsets.enemy_type_drop_probs_offset);
|
||||
}
|
||||
const parray<uint8_t, 0x64>& CommonItemSet::Table::enemy_item_classes() const {
|
||||
return this->r.pget<parray<uint8_t, 0x64>>(this->offsets.enemy_item_classes_offset);
|
||||
}
|
||||
const parray<CommonItemSet::Table::Range<uint16_t>, 0x0A>& CommonItemSet::Table::box_meseta_ranges() const {
|
||||
if (!this->parsed_box_meseta_ranges_populated) {
|
||||
if (this->is_big_endian) {
|
||||
const auto& data = r.pget<parray<Range<be_uint16_t>, 0x0A>>(this->offsets.box_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->parsed_box_meseta_ranges[z] = Range<uint16_t>{data[z].min, data[z].max};
|
||||
}
|
||||
} else {
|
||||
const auto& data = r.pget<parray<Range<le_uint16_t>, 0x0A>>(this->offsets.box_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->parsed_box_meseta_ranges[z] = Range<uint16_t>{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<parray<uint16_t, 6>, 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<parray<parray<uint8_t, 5>, 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<parray<parray<be_uint16_t, 6>, 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<parray<parray<le_uint16_t, 6>, 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<parray<uint8_t, 10>, 3>& CommonItemSet::Table::nonrare_bonus_prob_spec() const {
|
||||
return this->r.pget<parray<parray<uint8_t, 10>, 3>>(this->offsets.nonrare_bonus_prob_spec_offset);
|
||||
}
|
||||
const parray<parray<uint8_t, 10>, 6>& CommonItemSet::Table::bonus_type_prob_table() const {
|
||||
return this->r.pget<parray<parray<uint8_t, 10>, 6>>(this->offsets.bonus_type_prob_table_offset);
|
||||
}
|
||||
const parray<uint8_t, 0x0A>& CommonItemSet::Table::special_mult() const {
|
||||
return this->r.pget<parray<uint8_t, 0x0A>>(this->offsets.special_mult_offset);
|
||||
}
|
||||
const parray<uint8_t, 0x0A>& CommonItemSet::Table::special_percent() const {
|
||||
return this->r.pget<parray<uint8_t, 0x0A>>(this->offsets.special_percent_offset);
|
||||
}
|
||||
const parray<parray<uint16_t, 0x0A>, 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<parray<parray<be_uint16_t, 0x0A>, 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<parray<parray<le_uint16_t, 0x0A>, 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<parray<uint8_t, 0x0A>, 0x13>& CommonItemSet::Table::technique_index_prob_table() const {
|
||||
return this->r.pget<parray<parray<uint8_t, 0x0A>, 0x13>>(this->offsets.technique_index_prob_table_offset);
|
||||
}
|
||||
const parray<parray<CommonItemSet::Table::Range<uint8_t>, 0x0A>, 0x13>& CommonItemSet::Table::technique_level_ranges() const {
|
||||
return this->r.pget<parray<parray<Range<uint8_t>, 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<uint8_t, 0x0A>& CommonItemSet::Table::unit_maxes_table() const {
|
||||
return this->r.pget<parray<uint8_t, 0x0A>>(this->offsets.unit_maxes_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);
|
||||
}
|
||||
|
||||
uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) {
|
||||
// Bits: -----EEEMMDDSSSS
|
||||
return (((static_cast<uint16_t>(episode) << 8) & 0x0700) |
|
||||
((static_cast<uint16_t>(mode) << 6) & 0x00C0) |
|
||||
((static_cast<uint16_t>(difficulty) << 4) & 0x0030) |
|
||||
(static_cast<uint16_t>(secid) & 0x000F));
|
||||
}
|
||||
|
||||
shared_ptr<const CommonItemSet::Table> 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<const std::string> pt_afs_data,
|
||||
std::shared_ptr<const std::string> 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> 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> 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<const std::string> gsl_data, bool is_big_endian) {
|
||||
GSLArchive gsl(gsl_data, is_big_endian);
|
||||
|
||||
vector<Episode> 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> 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> 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<const Table<true>*>(data.first);
|
||||
}
|
||||
|
||||
RELFileSet::RELFileSet(std::shared_ptr<const std::string> data)
|
||||
|
||||
+278
-242
@@ -8,6 +8,284 @@
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class CommonItemSet {
|
||||
public:
|
||||
class Table {
|
||||
public:
|
||||
Table() = delete;
|
||||
Table(std::shared_ptr<const std::string> owned_data, const StringReader& r, bool big_endian, bool is_v3);
|
||||
|
||||
template <typename IntT>
|
||||
struct Range {
|
||||
IntT min;
|
||||
IntT max;
|
||||
} __attribute__((packed));
|
||||
|
||||
const parray<uint8_t, 0x0C>& base_weapon_type_prob_table() const;
|
||||
const parray<int8_t, 0x0C>& subtype_base_table() const;
|
||||
const parray<uint8_t, 0x0C>& subtype_area_length_table() const;
|
||||
const parray<parray<uint8_t, 4>, 9>& grind_prob_table() const;
|
||||
const parray<uint8_t, 0x05>& armor_shield_type_index_prob_table() const;
|
||||
const parray<uint8_t, 0x05>& armor_slot_count_prob_table() const;
|
||||
const parray<Range<uint16_t>, 0x64>& enemy_meseta_ranges() const;
|
||||
const parray<uint8_t, 0x64>& enemy_type_drop_probs() const;
|
||||
const parray<uint8_t, 0x64>& enemy_item_classes() const;
|
||||
const parray<Range<uint16_t>, 0x0A>& box_meseta_ranges() const;
|
||||
bool has_rare_bonus_value_prob_table() const;
|
||||
const parray<parray<uint16_t, 6>, 0x17>& bonus_value_prob_table() const;
|
||||
const parray<parray<uint8_t, 10>, 3>& nonrare_bonus_prob_spec() const;
|
||||
const parray<parray<uint8_t, 10>, 6>& bonus_type_prob_table() const;
|
||||
const parray<uint8_t, 0x0A>& special_mult() const;
|
||||
const parray<uint8_t, 0x0A>& special_percent() const;
|
||||
const parray<parray<uint16_t, 0x0A>, 0x1C>& tool_class_prob_table() const;
|
||||
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<parray<uint8_t, 10>, 7>& box_item_class_prob_table() const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
struct Offsets {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::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<uint8_t, 0x0C>
|
||||
/* 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<int8_t, 0x0C>
|
||||
/* 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<uint8_t, 0x0C>
|
||||
/* 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<parray<uint8_t, 4>, 9>
|
||||
/* 0C */ U32T grind_prob_table_offset;
|
||||
|
||||
// TODO: Figure out exactly how this table is used. Anchor: 80106D34
|
||||
// V2/GC: -> parray<uint8_t, 0x05>
|
||||
/* 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<uint8_t, 0x05>
|
||||
/* 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<Range<U16T>, 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<uint8_t, 0x64>
|
||||
/* 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<uint8_t, 0x64>
|
||||
/* 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<Range<U16T>, 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<parray<uint8_t, 5>, 0x17>
|
||||
// GC: -> parray<parray<U16T, 6>, 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<parray<uint8_t, 10>, 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<parray<uint8_t, 10>, 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<uint8_t, 0x0A>
|
||||
/* 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<uint8_t, 0x0A>
|
||||
/* 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<parray<U16T, 0x0A>, 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<parray<uint8_t, 0x0A>, 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<parray<Range<uint8_t>, 0x0A>, 0x13>
|
||||
/* 44 */ U32T technique_level_ranges_offset;
|
||||
|
||||
/* 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
|
||||
// V2/GC: -> parray<uint8_t, 0x0A>
|
||||
/* 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<parray<uint8_t, 10>, 7>
|
||||
/* 50 */ U32T box_item_class_prob_table_offset;
|
||||
|
||||
// There are several unused fields here.
|
||||
} __attribute__((packed));
|
||||
|
||||
std::shared_ptr<const std::string> owned_data;
|
||||
StringReader r;
|
||||
bool is_big_endian;
|
||||
bool is_v3;
|
||||
|
||||
Offsets<false> offsets;
|
||||
|
||||
mutable parray<Range<uint16_t>, 0x64> parsed_enemy_meseta_ranges;
|
||||
mutable bool parsed_enemy_meseta_ranges_populated = false;
|
||||
mutable parray<Range<uint16_t>, 0x0A> parsed_box_meseta_ranges;
|
||||
mutable bool parsed_box_meseta_ranges_populated = false;
|
||||
mutable parray<parray<uint16_t, 6>, 0x17> parsed_bonus_value_prob_table;
|
||||
mutable bool parsed_bonus_value_prob_table_populated = false;
|
||||
mutable parray<parray<uint16_t, 0x0A>, 0x1C> parsed_tool_class_prob_table;
|
||||
mutable bool parsed_tool_class_prob_table_populated = false;
|
||||
};
|
||||
|
||||
std::shared_ptr<const Table> 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<uint16_t, std::shared_ptr<Table>> tables;
|
||||
};
|
||||
|
||||
class AFSV2CommonItemSet : public CommonItemSet {
|
||||
public:
|
||||
AFSV2CommonItemSet(std::shared_ptr<const std::string> pt_afs_data, std::shared_ptr<const std::string> ct_afs_data);
|
||||
};
|
||||
|
||||
class GSLV3CommonItemSet : public CommonItemSet {
|
||||
public:
|
||||
GSLV3CommonItemSet(std::shared_ptr<const std::string> 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 <typename ItemT, size_t MaxCount>
|
||||
@@ -51,248 +329,6 @@ struct ProbabilityTable {
|
||||
}
|
||||
};
|
||||
|
||||
class CommonItemSet {
|
||||
public:
|
||||
template <typename IntT>
|
||||
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 <bool IsBigEndian>
|
||||
struct Table {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
using U32T = typename std::conditional<IsBigEndian, be_uint32_t, le_uint32_t>::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<uint8_t, 0x0C> 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<int8_t, 0x0C> 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<uint8_t, 0x0C> 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<parray<uint8_t, 4>, 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<parray<U16T, 6>, 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<parray<uint8_t, 10>, 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<parray<uint8_t, 10>, 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<uint8_t, 0x0A> special_mult;
|
||||
|
||||
// This array (indexed by area - 1) specifies the probability that any
|
||||
// non-rare weapon will have a special ability.
|
||||
/* 01C0 */ parray<uint8_t, 0x0A> special_percent;
|
||||
|
||||
// TODO: Figure out exactly how this table is used. Anchor: 80106D34
|
||||
/* 01CA */ parray<uint8_t, 0x05> armor_shield_type_index_prob_table;
|
||||
|
||||
// This index probability table specifies how common each possible slot
|
||||
// count is for armor drops.
|
||||
/* 01CF */ parray<uint8_t, 0x05> 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<uint8_t, 0x0A> 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<parray<U16T, 0x0A>, 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<parray<uint8_t, 0x0A>, 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<parray<Range<uint8_t>, 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<uint8_t, 0x64> enemy_type_drop_probs;
|
||||
|
||||
// This array (indexed by enemy_id) specifies the range of meseta values
|
||||
// that each enemy can drop.
|
||||
/* 06AC */ parray<Range<U16T>, 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<uint8_t, 0x64> enemy_item_classes;
|
||||
|
||||
// This table (indexed by area - 1) specifies the ranges of meseta values
|
||||
// that can drop from boxes.
|
||||
/* 08A0 */ parray<Range<U16T>, 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<parray<uint8_t, 10>, 7> box_item_class_prob_tables;
|
||||
|
||||
/* 090E */ parray<uint8_t, 2> 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<uint8_t, 3> 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<const std::string> data);
|
||||
|
||||
const Table<true>& get_table(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
|
||||
private:
|
||||
GSLArchive gsl;
|
||||
};
|
||||
|
||||
class RELFileSet {
|
||||
public:
|
||||
template <typename ValueT, typename WeightT = ValueT>
|
||||
|
||||
+64
-70
@@ -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 <size_t NumRanges>
|
||||
uint32_t ItemCreator::choose_meseta_amount(
|
||||
const parray<CommonItemSet::Range<be_uint16_t>, NumRanges> ranges,
|
||||
const parray<CommonItemSet::Table::Range<uint16_t>, 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<uint32_t>(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<uint8_t>(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 <typename IntT>
|
||||
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<IntT, X>& tables
|
||||
}
|
||||
|
||||
template <typename IntT, size_t X, size_t Y>
|
||||
IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical(
|
||||
const parray<parray<IntT, X>, Y>& tables, size_t offset) {
|
||||
return ItemCreator::get_rand_from_weighted_tables<IntT>(tables[0].data(),
|
||||
offset, Y, X);
|
||||
IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical(const parray<parray<IntT, X>, Y>& tables, size_t offset) {
|
||||
return ItemCreator::get_rand_from_weighted_tables<IntT>(tables[0].data(), offset, Y, X);
|
||||
}
|
||||
|
||||
vector<ItemData> ItemCreator::generate_armor_shop_contents(size_t player_level) {
|
||||
|
||||
+4
-5
@@ -49,14 +49,13 @@ private:
|
||||
GameMode mode;
|
||||
uint8_t difficulty;
|
||||
uint8_t section_id;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set;
|
||||
std::shared_ptr<const RareItemSet> rare_item_set;
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set;
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set;
|
||||
std::shared_ptr<const WeaponRandomSet> weapon_random_set;
|
||||
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table;
|
||||
const CommonItemSet::Table<true>* pt;
|
||||
std::shared_ptr<const CommonItemSet::Table> pt;
|
||||
std::shared_ptr<const BattleRules> restrictions;
|
||||
|
||||
parray<uint8_t, 0x88> unit_weights_table1;
|
||||
@@ -78,14 +77,14 @@ private:
|
||||
|
||||
template <size_t NumRanges>
|
||||
uint32_t choose_meseta_amount(
|
||||
const parray<CommonItemSet::Range<be_uint16_t>, NumRanges> ranges,
|
||||
const parray<CommonItemSet::Table::Range<uint16_t>, 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;
|
||||
|
||||
+6
-2
@@ -47,16 +47,20 @@ void Lobby::create_item_creator() {
|
||||
auto s = this->require_server_state();
|
||||
|
||||
shared_ptr<const RareItemSet> rare_item_set;
|
||||
shared_ptr<const CommonItemSet> 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,
|
||||
|
||||
+8
-5
@@ -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<string> ct_data_v2(new string(load_file("system/item-tables/ItemCT-v2.afs")));
|
||||
shared_ptr<string> 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<string> 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<string> 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<string> armor_data(new string(load_file(
|
||||
|
||||
+2
-1
@@ -100,7 +100,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const BattleParamsIndex> battle_params;
|
||||
std::shared_ptr<const GSLArchive> bb_data_gsl;
|
||||
std::unordered_map<std::string, std::shared_ptr<const RareItemSet>> rare_item_sets;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v2;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v3;
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set;
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set;
|
||||
std::array<std::shared_ptr<const WeaponRandomSet>, 4> weapon_random_sets;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user