add v4 ItemPT data
This commit is contained in:
+136
-181
@@ -6,176 +6,107 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
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) {
|
||||
CommonItemSet::Table::Table(const StringReader& r, bool is_big_endian, bool 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_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();
|
||||
this->parse_itempt_t<true>(r, is_v3);
|
||||
} else {
|
||||
this->offsets = r.pget<Offsets<false>>(r.pget_u32l(this->r.size() - 0x10));
|
||||
this->parse_itempt_t<false>(r, is_v3);
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
template <bool IsBigEndian>
|
||||
void CommonItemSet::Table::parse_itempt_t(const StringReader& r, bool is_v3) {
|
||||
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;
|
||||
|
||||
const auto& offsets = r.pget<Offsets<IsBigEndian>>(r.pget<U32T>(r.size() - 0x10));
|
||||
|
||||
this->base_weapon_type_prob_table = r.pget<parray<uint8_t, 0x0C>>(offsets.base_weapon_type_prob_table_offset);
|
||||
this->subtype_base_table = r.pget<parray<int8_t, 0x0C>>(offsets.subtype_base_table_offset);
|
||||
this->subtype_area_length_table = r.pget<parray<uint8_t, 0x0C>>(offsets.subtype_area_length_table_offset);
|
||||
this->grind_prob_table = r.pget<parray<parray<uint8_t, 4>, 9>>(offsets.grind_prob_table_offset);
|
||||
this->armor_shield_type_index_prob_table = r.pget<parray<uint8_t, 0x05>>(offsets.armor_shield_type_index_prob_table_offset);
|
||||
this->armor_slot_count_prob_table = r.pget<parray<uint8_t, 0x05>>(offsets.armor_slot_count_prob_table_offset);
|
||||
const auto& data = r.pget<parray<Range<U16T>, 0x64>>(offsets.enemy_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->enemy_meseta_ranges[z] = Range<uint16_t>{data[z].min, data[z].max};
|
||||
}
|
||||
this->enemy_type_drop_probs = r.pget<parray<uint8_t, 0x64>>(offsets.enemy_type_drop_probs_offset);
|
||||
this->enemy_item_classes = r.pget<parray<uint8_t, 0x64>>(offsets.enemy_item_classes_offset);
|
||||
{
|
||||
const auto& data = r.pget<parray<Range<U16T>, 0x0A>>(offsets.box_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
this->box_meseta_ranges[z] = Range<uint16_t>{data[z].min, data[z].max};
|
||||
}
|
||||
}
|
||||
this->has_rare_bonus_value_prob_table = is_v3;
|
||||
if (!this->has_rare_bonus_value_prob_table) { // V2
|
||||
const auto& data = r.pget<parray<parray<uint8_t, 5>, 0x17>>(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->bonus_value_prob_table[z][x] = data[z][x];
|
||||
}
|
||||
}
|
||||
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};
|
||||
} else { // V3
|
||||
const auto& data = r.pget<parray<parray<U16T, 6>, 0x17>>(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->bonus_value_prob_table[z][x] = data[z][x];
|
||||
}
|
||||
}
|
||||
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->nonrare_bonus_prob_spec = r.pget<parray<parray<uint8_t, 10>, 3>>(offsets.nonrare_bonus_prob_spec_offset);
|
||||
this->bonus_type_prob_table = r.pget<parray<parray<uint8_t, 10>, 6>>(offsets.bonus_type_prob_table_offset);
|
||||
this->special_mult = r.pget<parray<uint8_t, 0x0A>>(offsets.special_mult_offset);
|
||||
this->special_percent = r.pget<parray<uint8_t, 0x0A>>(offsets.special_percent_offset);
|
||||
{
|
||||
const auto& data = r.pget<parray<parray<U16T, 0x0A>, 0x1C>>(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->tool_class_prob_table[z][x] = data[z][x];
|
||||
}
|
||||
}
|
||||
this->parsed_bonus_value_prob_table_populated = true;
|
||||
}
|
||||
return this->parsed_bonus_value_prob_table;
|
||||
this->technique_index_prob_table = r.pget<parray<parray<uint8_t, 0x0A>, 0x13>>(offsets.technique_index_prob_table_offset);
|
||||
this->technique_level_ranges = r.pget<parray<parray<Range<uint8_t>, 0x0A>, 0x13>>(offsets.technique_level_ranges_offset);
|
||||
this->armor_or_shield_type_bias = offsets.armor_or_shield_type_bias;
|
||||
this->unit_max_stars_table = r.pget<parray<uint8_t, 0x0A>>(offsets.unit_max_stars_offset);
|
||||
this->box_item_class_prob_table = r.pget<parray<parray<uint8_t, 10>, 7>>(offsets.box_item_class_prob_table_offset);
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
void CommonItemSet::Table::print_enemy_table(FILE* stream) const {
|
||||
const auto& meseta_ranges = this->enemy_meseta_ranges;
|
||||
const auto& drop_probs = this->enemy_type_drop_probs;
|
||||
const auto& item_classes = this->enemy_item_classes;
|
||||
// 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;
|
||||
fprintf(stream, "## $_LOW $_HIGH DAR ITEM\n");
|
||||
for (size_t z = 0; z < 0x64; z++) {
|
||||
const char* item_class_name = "__UNKNOWN__";
|
||||
switch (item_classes[z]) {
|
||||
case 0x00:
|
||||
item_class_name = "WEAPON";
|
||||
break;
|
||||
case 0x01:
|
||||
item_class_name = "ARMOR";
|
||||
break;
|
||||
case 0x02:
|
||||
item_class_name = "SHIELD";
|
||||
break;
|
||||
case 0x03:
|
||||
item_class_name = "UNIT";
|
||||
break;
|
||||
case 0x04:
|
||||
item_class_name = "TOOL";
|
||||
break;
|
||||
case 0x05:
|
||||
item_class_name = "MESETA";
|
||||
break;
|
||||
}
|
||||
this->parsed_tool_class_prob_table_populated = true;
|
||||
fprintf(stream, "%02zX %5hu %5hu %3hhu %02hX (%s)\n",
|
||||
z, meseta_ranges[z].min, meseta_ranges[z].max, drop_probs[z], item_classes[z], item_class_name);
|
||||
}
|
||||
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_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);
|
||||
}
|
||||
|
||||
uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) {
|
||||
@@ -205,7 +136,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
||||
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);
|
||||
auto table = make_shared<Table>(pt_afs_data, r, false, false);
|
||||
auto table = make_shared<Table>(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);
|
||||
@@ -217,48 +148,72 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
||||
AFSArchive ct_afs(ct_afs_data);
|
||||
for (size_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
auto r = ct_afs.get_reader(difficulty * 10);
|
||||
auto table = make_shared<Table>(ct_afs_data, r, false, false);
|
||||
auto table = make_shared<Table>(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) {
|
||||
GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(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};
|
||||
auto filename_for_table = +[](Episode episode, uint8_t difficulty, uint8_t section_id, bool is_challenge) -> string {
|
||||
const char* episode_token = "";
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
episode_token = "";
|
||||
break;
|
||||
case Episode::EP2:
|
||||
episode_token = "l";
|
||||
break;
|
||||
case Episode::EP4:
|
||||
episode_token = "s";
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid episode");
|
||||
}
|
||||
return string_printf(
|
||||
"ItemPT%s%s%c%1hhu.rel",
|
||||
is_challenge ? "c" : "",
|
||||
episode_token,
|
||||
tolower(abbreviation_for_difficulty(difficulty)),
|
||||
section_id);
|
||||
};
|
||||
|
||||
vector<Episode> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
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);
|
||||
auto table = make_shared<Table>(gsl_data, r, is_big_endian, true);
|
||||
StringReader r;
|
||||
try {
|
||||
r = gsl.get_reader(filename_for_table(episode, difficulty, section_id, false));
|
||||
} catch (const exception&) {
|
||||
// Fall back to Episode 1 if Episode 4 data is missing
|
||||
if (episode == Episode::EP4) {
|
||||
auto ep1_table = this->tables.at(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id));
|
||||
this->tables.emplace(this->key_for_table(episode, GameMode::NORMAL, difficulty, section_id), ep1_table);
|
||||
this->tables.emplace(this->key_for_table(episode, GameMode::BATTLE, difficulty, section_id), ep1_table);
|
||||
this->tables.emplace(this->key_for_table(episode, GameMode::SOLO, difficulty, section_id), ep1_table);
|
||||
continue;
|
||||
} else {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
auto table = make_shared<Table>(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);
|
||||
auto table = make_shared<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);
|
||||
|
||||
if (episode != Episode::EP4) {
|
||||
for (size_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
|
||||
auto table = make_shared<Table>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-41
@@ -13,7 +13,7 @@ public:
|
||||
class Table {
|
||||
public:
|
||||
Table() = delete;
|
||||
Table(std::shared_ptr<const std::string> owned_data, const StringReader& r, bool big_endian, bool is_v3);
|
||||
Table(const StringReader& r, bool big_endian, bool is_v3);
|
||||
|
||||
template <typename IntT>
|
||||
struct Range {
|
||||
@@ -21,30 +21,35 @@ public:
|
||||
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_max_stars_table() const;
|
||||
const parray<parray<uint8_t, 10>, 7>& box_item_class_prob_table() const;
|
||||
parray<uint8_t, 0x0C> base_weapon_type_prob_table;
|
||||
parray<int8_t, 0x0C> subtype_base_table;
|
||||
parray<uint8_t, 0x0C> subtype_area_length_table;
|
||||
parray<parray<uint8_t, 4>, 9> grind_prob_table;
|
||||
parray<uint8_t, 0x05> armor_shield_type_index_prob_table;
|
||||
parray<uint8_t, 0x05> armor_slot_count_prob_table;
|
||||
parray<Range<uint16_t>, 0x64> enemy_meseta_ranges;
|
||||
parray<uint8_t, 0x64> enemy_type_drop_probs;
|
||||
parray<uint8_t, 0x64> enemy_item_classes;
|
||||
parray<Range<uint16_t>, 0x0A> box_meseta_ranges;
|
||||
bool has_rare_bonus_value_prob_table;
|
||||
parray<parray<uint16_t, 6>, 0x17> bonus_value_prob_table;
|
||||
parray<parray<uint8_t, 10>, 3> nonrare_bonus_prob_spec;
|
||||
parray<parray<uint8_t, 10>, 6> bonus_type_prob_table;
|
||||
parray<uint8_t, 0x0A> special_mult;
|
||||
parray<uint8_t, 0x0A> special_percent;
|
||||
parray<parray<uint16_t, 0x0A>, 0x1C> tool_class_prob_table;
|
||||
parray<parray<uint8_t, 0x0A>, 0x13> technique_index_prob_table;
|
||||
parray<parray<Range<uint8_t>, 0x0A>, 0x13> technique_level_ranges;
|
||||
uint8_t armor_or_shield_type_bias;
|
||||
parray<uint8_t, 0x0A> unit_max_stars_table;
|
||||
parray<parray<uint8_t, 10>, 7> box_item_class_prob_table;
|
||||
|
||||
void print_enemy_table(FILE* stream) const;
|
||||
|
||||
private:
|
||||
template <bool IsBigEndian>
|
||||
void parse_itempt_t(const StringReader& r, bool is_v3);
|
||||
|
||||
template <bool IsBigEndian>
|
||||
struct Offsets {
|
||||
using U16T = typename std::conditional<IsBigEndian, be_uint16_t, le_uint16_t>::type;
|
||||
@@ -248,22 +253,6 @@ public:
|
||||
|
||||
// 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;
|
||||
@@ -281,9 +270,9 @@ public:
|
||||
AFSV2CommonItemSet(std::shared_ptr<const std::string> pt_afs_data, std::shared_ptr<const std::string> ct_afs_data);
|
||||
};
|
||||
|
||||
class GSLV3CommonItemSet : public CommonItemSet {
|
||||
class GSLV3V4CommonItemSet : public CommonItemSet {
|
||||
public:
|
||||
GSLV3CommonItemSet(std::shared_ptr<const std::string> gsl_data, bool is_big_endian);
|
||||
GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gsl_data, bool is_big_endian);
|
||||
};
|
||||
|
||||
// Note: There are clearly better ways of doing this, but this implementation
|
||||
|
||||
+27
-21
@@ -852,9 +852,9 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::AL_RAPPY:
|
||||
return 0x06;
|
||||
case EnemyType::ASTARK:
|
||||
return 0x01;
|
||||
return 0x41;
|
||||
case EnemyType::BA_BOOTA:
|
||||
return 0x0B;
|
||||
return 0x4F;
|
||||
case EnemyType::BARBA_RAY:
|
||||
return 0x49;
|
||||
case EnemyType::BARBAROUS_WOLF:
|
||||
@@ -862,7 +862,7 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::BOOMA:
|
||||
return 0x09;
|
||||
case EnemyType::BOOTA:
|
||||
return 0x09;
|
||||
return 0x4D;
|
||||
case EnemyType::BULCLAW:
|
||||
return 0x28;
|
||||
case EnemyType::CANADINE:
|
||||
@@ -889,8 +889,9 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::DEL_LILY:
|
||||
return 0x53;
|
||||
case EnemyType::DEL_RAPPY:
|
||||
return 0x57;
|
||||
case EnemyType::DEL_RAPPY_ALT:
|
||||
return 0x12;
|
||||
return 0x58;
|
||||
case EnemyType::DELBITER:
|
||||
return 0x48;
|
||||
case EnemyType::DELDEPTH:
|
||||
@@ -904,9 +905,9 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::DOLMOLM:
|
||||
return 0x40;
|
||||
case EnemyType::DORPHON:
|
||||
return 0x0C;
|
||||
return 0x50;
|
||||
case EnemyType::DORPHON_ECLAIR:
|
||||
return 0x0D;
|
||||
return 0x51;
|
||||
case EnemyType::DRAGON:
|
||||
return 0x2C;
|
||||
case EnemyType::DUBCHIC:
|
||||
@@ -932,15 +933,15 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::GILLCHIC:
|
||||
return 0x32;
|
||||
case EnemyType::GIRTABLULU:
|
||||
return 0x06;
|
||||
return 0x48;
|
||||
case EnemyType::GOBOOMA:
|
||||
return 0x0A;
|
||||
case EnemyType::GOL_DRAGON:
|
||||
return 0x4C;
|
||||
case EnemyType::GORAN:
|
||||
return 0x0E;
|
||||
return 0x52;
|
||||
case EnemyType::GORAN_DETONATOR:
|
||||
return 0x0F;
|
||||
return 0x53;
|
||||
case EnemyType::GRASS_ASSASSIN:
|
||||
return 0x0C;
|
||||
case EnemyType::GUIL_SHARK:
|
||||
@@ -956,7 +957,7 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::ILL_GILL:
|
||||
return 0x52;
|
||||
case EnemyType::KONDRIEU:
|
||||
return 0x15;
|
||||
return 0x5B;
|
||||
case EnemyType::LA_DIMENIAN:
|
||||
return 0x2A;
|
||||
case EnemyType::LOVE_RAPPY:
|
||||
@@ -972,9 +973,9 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::MERILTAS:
|
||||
return 0x35;
|
||||
case EnemyType::MERISSA_A:
|
||||
return 0x04;
|
||||
return 0x46;
|
||||
case EnemyType::MERISSA_AA:
|
||||
return 0x05;
|
||||
return 0x47;
|
||||
case EnemyType::MIGIUM:
|
||||
return 0x16;
|
||||
case EnemyType::MONEST:
|
||||
@@ -994,8 +995,9 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::PAN_ARMS:
|
||||
return 0x15;
|
||||
case EnemyType::PAZUZU:
|
||||
return 0x4B;
|
||||
case EnemyType::PAZUZU_ALT:
|
||||
return 0x08;
|
||||
return 0x4C;
|
||||
case EnemyType::POFUILLY_SLIME:
|
||||
return 0x13;
|
||||
case EnemyType::POUILLY_SLIME:
|
||||
@@ -1003,7 +1005,7 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::POISON_LILY:
|
||||
return 0x0D;
|
||||
case EnemyType::PYRO_GORAN:
|
||||
return 0x10;
|
||||
return 0x54;
|
||||
case EnemyType::RAG_RAPPY:
|
||||
return 0x05;
|
||||
case EnemyType::RECOBOX:
|
||||
@@ -1013,17 +1015,19 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::SAINT_RAPPY:
|
||||
return 0x4F;
|
||||
case EnemyType::SAINT_MILLION:
|
||||
return 0x13;
|
||||
return 0x59;
|
||||
case EnemyType::SAND_RAPPY:
|
||||
return 0x55;
|
||||
case EnemyType::SAND_RAPPY_ALT:
|
||||
return 0x11;
|
||||
return 0x56;
|
||||
case EnemyType::SATELLITE_LIZARD:
|
||||
return 0x44;
|
||||
case EnemyType::SATELLITE_LIZARD_ALT:
|
||||
return 0x03;
|
||||
return 0x45;
|
||||
case EnemyType::SAVAGE_WOLF:
|
||||
return 0x07;
|
||||
case EnemyType::SHAMBERTIN:
|
||||
return 0x14;
|
||||
return 0x5A;
|
||||
case EnemyType::SINOW_BEAT:
|
||||
return 0x1A;
|
||||
case EnemyType::SINOW_BERILL:
|
||||
@@ -1043,15 +1047,17 @@ uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type) {
|
||||
case EnemyType::VOL_OPT_2:
|
||||
return 0x2E;
|
||||
case EnemyType::YOWIE:
|
||||
return 0x42;
|
||||
case EnemyType::YOWIE_ALT:
|
||||
return 0x02;
|
||||
return 0x43;
|
||||
case EnemyType::ZE_BOOTA:
|
||||
return 0x0A;
|
||||
return 0x4E;
|
||||
case EnemyType::ZOL_GIBBON:
|
||||
return 0x3C;
|
||||
case EnemyType::ZU:
|
||||
return 0x49;
|
||||
case EnemyType::ZU_ALT:
|
||||
return 0x07;
|
||||
return 0x4A;
|
||||
default:
|
||||
throw runtime_error(string_printf("%s does not have a rare table entry", name_for_enum(enemy_type)));
|
||||
}
|
||||
|
||||
+46
-30
@@ -39,6 +39,16 @@ ItemCreator::ItemCreator(
|
||||
restrictions(restrictions),
|
||||
random_crypt(random_seed) {
|
||||
this->generate_unit_stars_tables();
|
||||
this->pt->print_enemy_table(stderr);
|
||||
}
|
||||
|
||||
void ItemCreator::set_random_state(uint32_t seed, uint32_t absolute_offset) {
|
||||
if ((this->random_crypt.seed() != seed) || (this->random_crypt.absolute_offset() > absolute_offset)) {
|
||||
this->random_crypt = PSOV2Encryption(seed);
|
||||
}
|
||||
while (this->random_crypt.absolute_offset() < absolute_offset) {
|
||||
this->random_crypt.next();
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCreator::clear_destroyed_entities() {
|
||||
@@ -132,7 +142,7 @@ ItemData ItemCreator::on_box_item_drop_with_area_norm(uint8_t area_norm) {
|
||||
area_norm, this->random_crypt.seed(), this->random_crypt.absolute_offset());
|
||||
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_table(), area_norm);
|
||||
uint8_t item_class = this->get_rand_from_weighted_tables_2d_vertical(this->pt->box_item_class_prob_table, area_norm);
|
||||
this->log.info("Item class is %02hhX", item_class);
|
||||
switch (item_class) {
|
||||
case 0: // Weapon
|
||||
@@ -175,11 +185,13 @@ ItemData ItemCreator::on_monster_item_drop_with_area_norm(uint32_t enemy_type, u
|
||||
}
|
||||
this->log.info("Enemy type: %" PRIX32 "; random state: %08" PRIX32 " %08" PRIX32, enemy_type, this->random_crypt.seed(), this->random_crypt.absolute_offset());
|
||||
|
||||
uint8_t type_drop_prob = this->pt->enemy_type_drop_probs().at(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 >= %hhu)", drop_sample, type_drop_prob);
|
||||
return ItemData();
|
||||
} else {
|
||||
this->log.info("Drop chosen (%hhu < %hhu)", drop_sample, type_drop_prob);
|
||||
}
|
||||
|
||||
ItemData item = this->check_rare_spec_and_create_rare_enemy_item(enemy_type, area_norm);
|
||||
@@ -198,7 +210,7 @@ ItemData ItemCreator::on_monster_item_drop_with_area_norm(uint32_t enemy_type, u
|
||||
item_class = 4;
|
||||
break;
|
||||
case 2:
|
||||
item_class = this->pt->enemy_item_classes().at(enemy_type);
|
||||
item_class = this->pt->enemy_item_classes.at(enemy_type);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid item class determinant");
|
||||
@@ -224,7 +236,7 @@ ItemData ItemCreator::on_monster_item_drop_with_area_norm(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;
|
||||
@@ -277,12 +289,16 @@ uint32_t ItemCreator::choose_meseta_amount(
|
||||
|
||||
// Note: The original code seems like it has a bug here: it compares to 0xFF
|
||||
// instead of 0xFFFF (and returns 0xFF if either limit matches 0xFF).
|
||||
uint32_t ret = 0;
|
||||
if (((min == 0xFFFF) || (max == 0xFFFF)) || (max < min)) {
|
||||
return 0xFFFF;
|
||||
ret = 0xFFFF;
|
||||
} else if (min != max) {
|
||||
return this->rand_int((max - min) + 1) + min;
|
||||
ret = this->rand_int((max - min) + 1) + min;
|
||||
} else {
|
||||
ret = min;
|
||||
}
|
||||
return min;
|
||||
this->log.info("Chose %" PRIu32 " Meseta from range [%hu, %hu]", ret, min, max);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ItemCreator::should_allow_meseta_drops() const {
|
||||
@@ -329,7 +345,7 @@ ItemData ItemCreator::check_rate_and_create_rare_item(const RareItemSet::Expande
|
||||
item.data1[2] = drop.item_code[2];
|
||||
switch (item.data1[0]) {
|
||||
case 0:
|
||||
if (this->pt->has_rare_bonus_value_prob_table()) {
|
||||
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);
|
||||
@@ -362,13 +378,13 @@ void ItemCreator::generate_rare_weapon_bonuses(ItemData& item, uint32_t random_s
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->pt->has_rare_bonus_value_prob_table()) {
|
||||
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_table(), random_sample);
|
||||
int16_t bonus_value = this->get_rand_from_weighted_tables_2d_vertical(this->pt->bonus_value_prob_table(), 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
|
||||
@@ -386,12 +402,12 @@ void ItemCreator::generate_common_weapon_bonuses(ItemData& item, uint8_t area_no
|
||||
}
|
||||
|
||||
for (size_t row = 0; row < 3; row++) {
|
||||
uint8_t spec = this->pt->nonrare_bonus_prob_spec().at(row).at(area_norm);
|
||||
uint8_t spec = this->pt->nonrare_bonus_prob_spec.at(row).at(area_norm);
|
||||
if (spec == 0xFF) {
|
||||
this->log.info("Bonus %zu is forbidden", row);
|
||||
} else {
|
||||
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) + 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;
|
||||
this->log.info("Bonus %zu generated as %02hhX %02hhX from area_norm %02hhX and spec %02hhX", row, item.data1[(row * 2) + 6], item.data1[(row * 2) + 7], area_norm, spec);
|
||||
}
|
||||
@@ -560,7 +576,7 @@ 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_max_stars_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 stars = static_cast<uint32_t>(f1 * f2) & 0xFF;
|
||||
this->log.info("Unit stars: %g * %g = %" PRIu32, f1, f2, stars);
|
||||
@@ -580,7 +596,7 @@ void ItemCreator::generate_common_item_variances(uint32_t area_norm, ItemData& i
|
||||
this->generate_common_tool_variances(area_norm, item);
|
||||
break;
|
||||
case 4:
|
||||
item.data2d = this->choose_meseta_amount(this->pt->box_meseta_ranges(), area_norm) & 0xFFFF;
|
||||
item.data2d = this->choose_meseta_amount(this->pt->box_meseta_ranges, area_norm) & 0xFFFF;
|
||||
break;
|
||||
default:
|
||||
// Note: The original code does the following here:
|
||||
@@ -596,15 +612,15 @@ void ItemCreator::generate_common_item_variances(uint32_t area_norm, ItemData& i
|
||||
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 {
|
||||
item.data1[2] -= 3;
|
||||
}
|
||||
this->log.info("Armor/shield type: max(%02hhX + %02hhX + %02hhX - 3, 0) = %02hhX",
|
||||
area_norm, type, this->pt->armor_or_shield_type_bias(), item.data1[2]);
|
||||
area_norm, type, this->pt->armor_or_shield_type_bias, item.data1[2]);
|
||||
}
|
||||
|
||||
void ItemCreator::generate_common_armor_slots_and_bonuses(ItemData& item) {
|
||||
@@ -622,13 +638,13 @@ 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 (this->is_v3() && (tool_class == 0x1A)) {
|
||||
tool_class = 0x73;
|
||||
}
|
||||
@@ -653,7 +669,7 @@ void ItemCreator::generate_common_tool_variances(uint32_t area_norm, ItemData& i
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -661,7 +677,7 @@ 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) {
|
||||
const auto& range = this->pt->technique_level_ranges().at(tech_num).at(area_norm);
|
||||
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 (range.min != range.max) {
|
||||
@@ -685,12 +701,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().at(z - 1)) & 0x80) {
|
||||
if ((area_norm + this->pt->subtype_base_table.at(z - 1)) & 0x80) {
|
||||
weapon_type_prob_table[z] = 0;
|
||||
}
|
||||
}
|
||||
@@ -706,8 +722,8 @@ void ItemCreator::generate_common_weapon_variances(uint8_t area_norm, ItemData&
|
||||
this->log.info("00 chosen from subtype table; skipping item");
|
||||
item.clear();
|
||||
} else {
|
||||
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);
|
||||
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);
|
||||
this->log.info("Subtype table yielded %02hhX; subtype base is %hhd with area length %hhu", item.data1[1], subtype_base, area_length);
|
||||
if (subtype_base < 0) {
|
||||
item.data1[2] = (area_norm + subtype_base) / area_length;
|
||||
@@ -727,7 +743,7 @@ void ItemCreator::generate_common_weapon_variances(uint8_t area_norm, ItemData&
|
||||
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_table(), offset);
|
||||
item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical(this->pt->grind_prob_table, offset);
|
||||
this->log.info("Generated grind %02hhX from offset within subtype range %02hhX", item.data1[3], offset_within_subtype_range);
|
||||
}
|
||||
}
|
||||
@@ -740,13 +756,13 @@ void ItemCreator::generate_common_weapon_special(ItemData& item, uint8_t area_no
|
||||
this->log.info("Item is rare; skipping special generation");
|
||||
return;
|
||||
}
|
||||
uint8_t special_mult = this->pt->special_mult().at(area_norm);
|
||||
uint8_t special_mult = this->pt->special_mult.at(area_norm);
|
||||
if (special_mult == 0) {
|
||||
this->log.info("Special multiplier is zero for area_norm %02hhX; skipping special generation", area_norm);
|
||||
return;
|
||||
}
|
||||
uint8_t det = this->rand_int(100);
|
||||
uint8_t prob = this->pt->special_percent().at(area_norm);
|
||||
uint8_t prob = this->pt->special_percent.at(area_norm);
|
||||
if (det >= prob) {
|
||||
this->log.info("Special not chosen (%02hhX > %02hhX)", det, prob);
|
||||
return;
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
std::shared_ptr<const BattleRules> restrictions = nullptr);
|
||||
~ItemCreator() = default;
|
||||
|
||||
void set_random_state(uint32_t seed, uint32_t absolute_offset);
|
||||
void clear_destroyed_entities();
|
||||
|
||||
ItemData on_monster_item_drop(uint16_t entity_id, uint32_t enemy_type, uint8_t area);
|
||||
|
||||
+2
-2
@@ -77,11 +77,11 @@ void Lobby::create_item_creator() {
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
common_item_set = s->common_item_set_v3;
|
||||
common_item_set = s->common_item_set_v3_v4;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v3");
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
common_item_set = s->common_item_set_v3;
|
||||
common_item_set = s->common_item_set_v3_v4;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v4");
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1682,12 +1682,13 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
|
||||
string floor_warning_token = "";
|
||||
if (l->map) {
|
||||
const auto& enemy = l->map->enemies.at(cmd.entity_id);
|
||||
uint32_t expected_rt_index = rare_table_index_for_enemy_type(enemy.type);
|
||||
if (cmd.rt_index != expected_rt_index) {
|
||||
effective_rt_index = rare_table_index_for_enemy_type(enemy.type);
|
||||
// rt_indexes in Episode 4 don't match those sent in the command; we just
|
||||
// ignore what the client sends.
|
||||
if ((l->episode != Episode::EP4) && (cmd.rt_index != effective_rt_index)) {
|
||||
l->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, expected_rt_index);
|
||||
rt_index_warning_token = string_printf("$C6!RT:%02hhX/%02" PRIX32 "$C5 ", cmd.rt_index, expected_rt_index);
|
||||
effective_rt_index = expected_rt_index;
|
||||
cmd.rt_index, effective_rt_index);
|
||||
rt_index_warning_token = string_printf("$C6!RT:%02hhX/%02" PRIX32 "$C5 ", cmd.rt_index, effective_rt_index);
|
||||
}
|
||||
if (cmd.floor != enemy.floor) {
|
||||
l->log.warning("Floor %02hhX from command does not match entity\'s expected floor %02hhX",
|
||||
|
||||
+3
-6
@@ -1087,12 +1087,9 @@ void ServerState::load_item_tables() {
|
||||
auto ct_data_v2 = make_shared<string>(load_file("system/item-tables/ItemCT-v2.afs"));
|
||||
auto pt_data_v2 = make_shared<string>(load_file("system/item-tables/ItemPT-v2.afs"));
|
||||
this->common_item_set_v2 = make_shared<AFSV2CommonItemSet>(pt_data_v2, ct_data_v2);
|
||||
config_log.info("Loading v3 common item table");
|
||||
auto pt_data_v3 = make_shared<string>(load_file("system/item-tables/ItemPT-gc.gsl"));
|
||||
this->common_item_set_v3 = make_shared<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 v3+v4 common item table");
|
||||
auto pt_data_v3_v4 = make_shared<string>(load_file("system/item-tables/ItemPT-gc-v4.gsl"));
|
||||
this->common_item_set_v3_v4 = make_shared<GSLV3V4CommonItemSet>(pt_data_v3_v4, true);
|
||||
|
||||
config_log.info("Loading armor table");
|
||||
auto armor_data = make_shared<string>(load_file("system/item-tables/ArmorRandom-gc.rel"));
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
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_v2;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v3;
|
||||
std::shared_ptr<const CommonItemSet> common_item_set_v3_v4;
|
||||
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.
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user