implement JSON rare tables
This commit is contained in:
+32
-14
@@ -31,8 +31,6 @@ ItemCreator::ItemCreator(
|
||||
item_parameter_table(item_parameter_table),
|
||||
pt(&this->common_item_set->get_table(
|
||||
this->episode, this->mode, this->difficulty, this->section_id)),
|
||||
rt(&this->rare_item_set->get_table(
|
||||
this->episode, this->mode, this->difficulty, this->section_id)),
|
||||
restrictions(restrictions),
|
||||
random_crypt(random_seed) {
|
||||
print_data(stderr, this->pt, sizeof(*this->pt));
|
||||
@@ -232,13 +230,17 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(
|
||||
return item;
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < this->rt->box_count; z++) {
|
||||
if (area_norm + 1 == this->rt->box_areas[z]) {
|
||||
item = this->check_rate_and_create_rare_item(this->rt->box_rares[z]);
|
||||
if (!item.empty()) {
|
||||
break;
|
||||
}
|
||||
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);
|
||||
if (!item.empty()) {
|
||||
this->log.info("Box spec %08" PRIX32 " => %02hhX%02hhX%02hhX produced an item",
|
||||
spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]);
|
||||
break;
|
||||
}
|
||||
this->log.info("Box spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce",
|
||||
spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@@ -275,24 +277,38 @@ bool ItemCreator::should_allow_meseta_drops() const {
|
||||
|
||||
ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(
|
||||
uint32_t enemy_type) {
|
||||
ItemData item;
|
||||
if (this->are_rare_drops_allowed() &&
|
||||
(enemy_type > 0) && (enemy_type < 0x58)) {
|
||||
return this->check_rate_and_create_rare_item(
|
||||
this->rt->monster_rares[enemy_type]);
|
||||
} else {
|
||||
return ItemData();
|
||||
// Note: In the original implementation, enemies can only have one possible
|
||||
// rare drop. In our implementation, they can have multiple rare drops if
|
||||
// JSONRareItemSet is used (the other RareItemSet implementations never
|
||||
// return multiple drops for an enemy type).
|
||||
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);
|
||||
if (!item.empty()) {
|
||||
this->log.info("Enemy spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce",
|
||||
spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]);
|
||||
break;
|
||||
}
|
||||
this->log.info("Enemy spec %08" PRIX32 " => %02hhX%02hhX%02hhX did not produce",
|
||||
spec.probability, spec.item_code[0], spec.item_code[1], spec.item_code[2]);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
ItemData ItemCreator::check_rate_and_create_rare_item(
|
||||
const RareItemSet::Table::Drop& drop) {
|
||||
const RareItemSet::ExpandedDrop& drop) {
|
||||
if (drop.probability == 0) {
|
||||
return ItemData();
|
||||
}
|
||||
|
||||
// Note: The original code uses 0xFFFFFFFF as the maximum here. We use
|
||||
// 0x100000000 instead, which makes all rare items SLIGHTLY more rare.
|
||||
if (this->rand_int(0x100000000) >= this->rare_item_set->expand_rate(drop.probability)) {
|
||||
if (this->rand_int(0x100000000) >= drop.probability) {
|
||||
return ItemData();
|
||||
}
|
||||
|
||||
@@ -1689,4 +1705,6 @@ ItemData ItemCreator::on_specialized_box_item_drop(uint32_t def0, uint32_t def1,
|
||||
default:
|
||||
throw runtime_error("invalid item class");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
+1
-3
@@ -86,7 +86,6 @@ private:
|
||||
std::shared_ptr<const WeaponRandomSet> weapon_random_set;
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table;
|
||||
const CommonItemSet::Table<true>* pt;
|
||||
const RareItemSet::Table* rt;
|
||||
std::shared_ptr<const Restrictions> restrictions;
|
||||
|
||||
std::shared_ptr<ItemDropSub> item_drop_sub;
|
||||
@@ -116,8 +115,7 @@ private:
|
||||
|
||||
ItemData check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type);
|
||||
ItemData check_rare_specs_and_create_rare_box_item(uint8_t area_norm);
|
||||
ItemData check_rate_and_create_rare_item(
|
||||
const RareItemSet::Table::Drop& drop);
|
||||
ItemData check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop);
|
||||
|
||||
void generate_rare_weapon_bonuses(ItemData& item, uint32_t random_sample);
|
||||
void deduplicate_weapon_bonuses(ItemData& item) const;
|
||||
|
||||
+11
-65
@@ -221,7 +221,6 @@ enum class Behavior {
|
||||
DECODE_SJIS,
|
||||
EXTRACT_GSL,
|
||||
EXTRACT_BML,
|
||||
FORMAT_ITEMRT_ENTRY,
|
||||
FORMAT_ITEMRT_REL,
|
||||
SHOW_EP3_DATA,
|
||||
DESCRIBE_ITEM,
|
||||
@@ -251,7 +250,6 @@ static bool behavior_takes_input_filename(Behavior b) {
|
||||
(b == Behavior::ENCRYPT_GCI_SAVE) ||
|
||||
(b == Behavior::DECODE_QUEST_FILE) ||
|
||||
(b == Behavior::DECODE_SJIS) ||
|
||||
(b == Behavior::FORMAT_ITEMRT_ENTRY) ||
|
||||
(b == Behavior::FORMAT_ITEMRT_REL) ||
|
||||
(b == Behavior::EXTRACT_GSL) ||
|
||||
(b == Behavior::EXTRACT_BML) ||
|
||||
@@ -429,8 +427,6 @@ int main(int argc, char** argv) {
|
||||
quest_file_type = QuestFileFormat::QST;
|
||||
} else if (!strcmp(argv[x], "cat-client")) {
|
||||
behavior = Behavior::CAT_CLIENT;
|
||||
} else if (!strcmp(argv[x], "format-itemrt-entry")) {
|
||||
behavior = Behavior::FORMAT_ITEMRT_ENTRY;
|
||||
} else if (!strcmp(argv[x], "format-itemrt-rel")) {
|
||||
behavior = Behavior::FORMAT_ITEMRT_REL;
|
||||
} else if (!strcmp(argv[x], "show-ep3-data")) {
|
||||
@@ -1009,70 +1005,24 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::FORMAT_ITEMRT_ENTRY: {
|
||||
string data = read_input_data();
|
||||
if (data.size() < sizeof(RareItemSet::Table)) {
|
||||
throw runtime_error("input data too small");
|
||||
}
|
||||
const auto& table = *reinterpret_cast<const RareItemSet::Table*>(data.data());
|
||||
|
||||
auto format_drop = +[](const RareItemSet::Table::Drop& r) -> string {
|
||||
ItemData item;
|
||||
item.data1[0] = r.item_code[0];
|
||||
item.data1[1] = r.item_code[1];
|
||||
item.data1[2] = r.item_code[2];
|
||||
string name = item.name(false);
|
||||
|
||||
uint32_t expanded_probability = RareItemSet::expand_rate(r.probability);
|
||||
auto frac = reduce_fraction<uint64_t>(expanded_probability, 0x100000000);
|
||||
return string_printf(
|
||||
"(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)",
|
||||
r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str());
|
||||
};
|
||||
|
||||
fprintf(stdout, "Monster rares:\n");
|
||||
for (size_t z = 0; z < 0x65; z++) {
|
||||
const auto& r = table.monster_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: %s\n", z, s.c_str());
|
||||
}
|
||||
|
||||
fprintf(stdout, "Box rares:\n");
|
||||
for (size_t z = 0; z < 0x1E; z++) {
|
||||
const auto& r = table.box_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Behavior::FORMAT_ITEMRT_REL: {
|
||||
shared_ptr<string> data(new string(read_input_data()));
|
||||
RELRareItemSet rs(data);
|
||||
|
||||
auto format_drop = +[](const RareItemSet::Table::Drop& r) -> string {
|
||||
auto format_drop = +[](const RareItemSet::ExpandedDrop& r) -> string {
|
||||
ItemData item;
|
||||
item.data1[0] = r.item_code[0];
|
||||
item.data1[1] = r.item_code[1];
|
||||
item.data1[2] = r.item_code[2];
|
||||
string name = item.name(false);
|
||||
|
||||
uint32_t expanded_probability = RareItemSet::expand_rate(r.probability);
|
||||
auto frac = reduce_fraction<uint64_t>(expanded_probability, 0x100000000);
|
||||
auto frac = reduce_fraction<uint64_t>(r.probability, 0x100000000);
|
||||
return string_printf(
|
||||
"(%02hhX => %08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)",
|
||||
r.probability, expanded_probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str());
|
||||
"(%08" PRIX32 " => %" PRIu64 "/%" PRIu64 ") %02hhX%02hhX%02hhX (%s)",
|
||||
r.probability, frac.first, frac.second, r.item_code[0], r.item_code[1], r.item_code[2], name.c_str());
|
||||
};
|
||||
|
||||
auto print_collection = [&](GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id) -> void {
|
||||
const auto& table = rs.get_table(episode, mode, difficulty, section_id);
|
||||
|
||||
string secid_name = name_for_section_id(section_id);
|
||||
fprintf(stdout, "%s %s %s %s\n",
|
||||
name_for_mode(mode),
|
||||
@@ -1082,22 +1032,18 @@ int main(int argc, char** argv) {
|
||||
|
||||
fprintf(stdout, " Monster rares:\n");
|
||||
for (size_t z = 0; z < 0x65; z++) {
|
||||
const auto& r = table.monster_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
for (const auto& spec : rs.get_enemy_specs(mode, episode, difficulty, section_id, z)) {
|
||||
string s = format_drop(spec);
|
||||
fprintf(stdout, " %02zX: %s\n", z, s.c_str());
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: %s\n", z, s.c_str());
|
||||
}
|
||||
|
||||
fprintf(stdout, " Box rares:\n");
|
||||
for (size_t z = 0; z < 0x1E; z++) {
|
||||
const auto& r = table.box_rares[z];
|
||||
if (r.item_code[0] == 0 && r.item_code[1] == 0 && r.item_code[2] == 0) {
|
||||
continue;
|
||||
for (size_t area = 0; area < 0x12; area++) {
|
||||
for (const auto& spec : rs.get_box_specs(mode, episode, difficulty, section_id, area)) {
|
||||
string s = format_drop(spec);
|
||||
fprintf(stdout, " (area %02zX) %s\n", area, s.c_str());
|
||||
}
|
||||
string s = format_drop(r);
|
||||
fprintf(stdout, " %02zX: area %02hhX %s\n", z, table.box_areas[z], s.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+178
-19
@@ -3,11 +3,12 @@
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
uint32_t RareItemSet::expand_rate(uint8_t pc) {
|
||||
uint32_t RareItemSet::PackedDrop::expand_rate(uint8_t pc) {
|
||||
int8_t shift = ((pc >> 3) & 0x1F) - 4;
|
||||
if (shift < 0) {
|
||||
shift = 0;
|
||||
@@ -15,15 +16,40 @@ uint32_t RareItemSet::expand_rate(uint8_t pc) {
|
||||
return ((2 << shift) * ((pc & 7) + 7));
|
||||
}
|
||||
|
||||
bool RareItemSet::sample(mt19937& random, uint8_t pc) {
|
||||
return (random() < RareItemSet::expand_rate(pc));
|
||||
RareItemSet::ExpandedDrop::ExpandedDrop() : probability(0) {
|
||||
this->item_code[0] = 0;
|
||||
this->item_code[1] = 0;
|
||||
this->item_code[2] = 0;
|
||||
}
|
||||
|
||||
GSLRareItemSet::GSLRareItemSet(shared_ptr<const string> data, bool is_big_endian)
|
||||
: gsl(data, is_big_endian) {}
|
||||
RareItemSet::ExpandedDrop::ExpandedDrop(const PackedDrop& d)
|
||||
: probability(PackedDrop::expand_rate(d.probability)) {
|
||||
this->item_code[0] = d.item_code[0];
|
||||
this->item_code[1] = d.item_code[1];
|
||||
this->item_code[2] = d.item_code[2];
|
||||
}
|
||||
|
||||
const GSLRareItemSet::Table& GSLRareItemSet::get_table(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const {
|
||||
std::vector<RareItemSet::ExpandedDrop> GSLRareItemSet::Table::get_enemy_specs(uint8_t enemy_type) const {
|
||||
vector<ExpandedDrop> ret;
|
||||
if (this->monster_rares[enemy_type].item_code[0] != 0 ||
|
||||
this->monster_rares[enemy_type].item_code[1] != 0 ||
|
||||
this->monster_rares[enemy_type].item_code[2] != 0) {
|
||||
ret.emplace_back(this->monster_rares[enemy_type]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> GSLRareItemSet::Table::get_box_specs(uint8_t area) const {
|
||||
vector<ExpandedDrop> ret;
|
||||
for (size_t z = 0; z < 0x1E; z++) {
|
||||
if (this->box_areas[z] == area) {
|
||||
ret.emplace_back(this->box_rares[z]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) {
|
||||
if (difficulty > 3) {
|
||||
throw logic_error("incorrect difficulty");
|
||||
}
|
||||
@@ -31,20 +57,71 @@ const GSLRareItemSet::Table& GSLRareItemSet::get_table(
|
||||
throw logic_error("incorrect section id");
|
||||
}
|
||||
|
||||
if ((episode != Episode::EP1) && (episode != Episode::EP2)) {
|
||||
throw runtime_error("invalid episode");
|
||||
uint16_t key = ((difficulty & 3) << 4) | (secid & 0x0F);
|
||||
switch (mode) {
|
||||
case GameMode::NORMAL:
|
||||
break;
|
||||
case GameMode::BATTLE:
|
||||
key |= 0x0040;
|
||||
break;
|
||||
case GameMode::CHALLENGE:
|
||||
key |= 0x0080;
|
||||
break;
|
||||
case GameMode::SOLO:
|
||||
key |= 0x00C0;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid episode in RareItemSet");
|
||||
}
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
break;
|
||||
case Episode::EP2:
|
||||
key |= 0x0100;
|
||||
break;
|
||||
case Episode::EP4:
|
||||
key |= 0x0200;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid episode in RareItemSet");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
string filename = string_printf("ItemRT%s%s%c%1d.rel",
|
||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||
((episode == Episode::EP2) ? "l" : ""),
|
||||
tolower(abbreviation_for_difficulty(difficulty)), // One of "nhvu"
|
||||
secid);
|
||||
auto entry = this->gsl.get(filename);
|
||||
if (entry.second < sizeof(Table)) {
|
||||
throw runtime_error(string_printf("table %s is too small", filename.c_str()));
|
||||
GSLRareItemSet::GSLRareItemSet(shared_ptr<const string> data, bool is_big_endian)
|
||||
: gsl(data, is_big_endian) {
|
||||
const array<Episode, 2> episodes = {Episode::EP1, Episode::EP2};
|
||||
const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (GameMode mode : modes) {
|
||||
for (Episode episode : episodes) {
|
||||
for (size_t difficulty = 0; difficulty < 3; difficulty++) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
string filename = string_printf("ItemRT%s%s%c%1zu.rel",
|
||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||
((episode == Episode::EP2) ? "l" : ""),
|
||||
tolower(abbreviation_for_difficulty(difficulty)), // One of "nhvu"
|
||||
section_id);
|
||||
auto entry = this->gsl.get(filename);
|
||||
if (entry.second < sizeof(Table)) {
|
||||
throw runtime_error(string_printf("table %s is too small", filename.c_str()));
|
||||
}
|
||||
this->tables.emplace(
|
||||
this->key_for_params(mode, episode, difficulty, section_id),
|
||||
reinterpret_cast<const Table*>(entry.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return *reinterpret_cast<const Table*>(entry.first);
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> GSLRareItemSet::get_enemy_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const {
|
||||
return this->tables.at(this->key_for_params(mode, episode, difficulty, secid))->get_enemy_specs(enemy_type);
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> GSLRareItemSet::get_box_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const {
|
||||
return this->tables.at(this->key_for_params(mode, episode, difficulty, secid))->get_box_specs(area);
|
||||
}
|
||||
|
||||
RELRareItemSet::RELRareItemSet(shared_ptr<const string> data) : data(data) {
|
||||
@@ -53,8 +130,18 @@ RELRareItemSet::RELRareItemSet(shared_ptr<const string> data) : data(data) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> RELRareItemSet::get_enemy_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const {
|
||||
return this->get_table(mode, episode, difficulty, secid).get_enemy_specs(enemy_type);
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> RELRareItemSet::get_box_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const {
|
||||
return this->get_table(mode, episode, difficulty, secid).get_box_specs(area);
|
||||
}
|
||||
|
||||
const RELRareItemSet::Table& RELRareItemSet::get_table(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const {
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const {
|
||||
(void)mode; // TODO: Shouldn't we check for challenge mode somewhere?
|
||||
|
||||
if (difficulty > 3) {
|
||||
@@ -82,3 +169,75 @@ const RELRareItemSet::Table& RELRareItemSet::get_table(
|
||||
const auto* tables = reinterpret_cast<const Table*>(this->data->data());
|
||||
return tables[(ep_index * 10 * 4) + (difficulty * 10) + secid];
|
||||
}
|
||||
|
||||
JSONRareItemSet::JSONRareItemSet(std::shared_ptr<const JSONObject> json) {
|
||||
for (const auto& mode_it : json->as_dict()) {
|
||||
static const unordered_map<string, GameMode> mode_keys(
|
||||
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
|
||||
GameMode mode = mode_keys.at(mode_it.first);
|
||||
|
||||
for (const auto& episode_it : mode_it.second->as_dict()) {
|
||||
static const unordered_map<string, Episode> episode_keys(
|
||||
{{"Episode1", Episode::EP1}, {"Episode2", Episode::EP2}, {"Episode4", Episode::EP4}});
|
||||
Episode episode = episode_keys.at(episode_it.first);
|
||||
|
||||
for (const auto& difficulty_it : episode_it.second->as_dict()) {
|
||||
static const unordered_map<string, uint8_t> difficulty_keys(
|
||||
{{"Normal", 0}, {"Hard", 1}, {"VeryHard", 2}, {"Ultimate", 3}});
|
||||
uint8_t difficulty = difficulty_keys.at(difficulty_it.first);
|
||||
|
||||
for (const auto& section_id_it : difficulty_it.second->as_dict()) {
|
||||
uint8_t section_id = section_id_for_name(section_id_it.first);
|
||||
|
||||
auto& collection = this->collections[this->key_for_params(mode, episode, difficulty, section_id)];
|
||||
for (const auto& item_it : difficulty_it.second->as_dict()) {
|
||||
vector<ExpandedDrop>* target;
|
||||
if (starts_with(item_it.first, "Box-")) {
|
||||
uint8_t area = drop_area_for_name(item_it.first.substr(4));
|
||||
if (collection.box_area_to_specs.size() <= area) {
|
||||
collection.box_area_to_specs.resize(area + 1);
|
||||
}
|
||||
target = &collection.box_area_to_specs[area];
|
||||
} else {
|
||||
size_t index = static_cast<size_t>(enum_for_name<EnemyType>(item_it.first.c_str()));
|
||||
if (collection.enemy_type_to_specs.size() <= index) {
|
||||
collection.enemy_type_to_specs.resize(index + 1);
|
||||
}
|
||||
target = &collection.enemy_type_to_specs[index];
|
||||
}
|
||||
|
||||
for (const auto& spec_json : item_it.second->as_list()) {
|
||||
auto& spec_list = spec_json->as_list();
|
||||
auto& d = target->emplace_back();
|
||||
d.probability = spec_list.at(0)->as_int();
|
||||
uint32_t item_code = spec_list.at(1)->as_int();
|
||||
d.item_code[0] = (item_code >> 16) & 0xFF;
|
||||
d.item_code[1] = (item_code >> 8) & 0xFF;
|
||||
d.item_code[2] = item_code & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> JSONRareItemSet::get_enemy_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const {
|
||||
try {
|
||||
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid)).enemy_type_to_specs.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
static const std::vector<ExpandedDrop> empty_vector;
|
||||
return empty_vector;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> JSONRareItemSet::get_box_specs(
|
||||
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const {
|
||||
try {
|
||||
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid)).box_area_to_specs.at(area);
|
||||
} catch (const out_of_range&) {
|
||||
static const std::vector<ExpandedDrop> empty_vector;
|
||||
return empty_vector;
|
||||
}
|
||||
}
|
||||
|
||||
+49
-12
@@ -11,18 +11,29 @@
|
||||
|
||||
class RareItemSet {
|
||||
public:
|
||||
struct PackedDrop {
|
||||
uint8_t probability;
|
||||
uint8_t item_code[3];
|
||||
|
||||
static uint32_t expand_rate(uint8_t pc);
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ExpandedDrop {
|
||||
uint32_t probability;
|
||||
uint8_t item_code[3];
|
||||
|
||||
ExpandedDrop();
|
||||
explicit ExpandedDrop(const PackedDrop&);
|
||||
};
|
||||
|
||||
struct Table {
|
||||
// 0x280 in size; describes one difficulty, section ID, and episode
|
||||
// TODO: It looks like this structure can actually vary. In PSOGC, these all
|
||||
// appear to be the same size/format, but that's probably not strictly
|
||||
// required to be the case.
|
||||
struct Drop {
|
||||
uint8_t probability;
|
||||
uint8_t item_code[3];
|
||||
} __attribute__((packed));
|
||||
/* 0000 */ parray<Drop, 0x65> monster_rares;
|
||||
/* 0000 */ parray<PackedDrop, 0x65> monster_rares;
|
||||
/* 0194 */ parray<uint8_t, 0x1E> box_areas;
|
||||
/* 01B2 */ parray<Drop, 0x1E> box_rares;
|
||||
/* 01B2 */ parray<PackedDrop, 0x1E> box_rares;
|
||||
/* 022A */ parray<uint8_t, 2> unknown_a1;
|
||||
/* 022C */ be_uint32_t monster_rares_offset; // == 0x0000
|
||||
/* 0230 */ be_uint32_t box_count; // == 0x1E
|
||||
@@ -37,26 +48,32 @@ public:
|
||||
/* 0270 */ be_uint32_t offset_table_offset; // == 0x022C
|
||||
/* 0274 */ parray<be_uint32_t, 3> unknown_a5;
|
||||
/* 0280 */
|
||||
|
||||
std::vector<ExpandedDrop> get_enemy_specs(uint8_t enemy_type) const;
|
||||
std::vector<ExpandedDrop> get_box_specs(uint8_t area) const;
|
||||
} __attribute__((packed));
|
||||
|
||||
virtual ~RareItemSet() = default;
|
||||
|
||||
virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const = 0;
|
||||
|
||||
static bool sample(std::mt19937& rand, uint8_t probability);
|
||||
static uint32_t expand_rate(uint8_t pc);
|
||||
virtual std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const = 0;
|
||||
virtual std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const = 0;
|
||||
|
||||
protected:
|
||||
RareItemSet() = default;
|
||||
|
||||
static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid);
|
||||
};
|
||||
|
||||
class GSLRareItemSet : public RareItemSet {
|
||||
public:
|
||||
GSLRareItemSet(std::shared_ptr<const std::string> data, bool is_big_endian);
|
||||
virtual ~GSLRareItemSet() = default;
|
||||
virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
virtual std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const;
|
||||
virtual std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<uint16_t, const Table*> tables;
|
||||
|
||||
GSLArchive gsl;
|
||||
};
|
||||
|
||||
@@ -64,8 +81,28 @@ class RELRareItemSet : public RareItemSet {
|
||||
public:
|
||||
RELRareItemSet(std::shared_ptr<const std::string> data);
|
||||
virtual ~RELRareItemSet() = default;
|
||||
virtual const Table& get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const;
|
||||
virtual std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const;
|
||||
virtual std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<const std::string> data;
|
||||
|
||||
const Table& get_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const;
|
||||
};
|
||||
|
||||
class JSONRareItemSet : public RareItemSet {
|
||||
public:
|
||||
JSONRareItemSet(std::shared_ptr<const JSONObject> json);
|
||||
virtual ~JSONRareItemSet() = default;
|
||||
|
||||
virtual std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t enemy_type) const;
|
||||
virtual std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
|
||||
|
||||
private:
|
||||
struct SpecCollection {
|
||||
std::vector<std::vector<ExpandedDrop>> enemy_type_to_specs;
|
||||
std::vector<std::vector<ExpandedDrop>> box_area_to_specs;
|
||||
};
|
||||
|
||||
std::unordered_map<uint16_t, SpecCollection> collections;
|
||||
};
|
||||
|
||||
@@ -1030,9 +1030,13 @@ static void on_entity_drop_item_request(
|
||||
cmd.def[0], cmd.def[1], cmd.def[2]);
|
||||
}
|
||||
} else {
|
||||
uint32_t expected_rt_index = l->enemies.at(cmd.entity_id).rt_index;
|
||||
if (cmd.rt_index != l->enemies.at(cmd.entity_id).rt_index) {
|
||||
c->log.warning("rt_index %02hhX does not match entity\'s expected index %02" PRIX32,
|
||||
if (!l->map) {
|
||||
throw runtime_error("game does not have a map loaded");
|
||||
}
|
||||
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) {
|
||||
c->log.warning("rt_index %02hhX from command does not match entity\'s expected index %02" PRIX32,
|
||||
cmd.rt_index, expected_rt_index);
|
||||
}
|
||||
item.data = l->item_creator->on_monster_item_drop(expected_rt_index, cmd.area);
|
||||
|
||||
Reference in New Issue
Block a user