use EnemyType in ItemCreator; fix incorrect drop tables
This commit is contained in:
+150
-130
@@ -134,67 +134,57 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
|
||||
parse_field("UnitMaxStarsTable", this->unit_max_stars_table, prev_table ? &prev_table->unit_max_stars_table : nullptr);
|
||||
parse_field("BoxItemClassProbTable", this->box_item_class_prob_table, prev_table ? &prev_table->box_item_class_prob_table : nullptr);
|
||||
|
||||
const auto* enemy_meseta_ranges_json = json.count("EnemyMesetaRanges") ? &json.at("EnemyMesetaRanges").as_dict() : nullptr;
|
||||
const auto* enemy_type_drop_probs_json = json.count("EnemyTypeDropProbs") ? &json.at("EnemyTypeDropProbs").as_dict() : nullptr;
|
||||
const auto* enemy_item_classes_json = json.count("EnemyItemClasses") ? &json.at("EnemyItemClasses").as_dict() : nullptr;
|
||||
if (enemy_item_classes_json) {
|
||||
// Unspecified is 0xFF, not 0, unlike the other enemy-indexed arrays (except for [0], apparently... sigh)
|
||||
this->enemy_item_classes[0] = 0;
|
||||
this->enemy_item_classes.clear_after(1, 0xFF);
|
||||
if (json.count("EnemyMesetaRanges")) {
|
||||
const auto& dict = json.at("EnemyMesetaRanges").as_dict();
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
from_json_into(*dict.at(phosg::name_for_enum(enemy_type)), this->enemy_type_meseta_ranges[enemy_type]);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->enemy_type_meseta_ranges = prev_table->enemy_type_meseta_ranges;
|
||||
}
|
||||
for (size_t z = 0; z < NUM_RT_INDEXES_V4; z++) {
|
||||
auto types = enemy_types_for_rare_table_index(this->episode, z);
|
||||
vector<string> names;
|
||||
if (types.empty()) {
|
||||
names.emplace_back(std::format("!{:02X}", z));
|
||||
} else {
|
||||
for (auto type : types) {
|
||||
names.emplace_back(phosg::name_for_enum(type));
|
||||
|
||||
if (json.count("EnemyTypeDropProbs")) {
|
||||
const auto& dict = json.at("EnemyTypeDropProbs").as_dict();
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
this->enemy_type_drop_probs[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
for (const auto& name : names) {
|
||||
if (enemy_meseta_ranges_json) {
|
||||
try {
|
||||
from_json_into(*enemy_meseta_ranges_json->at(name), this->enemy_meseta_ranges[z]);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
} else if (prev_table) {
|
||||
this->enemy_meseta_ranges = prev_table->enemy_meseta_ranges;
|
||||
}
|
||||
if (enemy_type_drop_probs_json) {
|
||||
try {
|
||||
this->enemy_type_drop_probs[z] = enemy_type_drop_probs_json->at(name)->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
} else if (prev_table) {
|
||||
this->enemy_type_drop_probs = prev_table->enemy_type_drop_probs;
|
||||
}
|
||||
if (enemy_item_classes_json) {
|
||||
try {
|
||||
this->enemy_item_classes[z] = enemy_item_classes_json->at(name)->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
} else if (prev_table) {
|
||||
this->enemy_item_classes = prev_table->enemy_item_classes;
|
||||
} else {
|
||||
this->enemy_type_drop_probs = prev_table->enemy_type_drop_probs;
|
||||
}
|
||||
|
||||
if (json.count("EnemyItemClasses")) {
|
||||
const auto& dict = json.at("EnemyItemClasses").as_dict();
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
this->enemy_type_item_classes[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->enemy_type_item_classes = prev_table->enemy_type_item_classes;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* name_for_common_item_class(uint8_t item_class) {
|
||||
switch (item_class) {
|
||||
case 0x00:
|
||||
return "WEAPON ";
|
||||
return "WEAPON";
|
||||
case 0x01:
|
||||
return "ARMOR ";
|
||||
return "ARMOR";
|
||||
case 0x02:
|
||||
return "SHIELD ";
|
||||
return "SHIELD";
|
||||
case 0x03:
|
||||
return "UNIT ";
|
||||
return "UNIT";
|
||||
case 0x04:
|
||||
return "TOOL ";
|
||||
return "TOOL";
|
||||
case 0x05:
|
||||
return "MESETA ";
|
||||
return "MESETA";
|
||||
case 0x06:
|
||||
return "NOTHING";
|
||||
default:
|
||||
@@ -203,42 +193,29 @@ static const char* name_for_common_item_class(uint8_t item_class) {
|
||||
}
|
||||
|
||||
void CommonItemSet::Table::print(FILE* stream) const {
|
||||
const auto& meseta_ranges = this->enemy_meseta_ranges;
|
||||
const auto& meseta_ranges = this->enemy_type_meseta_ranges;
|
||||
const auto& drop_probs = this->enemy_type_drop_probs;
|
||||
const auto& item_classes = this->enemy_item_classes;
|
||||
const auto& item_classes = this->enemy_type_item_classes;
|
||||
phosg::fwrite_fmt(stream, "Enemy tables:\n");
|
||||
phosg::fwrite_fmt(stream, " ## $LOW $HIGH DAR% ITEM ENEMIES\n");
|
||||
for (size_t z = 0; z < NUM_RT_INDEXES_V4; z++) {
|
||||
string enemies_str;
|
||||
for (EnemyType enemy_type : enemy_types_for_rare_table_index(this->episode, z)) {
|
||||
if (!enemies_str.empty()) {
|
||||
enemies_str += ", ";
|
||||
}
|
||||
enemies_str += phosg::name_for_enum(enemy_type);
|
||||
}
|
||||
if (drop_probs[z] || !enemies_str.empty()) {
|
||||
phosg::fwrite_fmt(stream, " {:02X} {:5} {:5} {:3}% {:02X}:{} {}\n",
|
||||
z, meseta_ranges[z].min, meseta_ranges[z].max, drop_probs[z], item_classes[z],
|
||||
name_for_common_item_class(item_classes[z]), enemies_str);
|
||||
} else {
|
||||
phosg::fwrite_fmt(stream, " {:02X} ----- ----- 0% --\n", z);
|
||||
phosg::fwrite_fmt(stream, " ##:ENEMY $LOW $HIGH DAR% ITEM\n");
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
const auto& def = type_definition_for_enemy(enemy_type);
|
||||
try {
|
||||
const auto& meseta_range = meseta_ranges.at(enemy_type);
|
||||
const auto& drop_prob = drop_probs.at(enemy_type);
|
||||
const auto& item_class = item_classes.at(enemy_type);
|
||||
phosg::fwrite_fmt(stream, " {:02X}:{:<23} {:5} {:5} {:3}% {:02X}:{:<7}\n",
|
||||
def.rt_index, phosg::name_for_enum(enemy_type),
|
||||
meseta_range.min, meseta_range.max, drop_prob, item_class,
|
||||
name_for_common_item_class(item_class));
|
||||
} catch (const out_of_range&) {
|
||||
phosg::fwrite_fmt(stream, " {:02X}:{:<23} ----- ----- ---- --:-------\n",
|
||||
def.rt_index, phosg::name_for_enum(enemy_type));
|
||||
}
|
||||
}
|
||||
|
||||
static const array<const char*, 12> base_weapon_type_names = {
|
||||
"SABER ",
|
||||
"SWORD ",
|
||||
"DAGGER ",
|
||||
"PARTISAN",
|
||||
"SLICER ",
|
||||
"HANDGUN ",
|
||||
"RIFLE ",
|
||||
"MECHGUN ",
|
||||
"SHOT ",
|
||||
"CANE ",
|
||||
"ROD ",
|
||||
"WAND ",
|
||||
};
|
||||
"SABER", "SWORD", "DAGGER", "PARTISAN", "SLICER", "HANDGUN", "RIFLE", "MECHGUN", "SHOT", "CANE", "ROD", "WAND"};
|
||||
phosg::fwrite_fmt(stream, "Base weapon config:\n");
|
||||
phosg::fwrite_fmt(stream, " TYPE PROB [SB AL] FLOORS\n");
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
@@ -256,7 +233,7 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
floor_to_class[x] = this->subtype_base_table[z] + (x / this->subtype_area_length_table[z]);
|
||||
}
|
||||
}
|
||||
phosg::fwrite_fmt(stream, " {:02X}:{} {:3}% [{:02X} {:02X}] {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}\n",
|
||||
phosg::fwrite_fmt(stream, " {:02X}:{:<8} {:3}% [{:02X} {:02X}] {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}\n",
|
||||
z, base_weapon_type_names[z], this->base_weapon_type_prob_table[z],
|
||||
this->subtype_base_table[z], this->subtype_area_length_table[z],
|
||||
floor_to_class[0], floor_to_class[1], floor_to_class[2], floor_to_class[3], floor_to_class[4],
|
||||
@@ -413,20 +390,50 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
||||
phosg::format_data_string(&this->armor_slot_count_prob_table, sizeof(this->armor_slot_count_prob_table)),
|
||||
phosg::format_data_string(&other.armor_slot_count_prob_table, sizeof(other.armor_slot_count_prob_table)));
|
||||
}
|
||||
if (this->enemy_meseta_ranges != other.enemy_meseta_ranges) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_meseta_ranges: {} -> {}\n",
|
||||
phosg::format_data_string(&this->enemy_meseta_ranges, sizeof(this->enemy_meseta_ranges)),
|
||||
phosg::format_data_string(&other.enemy_meseta_ranges, sizeof(other.enemy_meseta_ranges)));
|
||||
|
||||
auto format_enemy_range_table = [&](const std::unordered_map<EnemyType, Range<uint16_t>>& table) -> std::string {
|
||||
string ret = "";
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
const auto& range = table.at(enemy_type);
|
||||
if (!ret.empty()) {
|
||||
ret += ",";
|
||||
}
|
||||
ret += std::format("{}=[{},{}]", phosg::name_for_enum(enemy_type), range.min, range.max);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
auto format_enemy_u8_table = [&](const std::unordered_map<EnemyType, uint8_t>& table) -> std::string {
|
||||
string ret = "";
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
uint8_t value = table.at(enemy_type);
|
||||
if (!ret.empty()) {
|
||||
ret += ",";
|
||||
}
|
||||
ret += std::format("{}={}", phosg::name_for_enum(enemy_type), value);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
if (this->enemy_type_meseta_ranges != other.enemy_type_meseta_ranges) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_type_meseta_ranges: {} -> {}\n",
|
||||
format_enemy_range_table(this->enemy_type_meseta_ranges),
|
||||
format_enemy_range_table(other.enemy_type_meseta_ranges));
|
||||
}
|
||||
if (this->enemy_type_drop_probs != other.enemy_type_drop_probs) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_type_drop_probs: {} -> {}\n",
|
||||
phosg::format_data_string(&this->enemy_type_drop_probs, sizeof(this->enemy_type_drop_probs)),
|
||||
phosg::format_data_string(&other.enemy_type_drop_probs, sizeof(other.enemy_type_drop_probs)));
|
||||
format_enemy_u8_table(this->enemy_type_drop_probs),
|
||||
format_enemy_u8_table(other.enemy_type_drop_probs));
|
||||
}
|
||||
if (this->enemy_item_classes != other.enemy_item_classes) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_item_classes: {} -> {}\n",
|
||||
phosg::format_data_string(&this->enemy_item_classes, sizeof(this->enemy_item_classes)),
|
||||
phosg::format_data_string(&other.enemy_item_classes, sizeof(other.enemy_item_classes)));
|
||||
if (this->enemy_type_item_classes != other.enemy_type_item_classes) {
|
||||
phosg::fwrite_fmt(stream, "> enemy_type_item_classes: {} -> {}\n",
|
||||
format_enemy_u8_table(this->enemy_type_item_classes),
|
||||
format_enemy_u8_table(other.enemy_type_item_classes));
|
||||
}
|
||||
if (this->box_meseta_ranges != other.box_meseta_ranges) {
|
||||
phosg::fwrite_fmt(stream, "> box_meseta_ranges: {} -> {}\n",
|
||||
@@ -517,44 +524,45 @@ phosg::JSON CommonItemSet::Table::json(std::shared_ptr<const Table> prev_table)
|
||||
ret.emplace("ArmorSlotCountProbTable", to_json(this->armor_slot_count_prob_table));
|
||||
}
|
||||
|
||||
bool needs_enemy_meseta_ranges = (!prev_table || (this->enemy_meseta_ranges != prev_table->enemy_meseta_ranges));
|
||||
bool needs_enemy_type_drop_probs = (!prev_table || (this->enemy_type_drop_probs != prev_table->enemy_type_drop_probs));
|
||||
bool needs_enemy_item_classes = (!prev_table || (this->enemy_item_classes != prev_table->enemy_item_classes));
|
||||
if (needs_enemy_meseta_ranges || needs_enemy_type_drop_probs || needs_enemy_item_classes) {
|
||||
phosg::JSON enemy_meseta_ranges_json = phosg::JSON::dict();
|
||||
bool needs_enemy_type_meseta_ranges = (!prev_table ||
|
||||
(this->enemy_type_meseta_ranges != prev_table->enemy_type_meseta_ranges));
|
||||
bool needs_enemy_type_drop_probs = (!prev_table ||
|
||||
(this->enemy_type_drop_probs != prev_table->enemy_type_drop_probs));
|
||||
bool needs_enemy_type_item_classes = (!prev_table ||
|
||||
(this->enemy_type_item_classes != prev_table->enemy_type_item_classes));
|
||||
if (needs_enemy_type_meseta_ranges || needs_enemy_type_drop_probs || needs_enemy_type_item_classes) {
|
||||
phosg::JSON enemy_type_meseta_ranges_json = phosg::JSON::dict();
|
||||
phosg::JSON enemy_type_drop_probs_json = phosg::JSON::dict();
|
||||
phosg::JSON enemy_item_classes_json = phosg::JSON::dict();
|
||||
for (size_t z = 0; z < NUM_RT_INDEXES_V4; z++) {
|
||||
auto types = enemy_types_for_rare_table_index(this->episode, z);
|
||||
vector<string> names;
|
||||
if (types.empty()) {
|
||||
names.emplace_back(std::format("!{:02X}", z));
|
||||
} else {
|
||||
for (auto type : types) {
|
||||
names.emplace_back(phosg::name_for_enum(type));
|
||||
phosg::JSON enemy_type_item_classes_json = phosg::JSON::dict();
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
auto name = phosg::name_for_enum(enemy_type);
|
||||
if (needs_enemy_type_meseta_ranges) {
|
||||
try {
|
||||
enemy_type_meseta_ranges_json.emplace(name, to_json(this->enemy_type_meseta_ranges.at(enemy_type)));
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
for (const auto& name : names) {
|
||||
if (needs_enemy_meseta_ranges && (!types.empty() || !this->enemy_meseta_ranges[z].empty())) {
|
||||
enemy_meseta_ranges_json.emplace(name, to_json(this->enemy_meseta_ranges[z]));
|
||||
if (needs_enemy_type_drop_probs) {
|
||||
try {
|
||||
enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs.at(enemy_type));
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (needs_enemy_type_drop_probs && (!types.empty() || this->enemy_type_drop_probs[z])) {
|
||||
enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs[z]);
|
||||
}
|
||||
if (needs_enemy_item_classes && (!types.empty() || (this->enemy_item_classes[z] != ((z == 0) ? 0x00 : 0xFF)))) {
|
||||
enemy_item_classes_json.emplace(name, this->enemy_item_classes[z]);
|
||||
}
|
||||
if (needs_enemy_type_item_classes) {
|
||||
try {
|
||||
enemy_type_item_classes_json.emplace(name, this->enemy_type_item_classes.at(enemy_type));
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_enemy_meseta_ranges) {
|
||||
ret.emplace("EnemyMesetaRanges", std::move(enemy_meseta_ranges_json));
|
||||
if (needs_enemy_type_meseta_ranges) {
|
||||
ret.emplace("EnemyMesetaRanges", std::move(enemy_type_meseta_ranges_json));
|
||||
}
|
||||
if (needs_enemy_type_drop_probs) {
|
||||
ret.emplace("EnemyTypeDropProbs", std::move(enemy_type_drop_probs_json));
|
||||
}
|
||||
if (needs_enemy_item_classes) {
|
||||
ret.emplace("EnemyItemClasses", std::move(enemy_item_classes_json));
|
||||
if (needs_enemy_type_item_classes) {
|
||||
ret.emplace("EnemyItemClasses", std::move(enemy_type_item_classes_json));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,13 +713,27 @@ void CommonItemSet::Table::parse_itempt_t(const phosg::StringReader& r, bool is_
|
||||
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<BE>>, NUM_RT_INDEXES_V3>>(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};
|
||||
const auto& enemy_rt_index_meseta_ranges = r.pget<parray<Range<U16T<BE>>, NUM_RT_INDEXES_V3>>(
|
||||
offsets.enemy_rt_index_meseta_ranges_offset);
|
||||
const auto& enemy_rt_index_drop_probs = r.pget<parray<uint8_t, NUM_RT_INDEXES_V3>>(
|
||||
offsets.enemy_rt_index_drop_probs_offset);
|
||||
const auto& enemy_rt_index_item_classes = r.pget<parray<uint8_t, NUM_RT_INDEXES_V3>>(
|
||||
offsets.enemy_rt_index_item_classes_offset);
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
const auto& def = type_definition_for_enemy(enemy_type);
|
||||
if (def.valid_in_episode(this->episode) && (def.rt_index < enemy_rt_index_meseta_ranges.size())) {
|
||||
const auto& meseta_range = enemy_rt_index_meseta_ranges[def.rt_index];
|
||||
if (meseta_range.max > 0) {
|
||||
this->enemy_type_meseta_ranges.emplace(enemy_type, Range<uint16_t>{meseta_range.min, meseta_range.max});
|
||||
}
|
||||
if (enemy_rt_index_drop_probs[def.rt_index] > 0) {
|
||||
this->enemy_type_drop_probs.emplace(enemy_type, enemy_rt_index_drop_probs[def.rt_index]);
|
||||
}
|
||||
if (enemy_rt_index_item_classes[def.rt_index] != 0xFF) {
|
||||
this->enemy_type_item_classes.emplace(enemy_type, enemy_rt_index_item_classes[def.rt_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->enemy_type_drop_probs = r.pget<parray<uint8_t, NUM_RT_INDEXES_V3>>(offsets.enemy_type_drop_probs_offset);
|
||||
this->enemy_item_classes = r.pget<parray<uint8_t, NUM_RT_INDEXES_V3>>(offsets.enemy_item_classes_offset);
|
||||
this->enemy_item_classes.clear_after(NUM_RT_INDEXES_V3, 0xFF);
|
||||
{
|
||||
const auto& data = r.pget<parray<Range<U16T<BE>>, 0x0A>>(offsets.box_meseta_ranges_offset);
|
||||
for (size_t z = 0; z < data.size(); z++) {
|
||||
@@ -873,7 +895,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
section_id);
|
||||
};
|
||||
|
||||
for (Episode episode : ALL_EPISODES_V4) {
|
||||
for (Episode episode : ALL_EPISODES_V3) {
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
phosg::StringReader r;
|
||||
@@ -898,17 +920,15 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
}
|
||||
}
|
||||
|
||||
if (episode != Episode::EP4) {
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
try {
|
||||
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
|
||||
auto table = make_shared<Table>(r, is_big_endian, true, episode);
|
||||
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);
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
// GC NTE doesn't have Ep2 challenge; just skip adding the table
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
try {
|
||||
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
|
||||
auto table = make_shared<Table>(r, is_big_endian, true, episode);
|
||||
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);
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
// GC NTE doesn't have Ep2 challenge; just skip adding the table
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -42,9 +42,10 @@ public:
|
||||
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>, NUM_RT_INDEXES_V4> enemy_meseta_ranges;
|
||||
parray<uint8_t, NUM_RT_INDEXES_V4> enemy_type_drop_probs;
|
||||
parray<uint8_t, NUM_RT_INDEXES_V4> enemy_item_classes;
|
||||
// Note: PSO originally uses arrays indexed by rt_index here, but we index enemies by the EnemyType enum instead
|
||||
std::unordered_map<EnemyType, Range<uint16_t>> enemy_type_meseta_ranges;
|
||||
std::unordered_map<EnemyType, uint8_t> enemy_type_drop_probs;
|
||||
std::unordered_map<EnemyType, uint8_t> enemy_type_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;
|
||||
@@ -126,17 +127,17 @@ public:
|
||||
// V2/V3: -> parray<uint8_t, 0x05>
|
||||
/* 14 */ U32T<BE> armor_slot_count_prob_table_offset;
|
||||
|
||||
// This array (indexed by enemy_type) specifies the range of meseta values that each enemy can drop.
|
||||
// This array (indexed by rt_index) specifies the range of meseta values that each enemy can drop.
|
||||
// V2/V3: -> parray<Range<U16T>, NUM_RT_INDEXES_V3>
|
||||
/* 18 */ U32T<BE> enemy_meseta_ranges_offset;
|
||||
/* 18 */ U32T<BE> enemy_rt_index_meseta_ranges_offset;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the percent chance that the enemy drops anything at
|
||||
// Each byte in this table (indexed by rt_index) 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/V3: -> parray<uint8_t, NUM_RT_INDEXES_V3>
|
||||
/* 1C */ U32T<BE> enemy_type_drop_probs_offset;
|
||||
/* 1C */ U32T<BE> enemy_rt_index_drop_probs_offset;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the class of item that can drop. The values are:
|
||||
// Each byte in this table (indexed by rt_index) represents the class of item that can drop. The values are:
|
||||
// 00 = weapon
|
||||
// 01 = armor
|
||||
// 02 = shield
|
||||
@@ -145,7 +146,7 @@ public:
|
||||
// 05 = meseta
|
||||
// Anything else = no item
|
||||
// V2/V3: -> parray<uint8_t, NUM_RT_INDEXES_V3>
|
||||
/* 20 */ U32T<BE> enemy_item_classes_offset;
|
||||
/* 20 */ U32T<BE> enemy_rt_index_item_classes_offset;
|
||||
|
||||
// This table (indexed by area - 1) specifies the ranges of meseta values that can drop from boxes.
|
||||
// V2/V3: -> parray<Range<U16T>, 0x0A>
|
||||
@@ -229,7 +230,7 @@ public:
|
||||
// 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 value has the same meaning as in enemy_item_classes above.
|
||||
// the weight values in the column.) The resulting value has the same meaning as in enemy_rt_index_item_classes.
|
||||
// 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
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ static constexpr size_t NUM_RT_INDEXES_V3 = 0x64;
|
||||
static constexpr size_t NUM_RT_INDEXES_V4 = 0x70;
|
||||
|
||||
enum class EnemyType : uint8_t {
|
||||
MIN_VALUE = 0,
|
||||
UNKNOWN = 0,
|
||||
NONE,
|
||||
NON_ENEMY_NPC,
|
||||
@@ -146,7 +147,7 @@ enum class EnemyType : uint8_t {
|
||||
ZOL_GIBBON,
|
||||
ZU_CRATER,
|
||||
ZU_DESERT,
|
||||
MAX_ENEMY_TYPE,
|
||||
MAX_VALUE,
|
||||
};
|
||||
|
||||
struct EnemyTypeDefinition {
|
||||
|
||||
+61
-47
@@ -188,17 +188,23 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area) {
|
||||
}
|
||||
}
|
||||
|
||||
ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, uint8_t area) {
|
||||
ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type, uint8_t area) {
|
||||
try {
|
||||
// Note: The original GC implementation uses (enemy_type > 0x58) here; we extend it to the full array size for BB
|
||||
if (enemy_type >= NUM_RT_INDEXES_V4) {
|
||||
this->log.warning_f("Invalid enemy type: {:X}", enemy_type);
|
||||
return DropResult();
|
||||
}
|
||||
this->log.info_f("Enemy type: {:X}", enemy_type);
|
||||
// Note: The original implementation has a bounds check for enemy_type here, because it uses rt_index instead
|
||||
// if (enemy_type >= NUM_RT_INDEXES_V4) {
|
||||
// this->log.warning_f("Invalid enemy type: {:X}", enemy_type);
|
||||
// return DropResult();
|
||||
// }
|
||||
this->log.info_f("Enemy type: {}", phosg::name_for_enum(enemy_type));
|
||||
|
||||
auto pt = this->pt(area);
|
||||
uint8_t type_drop_prob = pt->enemy_type_drop_probs.at(enemy_type);
|
||||
uint8_t type_drop_prob = 0;
|
||||
try {
|
||||
type_drop_prob = pt->enemy_type_drop_probs.at(enemy_type);
|
||||
} catch (const std::out_of_range&) {
|
||||
this->log.info_f("No drop probability is set for this enemy type");
|
||||
return DropResult();
|
||||
}
|
||||
uint8_t drop_sample = this->rand_int(100);
|
||||
if (drop_sample >= type_drop_prob) {
|
||||
this->log.info_f("Drop not chosen ({} >= {})", drop_sample, type_drop_prob);
|
||||
@@ -212,10 +218,8 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, u
|
||||
if (!res.item.empty()) {
|
||||
res.is_from_rare_table = true;
|
||||
} else {
|
||||
uint32_t item_class_determinant =
|
||||
this->should_allow_meseta_drops() ? this->rand_int(3) : (this->rand_int(2) + 1);
|
||||
|
||||
uint32_t item_class;
|
||||
uint8_t item_class_determinant = this->should_allow_meseta_drops() ? this->rand_int(3) : (this->rand_int(2) + 1);
|
||||
uint8_t item_class;
|
||||
switch (item_class_determinant) {
|
||||
case 0:
|
||||
item_class = 5;
|
||||
@@ -224,7 +228,12 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, u
|
||||
item_class = 4;
|
||||
break;
|
||||
case 2:
|
||||
item_class = pt->enemy_item_classes.at(enemy_type);
|
||||
try {
|
||||
item_class = pt->enemy_type_item_classes.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
this->log.info_f("Item class is not set for this enemy type");
|
||||
item_class = 0xFF;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid item class determinant");
|
||||
@@ -251,7 +260,12 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, u
|
||||
break;
|
||||
case 5: // Meseta
|
||||
res.item.data1[0] = 0x04;
|
||||
res.item.data2d = this->choose_meseta_amount(pt->enemy_meseta_ranges, enemy_type) & 0xFFFF;
|
||||
try {
|
||||
res.item.data2d = this->choose_meseta_amount(pt->enemy_type_meseta_ranges.at(enemy_type)) & 0xFFFF;
|
||||
} catch (const out_of_range&) {
|
||||
this->log.info_f("Meseta range is not set for this enemy type");
|
||||
return DropResult();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return res;
|
||||
@@ -305,24 +319,18 @@ float ItemCreator::rand_float_0_1_from_crypt() {
|
||||
return (static_cast<double>(this->rand_crypt->next() >> 16) / 65536.0);
|
||||
}
|
||||
|
||||
template <size_t NumRanges>
|
||||
uint32_t ItemCreator::choose_meseta_amount(
|
||||
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;
|
||||
|
||||
uint32_t ItemCreator::choose_meseta_amount(const CommonItemSet::Table::Range<uint16_t>& range) {
|
||||
// Note: The original code returns 0xFF here if either limit is equal to 0xFF (despite them being 16-bit integers!)
|
||||
uint16_t ret;
|
||||
if (min == max) {
|
||||
ret = min;
|
||||
} else if (max < min) {
|
||||
ret = this->rand_int((min - max) + 1) + max;
|
||||
if (range.min == range.max) {
|
||||
ret = range.min;
|
||||
} else if (range.max < range.min) {
|
||||
ret = this->rand_int((range.min - range.max) + 1) + range.max;
|
||||
} else {
|
||||
ret = this->rand_int((max - min) + 1) + min;
|
||||
ret = this->rand_int((range.max - range.min) + 1) + range.min;
|
||||
}
|
||||
|
||||
this->log.info_f("Chose {} Meseta from range [{}, {}]", ret, min, max);
|
||||
this->log.info_f("Chose {} Meseta from range [{}, {}]", ret, range.min, range.max);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -330,28 +338,32 @@ 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, uint8_t area) {
|
||||
ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(EnemyType enemy_type, uint8_t area) {
|
||||
// Note: The original implementation has a bounds check for enemy_type here, since it uses rt_index instead.
|
||||
// if ((enemy_type <= 0) || (enemy_type >= NUM_RT_INDEXES_V4)) return ItemData{};
|
||||
if (!this->are_rare_drops_allowed()) {
|
||||
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).
|
||||
Episode episode = episode_for_area(area);
|
||||
auto rare_specs = this->rare_item_set->get_enemy_specs(
|
||||
this->mode, episode, this->difficulty, this->section_id, enemy_type);
|
||||
ItemData item;
|
||||
if (this->are_rare_drops_allowed() && (enemy_type > 0) && (enemy_type < NUM_RT_INDEXES_V4)) {
|
||||
// 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).
|
||||
Episode episode = episode_for_area(area);
|
||||
auto rare_specs = this->rare_item_set->get_enemy_specs(
|
||||
this->mode, episode, this->difficulty, this->section_id, enemy_type);
|
||||
for (const auto& spec : rare_specs) {
|
||||
item = this->check_rate_and_create_rare_item(spec, area);
|
||||
if (!item.empty()) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info_f("Enemy spec {:08X} produced item {}", spec.probability, hex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (const auto& spec : rare_specs) {
|
||||
item = this->check_rate_and_create_rare_item(spec, area);
|
||||
if (!item.empty()) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info_f("Enemy spec {:08X} did not produce item {}", spec.probability, hex);
|
||||
this->log.info_f("Enemy spec {:08X} produced item {}", spec.probability, hex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info_f("Enemy spec {:08X} did not produce item {}", spec.probability, hex);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
@@ -617,9 +629,11 @@ void ItemCreator::generate_common_item_variances(ItemData& item, uint8_t area) {
|
||||
case 3:
|
||||
this->generate_common_tool_variances(item, area);
|
||||
break;
|
||||
case 4:
|
||||
item.data2d = this->choose_meseta_amount(this->pt(area)->box_meseta_ranges, this->table_index_for_area(area)) & 0xFFFF;
|
||||
case 4: {
|
||||
const auto& range = this->pt(area)->box_meseta_ranges.at(this->table_index_for_area(area));
|
||||
item.data2d = this->choose_meseta_amount(range) & 0xFFFF;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Note: The original code does the following here:
|
||||
// item.clear();
|
||||
|
||||
+3
-4
@@ -37,7 +37,7 @@ public:
|
||||
bool is_from_rare_table = false;
|
||||
};
|
||||
|
||||
DropResult on_monster_item_drop(uint32_t enemy_type, uint8_t area);
|
||||
DropResult on_monster_item_drop(EnemyType enemy_type, uint8_t area);
|
||||
DropResult on_box_item_drop(uint8_t area);
|
||||
// Note: param3-6 refer to the corresponding fields of the object definition
|
||||
DropResult on_specialized_box_item_drop(uint8_t area, float param3, uint32_t param4, uint32_t param5, uint32_t param6);
|
||||
@@ -116,12 +116,11 @@ private:
|
||||
uint32_t rand_int(uint64_t max);
|
||||
float rand_float_0_1_from_crypt();
|
||||
|
||||
template <size_t NumRanges>
|
||||
uint32_t choose_meseta_amount(const parray<CommonItemSet::Table::Range<uint16_t>, NumRanges> ranges, size_t table_index);
|
||||
uint32_t choose_meseta_amount(const CommonItemSet::Table::Range<uint16_t>& range);
|
||||
|
||||
bool should_allow_meseta_drops() const;
|
||||
|
||||
ItemData check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type, uint8_t area);
|
||||
ItemData check_rare_spec_and_create_rare_enemy_item(EnemyType enemy_type, uint8_t area);
|
||||
ItemData check_rare_specs_and_create_rare_box_item(uint8_t area);
|
||||
ItemData check_rate_and_create_rare_item(const RareItemSet::ExpandedDrop& drop, uint8_t area);
|
||||
|
||||
|
||||
+50
-24
@@ -1922,8 +1922,7 @@ Action a_extract_ppk("extract-ppk", "\
|
||||
PC/BB format. For PPK archives, the --password= option is required.\n",
|
||||
a_extract_archive_fn);
|
||||
|
||||
Action a_encode_sjis(
|
||||
"transcode-text", nullptr, +[](phosg::Arguments& args) {
|
||||
Action a_transcode_text("transcode-text", nullptr, +[](phosg::Arguments& args) {
|
||||
TextTranscoder* tt_from = nullptr;
|
||||
{
|
||||
std::string from_name = args.get<std::string>("from");
|
||||
@@ -1963,8 +1962,7 @@ Action a_encode_sjis(
|
||||
if (tt_to) {
|
||||
data = (*tt_to)(data);
|
||||
}
|
||||
write_output_data(args, data.data(), data.size(), "txt");
|
||||
});
|
||||
write_output_data(args, data.data(), data.size(), "txt"); });
|
||||
|
||||
Action a_decode_text_archive(
|
||||
"decode-text-archive", "\
|
||||
@@ -2204,6 +2202,25 @@ Action a_download_files(
|
||||
io_context->run();
|
||||
});
|
||||
|
||||
std::shared_ptr<RareItemSet> load_rare_item_set(
|
||||
const std::string& filename, bool is_v1, std::shared_ptr<const ItemNameIndex> v4_item_name_index) {
|
||||
string filename_lower = phosg::tolower(filename);
|
||||
auto data = make_shared<string>(phosg::load_file(filename));
|
||||
if (filename_lower.ends_with(".json")) {
|
||||
return make_shared<RareItemSet>(phosg::JSON::parse(*data), v4_item_name_index);
|
||||
} else if (filename_lower.ends_with(".gsl")) {
|
||||
return make_shared<RareItemSet>(GSLArchive(data, false), false);
|
||||
} else if (filename_lower.ends_with(".gslb")) {
|
||||
return make_shared<RareItemSet>(GSLArchive(data, true), true);
|
||||
} else if (filename_lower.ends_with(".afs")) {
|
||||
return make_shared<RareItemSet>(AFSArchive(data), is_v1);
|
||||
} else if (filename_lower.ends_with(".rel")) {
|
||||
return make_shared<RareItemSet>(*data, true);
|
||||
} else {
|
||||
throw runtime_error("cannot determine input format; use a filename ending with .json, .gsl, .gslb, .afs, or .rel");
|
||||
}
|
||||
}
|
||||
|
||||
Action a_convert_rare_item_set(
|
||||
"convert-rare-item-set", "\
|
||||
convert-rare-item-set INPUT-FILENAME [OUTPUT-FILENAME] [OPTIONS]\n\
|
||||
@@ -2233,24 +2250,8 @@ Action a_convert_rare_item_set(
|
||||
if (input_filename.empty() || (input_filename == "-")) {
|
||||
throw runtime_error("input filename must be given");
|
||||
}
|
||||
|
||||
string input_filename_lower = phosg::tolower(input_filename);
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
shared_ptr<RareItemSet> rs;
|
||||
if (input_filename_lower.ends_with(".json")) {
|
||||
rs = make_shared<RareItemSet>(phosg::JSON::parse(*data), s->item_name_index_opt(get_cli_version(args, Version::BB_V4)));
|
||||
} else if (input_filename_lower.ends_with(".gsl")) {
|
||||
rs = make_shared<RareItemSet>(GSLArchive(data, false), false);
|
||||
} else if (input_filename_lower.ends_with(".gslb")) {
|
||||
rs = make_shared<RareItemSet>(GSLArchive(data, true), true);
|
||||
} else if (input_filename_lower.ends_with(".afs")) {
|
||||
rs = make_shared<RareItemSet>(AFSArchive(data), is_v1(get_cli_version(args, Version::DC_V2)));
|
||||
} else if (input_filename_lower.ends_with(".rel")) {
|
||||
rs = make_shared<RareItemSet>(*data, true);
|
||||
} else {
|
||||
throw runtime_error("cannot determine input format; use a filename ending with .json, .gsl, .gslb, .afs, or .rel");
|
||||
}
|
||||
|
||||
auto rs = load_rare_item_set(
|
||||
input_filename, is_v1(get_cli_version(args, Version::BB_V4)), s->item_name_index(Version::BB_V4));
|
||||
if (rate_factor != 1.0) {
|
||||
rs->multiply_all_rates(rate_factor);
|
||||
}
|
||||
@@ -2294,6 +2295,32 @@ Action a_convert_rare_item_set(
|
||||
throw runtime_error("cannot determine output format; use a filename ending with .json, .gsl, .gslb, or .afs");
|
||||
}
|
||||
});
|
||||
Action a_compare_rare_item_set(
|
||||
"compare-rare-item-set", nullptr,
|
||||
+[](phosg::Arguments& args) {
|
||||
string input_filename1 = args.get<string>(1, false);
|
||||
if (input_filename1.empty() || (input_filename1 == "-")) {
|
||||
throw runtime_error("two input filenames must be given");
|
||||
}
|
||||
string input_filename2 = args.get<string>(2, false);
|
||||
if (input_filename2.empty() || (input_filename2 == "-")) {
|
||||
throw runtime_error("two input filenames must be given");
|
||||
}
|
||||
|
||||
auto s = make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->load_patch_indexes();
|
||||
s->load_text_index();
|
||||
s->load_item_definitions();
|
||||
s->load_item_name_indexes();
|
||||
s->load_drop_tables();
|
||||
|
||||
bool is_v1 = ::is_v1(get_cli_version(args, Version::BB_V4));
|
||||
auto rs1 = load_rare_item_set(input_filename1, is_v1, s->item_name_index(Version::BB_V4));
|
||||
auto rs2 = load_rare_item_set(input_filename2, is_v1, s->item_name_index(Version::BB_V4));
|
||||
|
||||
rs1->print_diff(stdout, *rs2);
|
||||
});
|
||||
|
||||
static shared_ptr<CommonItemSet> load_common_item_set(
|
||||
const std::string& filename, const std::string& ct_filename, bool big_endian) {
|
||||
@@ -3098,8 +3125,7 @@ Action a_check_supermaps(
|
||||
auto f = phosg::fopen_unique(filename, "wt");
|
||||
phosg::fwrite_fmt(f.get(), "QUEST {} ({})\n", it.first, it.second->meta.name);
|
||||
phosg::fwrite_fmt(f.get(), "ENEMY--------------- DCNTE 11/2K DC-V1 DC-V2 PCNTE PC-V2 GCNTE GC-V3 XB-V3 BB-V4\n");
|
||||
for (size_t type_ss = 0; type_ss < static_cast<size_t>(EnemyType::MAX_ENEMY_TYPE); type_ss++) {
|
||||
EnemyType type = static_cast<EnemyType>(type_ss);
|
||||
for (auto type : phosg::EnumRange<EnemyType>()) {
|
||||
bool any_count_nonzero = false;
|
||||
array<size_t, NUM_VERSIONS> counts;
|
||||
for (Version v : ALL_NON_PATCH_VERSIONS) {
|
||||
|
||||
@@ -970,7 +970,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
||||
}
|
||||
} else {
|
||||
c->log.info_f("Creating item from enemy {:04X} (area {:02X})", cmd.entity_index, cmd.effective_area);
|
||||
res = c->proxy_session->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
|
||||
res = c->proxy_session->item_creator->on_monster_item_drop(rec.effective_enemy_type, cmd.effective_area);
|
||||
}
|
||||
|
||||
if (res.item.empty()) {
|
||||
|
||||
+168
-89
@@ -177,23 +177,31 @@ RareItemSet::ParsedRELData::ParsedRELData(phosg::StringReader r, bool big_endian
|
||||
}
|
||||
|
||||
RareItemSet::ParsedRELData::ParsedRELData(const SpecCollection& collection) {
|
||||
for (const auto& specs : collection.rt_index_to_specs) {
|
||||
ExpandedDrop effective_spec;
|
||||
this->monster_rares.resize(NUM_RT_INDEXES_V4);
|
||||
|
||||
for (const auto& [enemy_type, specs] : collection.enemy_specs) {
|
||||
const auto& def = type_definition_for_enemy(enemy_type);
|
||||
if (def.rt_index == 0xFF) {
|
||||
throw runtime_error(std::format(
|
||||
"monster spec for {} has no rt_index and cannot be converted to ItemRT format", def.enum_name));
|
||||
}
|
||||
|
||||
auto& dest_spec = this->monster_rares.at(def.rt_index);
|
||||
for (const auto& spec : specs) {
|
||||
if (effective_spec.data.empty()) {
|
||||
effective_spec = spec;
|
||||
} else if ((effective_spec.probability != spec.probability) || (effective_spec.data != spec.data)) {
|
||||
throw runtime_error("monster spec cannot be converted to ItemRT format");
|
||||
if (dest_spec.data.empty()) {
|
||||
dest_spec = spec;
|
||||
} else if ((dest_spec.probability != spec.probability) || (dest_spec.data != spec.data)) {
|
||||
throw runtime_error(std::format(
|
||||
"monster spec for {} contains multiple drops and cannot be converted to ItemRT format", def.enum_name));
|
||||
}
|
||||
}
|
||||
this->monster_rares.emplace_back(specs.empty() ? ExpandedDrop() : specs[0]);
|
||||
}
|
||||
|
||||
if (collection.box_area_norm_to_specs.size() > 0xFF) {
|
||||
if (collection.box_specs.size() > 0xFF) {
|
||||
throw runtime_error("area_norm value too high");
|
||||
}
|
||||
for (uint8_t area_norm = 0; area_norm < collection.box_area_norm_to_specs.size(); area_norm++) {
|
||||
for (const auto& spec : collection.box_area_norm_to_specs[area_norm]) {
|
||||
for (uint8_t area_norm = 0; area_norm < collection.box_specs.size(); area_norm++) {
|
||||
for (const auto& spec : collection.box_specs[area_norm]) {
|
||||
uint8_t area_norm_plus_1 = area_norm + 1;
|
||||
this->box_rares.emplace_back(BoxRare{.area_norm_plus_1 = area_norm_plus_1, .drop = spec});
|
||||
}
|
||||
@@ -208,27 +216,26 @@ std::string RareItemSet::ParsedRELData::serialize(bool big_endian, bool is_v1) c
|
||||
}
|
||||
}
|
||||
|
||||
RareItemSet::SpecCollection RareItemSet::ParsedRELData::as_collection() const {
|
||||
RareItemSet::SpecCollection RareItemSet::ParsedRELData::as_collection(Episode episode) const {
|
||||
SpecCollection ret;
|
||||
for (size_t z = 0; z < this->monster_rares.size(); z++) {
|
||||
const auto& drop = this->monster_rares[z];
|
||||
for (size_t rt_index = 0; rt_index < this->monster_rares.size(); rt_index++) {
|
||||
const auto& drop = this->monster_rares[rt_index];
|
||||
if (drop.data.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (z >= ret.rt_index_to_specs.size()) {
|
||||
ret.rt_index_to_specs.resize(z + 1);
|
||||
for (auto enemy_type : enemy_types_for_rare_table_index(episode, rt_index)) {
|
||||
ret.enemy_specs[enemy_type].emplace_back(drop);
|
||||
}
|
||||
ret.rt_index_to_specs[z].emplace_back(drop);
|
||||
}
|
||||
for (const auto& drop : this->box_rares) {
|
||||
if ((drop.area_norm_plus_1 == 0) || drop.drop.data.empty()) {
|
||||
continue;
|
||||
}
|
||||
uint8_t area_norm = drop.area_norm_plus_1 - 1;
|
||||
if (area_norm >= ret.box_area_norm_to_specs.size()) {
|
||||
ret.box_area_norm_to_specs.resize(area_norm + 1);
|
||||
if (area_norm >= ret.box_specs.size()) {
|
||||
ret.box_specs.resize(area_norm + 1);
|
||||
}
|
||||
ret.box_area_norm_to_specs[area_norm].emplace_back(drop.drop);
|
||||
ret.box_specs[area_norm].emplace_back(drop.drop);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -241,8 +248,7 @@ RareItemSet::RareItemSet(const AFSArchive& afs, bool is_v1) {
|
||||
size_t index = static_cast<size_t>(difficulty) * 10 + section_id;
|
||||
ParsedRELData rel(afs.get_reader(index), false, is_v1);
|
||||
this->collections.emplace(
|
||||
this->key_for_params(mode, Episode::EP1, difficulty, section_id),
|
||||
rel.as_collection());
|
||||
this->key_for_params(mode, Episode::EP1, difficulty, section_id), rel.as_collection(Episode::EP1));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
@@ -250,7 +256,8 @@ RareItemSet::RareItemSet(const AFSArchive& afs, bool is_v1) {
|
||||
}
|
||||
}
|
||||
|
||||
string RareItemSet::gsl_entry_name_for_table(GameMode mode, Episode episode, Difficulty difficulty, uint8_t section_id) {
|
||||
string RareItemSet::gsl_entry_name_for_table(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t section_id) {
|
||||
return std::format("ItemRT{}{}{}{}.rel",
|
||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||
((episode == Episode::EP2) ? "l" : ""),
|
||||
@@ -259,7 +266,7 @@ string RareItemSet::gsl_entry_name_for_table(GameMode mode, Episode episode, Dif
|
||||
}
|
||||
|
||||
RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) {
|
||||
for (GameMode mode : ALL_GAME_MODES_V23) {
|
||||
for (GameMode mode : {GameMode::NORMAL, GameMode::CHALLENGE}) {
|
||||
for (Episode episode : ALL_EPISODES_V3) {
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
@@ -267,7 +274,7 @@ RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) {
|
||||
string filename = this->gsl_entry_name_for_table(mode, episode, difficulty, section_id);
|
||||
ParsedRELData rel(gsl.get_reader(filename), is_big_endian, false);
|
||||
this->collections.emplace(
|
||||
this->key_for_params(mode, episode, difficulty, section_id), rel.as_collection());
|
||||
this->key_for_params(mode, episode, difficulty, section_id), rel.as_collection(episode));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
@@ -287,7 +294,7 @@ RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) {
|
||||
size_t index = (ep_index * 40) + static_cast<size_t>(difficulty) * 10 + section_id;
|
||||
ParsedRELData rel(r.sub(0x280 * index, 0x280), is_big_endian, false);
|
||||
this->collections.emplace(
|
||||
this->key_for_params(GameMode::NORMAL, episode, difficulty, section_id), rel.as_collection());
|
||||
this->key_for_params(GameMode::NORMAL, episode, difficulty, section_id), rel.as_collection(episode));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
@@ -315,26 +322,19 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
||||
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 : section_id_it.second->as_dict()) {
|
||||
for (const auto& [enemy_type_name, specs_json] : section_id_it.second->as_dict()) {
|
||||
vector<ExpandedDrop>* target;
|
||||
if (item_it.first.starts_with("Box-")) {
|
||||
uint8_t area_norm = FloorDefinition::get(episode, item_it.first.substr(4)).drop_area_norm;
|
||||
if (collection.box_area_norm_to_specs.size() <= area_norm) {
|
||||
collection.box_area_norm_to_specs.resize(area_norm + 1);
|
||||
if (enemy_type_name.starts_with("Box-")) {
|
||||
uint8_t area_norm = FloorDefinition::get(episode, enemy_type_name.substr(4)).drop_area_norm;
|
||||
if (collection.box_specs.size() <= area_norm) {
|
||||
collection.box_specs.resize(area_norm + 1);
|
||||
}
|
||||
target = &collection.box_area_norm_to_specs[area_norm];
|
||||
target = &collection.box_specs[area_norm];
|
||||
} else {
|
||||
size_t rt_index = type_definition_for_enemy(phosg::enum_for_name<EnemyType>(item_it.first)).rt_index;
|
||||
if (rt_index == 0xFF) {
|
||||
throw runtime_error("enemy type " + item_it.first + " does not have an rt_index");
|
||||
}
|
||||
if (collection.rt_index_to_specs.size() <= rt_index) {
|
||||
collection.rt_index_to_specs.resize(rt_index + 1);
|
||||
}
|
||||
target = &collection.rt_index_to_specs[rt_index];
|
||||
target = &collection.enemy_specs[phosg::enum_for_name<EnemyType>(enemy_type_name)];
|
||||
}
|
||||
|
||||
for (const auto& spec_json : item_it.second->as_list()) {
|
||||
for (const auto& spec_json : specs_json->as_list()) {
|
||||
auto& d = target->emplace_back();
|
||||
|
||||
auto prob_desc = spec_json->at(0);
|
||||
@@ -702,7 +702,7 @@ string RareItemSet::serialize_html(
|
||||
std::string exact_token = std::format("Exact rate: {} / {}", frac.first, frac.second);
|
||||
if (common_item_set && type_def && type_def->rt_index != 0xFF) {
|
||||
auto table = common_item_set->get_table(episode, mode, difficulty, section_id);
|
||||
uint8_t dar = table->enemy_type_drop_probs.at(type_def->rt_index);
|
||||
uint8_t dar = table->enemy_type_drop_probs.at(type_def->type);
|
||||
exact_token += std::format(" (DAR: {}%)", dar);
|
||||
frac.first *= dar;
|
||||
frac.second *= 100;
|
||||
@@ -747,13 +747,9 @@ string RareItemSet::serialize_html(
|
||||
for (const auto& zone_type : zone_types) {
|
||||
add_location_header(zone_type.name);
|
||||
for (EnemyType type : zone_type.types) {
|
||||
uint8_t rt_index = type_definition_for_enemy(type).rt_index;
|
||||
if (rt_index == 0xFF) {
|
||||
continue;
|
||||
}
|
||||
array<vector<ExpandedDrop>, 10> specs_lists;
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
specs_lists[section_id] = this->get_enemy_specs(mode, episode, difficulty, section_id, rt_index);
|
||||
specs_lists[section_id] = this->get_enemy_specs(mode, episode, difficulty, section_id, type);
|
||||
}
|
||||
const auto& type_def = type_definition_for_enemy(type);
|
||||
const char* name = (difficulty == Difficulty::ULTIMATE && type_def.ultimate_name) ? type_def.ultimate_name : type_def.in_game_name;
|
||||
@@ -787,13 +783,9 @@ phosg::JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const
|
||||
auto section_id_dict = phosg::JSON::dict();
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
auto collection_dict = phosg::JSON::dict();
|
||||
for (size_t rt_index = 0; rt_index < 0x80; rt_index++) {
|
||||
const auto& enemy_types = enemy_types_for_rare_table_index(episode, rt_index);
|
||||
if (enemy_types.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& spec : this->get_enemy_specs(GameMode::NORMAL, episode, difficulty, section_id, rt_index)) {
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
const auto& specs = this->get_enemy_specs(GameMode::NORMAL, episode, difficulty, section_id, enemy_type);
|
||||
for (const auto& spec : specs) {
|
||||
if (spec.data.empty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -807,12 +799,8 @@ phosg::JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const
|
||||
if (name_index) {
|
||||
spec_json.emplace_back(name_index->describe_item(spec.data));
|
||||
}
|
||||
for (const auto& enemy_type : enemy_types) {
|
||||
if (type_definition_for_enemy(enemy_type).valid_in_episode(episode)) {
|
||||
phosg::JSON this_spec_json = spec_json;
|
||||
collection_dict.emplace(phosg::name_for_enum(enemy_type), phosg::JSON::list()).first->second->emplace_back(std::move(this_spec_json));
|
||||
}
|
||||
}
|
||||
auto list_emplace_ret = collection_dict.emplace(phosg::name_for_enum(enemy_type), phosg::JSON::list());
|
||||
list_emplace_ret.first->second->emplace_back(std::move(spec_json));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,17 +846,17 @@ phosg::JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const
|
||||
}
|
||||
|
||||
void RareItemSet::multiply_all_rates(double factor) {
|
||||
auto multiply_rates_vec = +[](vector<vector<ExpandedDrop>>& vec, double factor) -> void {
|
||||
for (auto& vec_it : vec) {
|
||||
for (auto& z_it : vec_it) {
|
||||
uint64_t new_probability = z_it.probability * factor;
|
||||
z_it.probability = min<uint64_t>(new_probability, 0xFFFFFFFF);
|
||||
for (auto& [_, collection] : this->collections) {
|
||||
for (auto& [_, specs] : collection.enemy_specs) {
|
||||
for (auto& spec : specs) {
|
||||
spec.probability = min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
for (auto& specs : collection.box_specs) {
|
||||
for (auto& spec : specs) {
|
||||
spec.probability = min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
};
|
||||
for (auto& coll_it : this->collections) {
|
||||
multiply_rates_vec(coll_it.second.rt_index_to_specs, factor);
|
||||
multiply_rates_vec(coll_it.second.box_area_norm_to_specs, factor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,28 +881,22 @@ void RareItemSet::print_collection(
|
||||
name_for_section_id(section_id));
|
||||
|
||||
phosg::fwrite_fmt(stream, " Monster rares:\n");
|
||||
for (size_t z = 0; z < collection->rt_index_to_specs.size(); z++) {
|
||||
string enemy_types_str;
|
||||
const auto& enemy_types = enemy_types_for_rare_table_index(episode, z);
|
||||
for (EnemyType enemy_type : enemy_types) {
|
||||
enemy_types_str += phosg::name_for_enum(enemy_type);
|
||||
enemy_types_str.push_back(',');
|
||||
}
|
||||
if (!enemy_types_str.empty()) {
|
||||
enemy_types_str.resize(enemy_types_str.size() - 1);
|
||||
}
|
||||
|
||||
for (const auto& spec : collection->rt_index_to_specs[z]) {
|
||||
string s = name_index ? spec.str(name_index) : spec.str();
|
||||
phosg::fwrite_fmt(stream, " {:02X}: {} ({})\n", z, s, enemy_types_str);
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
const auto& def = type_definition_for_enemy(enemy_type);
|
||||
for (const auto& spec : collection->enemy_specs.at(enemy_type)) {
|
||||
string s = name_index ? spec.str(name_index) : spec.str();
|
||||
phosg::fwrite_fmt(stream, " {:<23} {}\n", def.enum_name, s);
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
phosg::fwrite_fmt(stream, " Box rares:\n");
|
||||
for (size_t area_norm = 0; area_norm < collection->box_area_norm_to_specs.size(); area_norm++) {
|
||||
for (const auto& spec : collection->box_area_norm_to_specs[area_norm]) {
|
||||
for (size_t area_norm = 0; area_norm < collection->box_specs.size(); area_norm++) {
|
||||
for (const auto& spec : collection->box_specs[area_norm]) {
|
||||
string s = name_index ? spec.str(name_index) : spec.str();
|
||||
phosg::fwrite_fmt(stream, " (area-norm {:02X}) {}\n", area_norm, s);
|
||||
phosg::fwrite_fmt(stream, " (area-norm {:02X}) {}\n", area_norm, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -934,10 +916,100 @@ void RareItemSet::print_all_collections(FILE* stream, std::shared_ptr<const Item
|
||||
}
|
||||
}
|
||||
|
||||
void RareItemSet::SpecCollection::print_diff(FILE* stream, const SpecCollection& other) const {
|
||||
auto format_specs = [](const std::vector<ExpandedDrop>& specs) -> std::string {
|
||||
std::string ret;
|
||||
for (const auto& spec : specs) {
|
||||
if (!ret.empty()) {
|
||||
ret += ",";
|
||||
}
|
||||
ret += std::format("{:08X}:{}", spec.probability, spec.data.short_hex());
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const std::vector<ExpandedDrop> empty_specs{};
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
const std::vector<ExpandedDrop>* this_specs = &empty_specs;
|
||||
const std::vector<ExpandedDrop>* other_specs = &empty_specs;
|
||||
try {
|
||||
this_specs = &this->enemy_specs.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
other_specs = &other.enemy_specs.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
if (*this_specs != *other_specs) {
|
||||
phosg::fwrite_fmt(stream, " {}: {} -> {}\n",
|
||||
phosg::name_for_enum(enemy_type), format_specs(*this_specs), format_specs(*other_specs));
|
||||
}
|
||||
}
|
||||
for (size_t area_norm = 0; area_norm < 10; area_norm++) {
|
||||
const auto& this_specs = (area_norm < this->box_specs.size()) ? this->box_specs[area_norm] : empty_specs;
|
||||
const auto& other_specs = (area_norm < other.box_specs.size()) ? other.box_specs[area_norm] : empty_specs;
|
||||
if (this_specs != other_specs) {
|
||||
phosg::fwrite_fmt(stream, " Box (area_norm {}): {} -> {}\n",
|
||||
area_norm, format_specs(this_specs), format_specs(other_specs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RareItemSet::print_diff(FILE* stream, const RareItemSet& other) const {
|
||||
bool any_difference_found = false;
|
||||
for (const auto& episode : ALL_EPISODES_V4) {
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
for (const auto& difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
const SpecCollection* this_coll = nullptr;
|
||||
const SpecCollection* other_coll = nullptr;
|
||||
try {
|
||||
this_coll = &this->get_collection(mode, episode, difficulty, section_id);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
other_coll = &other.get_collection(mode, episode, difficulty, section_id);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
if (!this_coll && !other_coll) {
|
||||
continue;
|
||||
} else if (!this_coll) {
|
||||
any_difference_found = true;
|
||||
phosg::fwrite_fmt(stream, "> Collection present in other but not this: {} {} {} {}\n",
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
} else if (!other_coll) {
|
||||
any_difference_found = true;
|
||||
phosg::fwrite_fmt(stream, "> Collection present in this but not other: {} {} {} {}\n",
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
} else if (*this_coll != *other_coll) {
|
||||
any_difference_found = true;
|
||||
phosg::fwrite_fmt(stream, "> Collections do not match: {} {} {} {}\n",
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
this_coll->print_diff(stream, *other_coll);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!any_difference_found) {
|
||||
phosg::fwrite_fmt(stream, "> These rare item sets are identical\n");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_enemy_specs(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t rt_index) const {
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, EnemyType enemy_type) const {
|
||||
try {
|
||||
return this->get_collection(mode, episode, difficulty, secid).rt_index_to_specs.at(rt_index);
|
||||
return this->get_collection(mode, episode, difficulty, secid).enemy_specs.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
static const std::vector<ExpandedDrop> empty_vector;
|
||||
return empty_vector;
|
||||
@@ -947,7 +1019,7 @@ std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_enemy_specs(
|
||||
std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_box_specs(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t area_norm) const {
|
||||
try {
|
||||
return this->get_collection(mode, episode, difficulty, secid).box_area_norm_to_specs.at(area_norm);
|
||||
return this->get_collection(mode, episode, difficulty, secid).box_specs.at(area_norm);
|
||||
} catch (const out_of_range&) {
|
||||
static const std::vector<ExpandedDrop> empty_vector;
|
||||
return empty_vector;
|
||||
@@ -965,7 +1037,14 @@ bool RareItemSet::has_entries_for_game_config(GameMode mode, Episode episode, Di
|
||||
|
||||
const RareItemSet::SpecCollection& RareItemSet::get_collection(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) const {
|
||||
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid));
|
||||
try {
|
||||
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid));
|
||||
} catch (const out_of_range&) {
|
||||
if (mode == GameMode::BATTLE || mode == GameMode::SOLO) {
|
||||
return this->collections.at(this->key_for_params(GameMode::NORMAL, episode, difficulty, secid));
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) {
|
||||
|
||||
+15
-6
@@ -22,6 +22,9 @@ public:
|
||||
uint32_t probability = 0;
|
||||
ItemData data;
|
||||
|
||||
bool operator==(const ExpandedDrop& other) const = default;
|
||||
bool operator!=(const ExpandedDrop& other) const = default;
|
||||
|
||||
std::string str() const;
|
||||
std::string str(std::shared_ptr<const ItemNameIndex> name_index) const;
|
||||
};
|
||||
@@ -34,7 +37,7 @@ public:
|
||||
~RareItemSet() = default;
|
||||
|
||||
std::vector<ExpandedDrop> get_enemy_specs(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t rt_index) const;
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, EnemyType enemy_type) const;
|
||||
std::vector<ExpandedDrop> get_box_specs(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t area_norm) const;
|
||||
|
||||
@@ -60,11 +63,17 @@ public:
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
void print_all_collections(FILE* stream, std::shared_ptr<const ItemNameIndex> name_index = nullptr) const;
|
||||
void print_diff(FILE* stream, const RareItemSet& other) const;
|
||||
|
||||
protected:
|
||||
struct SpecCollection {
|
||||
std::vector<std::vector<ExpandedDrop>> rt_index_to_specs;
|
||||
std::vector<std::vector<ExpandedDrop>> box_area_norm_to_specs;
|
||||
std::unordered_map<EnemyType, std::vector<ExpandedDrop>> enemy_specs;
|
||||
std::vector<std::vector<ExpandedDrop>> box_specs; // Indexed by area_norm
|
||||
|
||||
bool operator==(const SpecCollection& other) const = default;
|
||||
bool operator!=(const SpecCollection& other) const = default;
|
||||
|
||||
void print_diff(FILE* stream, const SpecCollection& other) const;
|
||||
};
|
||||
|
||||
struct ParsedRELData {
|
||||
@@ -95,8 +104,8 @@ protected:
|
||||
ExpandedDrop drop;
|
||||
};
|
||||
|
||||
std::vector<ExpandedDrop> monster_rares;
|
||||
std::vector<BoxRare> box_rares;
|
||||
std::vector<ExpandedDrop> monster_rares; // Indexed by rt_index
|
||||
std::vector<BoxRare> box_rares; // Not indexed (area_norm + 1 is in the struct)
|
||||
|
||||
ParsedRELData() = default;
|
||||
ParsedRELData(phosg::StringReader r, bool big_endian, bool is_v1);
|
||||
@@ -108,7 +117,7 @@ protected:
|
||||
template <bool BE>
|
||||
std::string serialize_t(bool is_v1) const;
|
||||
|
||||
SpecCollection as_collection() const;
|
||||
SpecCollection as_collection(Episode episode) const;
|
||||
};
|
||||
|
||||
std::unordered_map<uint16_t, SpecCollection> collections;
|
||||
|
||||
+15
-14
@@ -2810,7 +2810,7 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
bool is_box = (cmd.rt_index == 0x30);
|
||||
|
||||
DropReconcileResult res;
|
||||
res.effective_rt_index = 0xFF;
|
||||
res.effective_enemy_type = EnemyType::UNKNOWN;
|
||||
res.should_drop = true;
|
||||
res.ignore_def = (cmd.ignore_def != 0);
|
||||
if (!map) {
|
||||
@@ -2856,22 +2856,22 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
res.ref_ene_st = map->enemy_state_for_index(version, cmd.floor, cmd.entity_index);
|
||||
res.target_ene_st = res.ref_ene_st->alias_target_ene_st ? res.ref_ene_st->alias_target_ene_st : res.ref_ene_st;
|
||||
uint8_t area = map->floor_to_area.at(res.target_ene_st->super_ene->floor);
|
||||
EnemyType type = res.target_ene_st->type(version, area, difficulty, event);
|
||||
res.effective_enemy_type = res.target_ene_st->type(version, area, difficulty, event);
|
||||
c->log.info_f("Drop check for E-{:03X} (target E-{:03X}, type {})",
|
||||
res.ref_ene_st->e_id, res.target_ene_st->e_id, phosg::name_for_enum(type));
|
||||
res.effective_rt_index = type_definition_for_enemy(type).rt_index;
|
||||
res.ref_ene_st->e_id, res.target_ene_st->e_id, phosg::name_for_enum(res.effective_enemy_type));
|
||||
uint8_t expected_rt_index = type_definition_for_enemy(res.effective_enemy_type).rt_index;
|
||||
bool mismatched_rt_index = false;
|
||||
if (cmd.rt_index != res.effective_rt_index) {
|
||||
if (cmd.rt_index != expected_rt_index) {
|
||||
// Special cases: BULCLAW => BULK and DARK_GUNNER => DEATH_GUNNER
|
||||
if (cmd.rt_index == 0x27 && type == EnemyType::BULCLAW) {
|
||||
if ((cmd.rt_index == 0x27) && (res.effective_enemy_type == EnemyType::BULCLAW)) {
|
||||
c->log.info_f("E-{:03X} killed as BULK instead of BULCLAW", res.target_ene_st->e_id);
|
||||
res.effective_rt_index = 0x27;
|
||||
} else if (cmd.rt_index == 0x23 && type == EnemyType::DARK_GUNNER) {
|
||||
res.effective_enemy_type = EnemyType::BULK;
|
||||
} else if ((cmd.rt_index == 0x23) && (res.effective_enemy_type == EnemyType::DARK_GUNNER)) {
|
||||
c->log.info_f("E-{:03X} killed as DEATH_GUNNER instead of DARK_GUNNER", res.target_ene_st->e_id);
|
||||
res.effective_rt_index = 0x23;
|
||||
res.effective_enemy_type = EnemyType::DEATH_GUNNER;
|
||||
} else {
|
||||
c->log.warning_f("rt_index {:02X} from command does not match entity\'s expected index {:02X}",
|
||||
cmd.rt_index, res.effective_rt_index);
|
||||
cmd.rt_index, expected_rt_index);
|
||||
mismatched_rt_index = true;
|
||||
}
|
||||
}
|
||||
@@ -2881,9 +2881,10 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
}
|
||||
if (c->check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
std::string rt_index_str = mismatched_rt_index
|
||||
? std::format(" $C4{:02X}->{:02X}$C5", cmd.rt_index, res.effective_rt_index)
|
||||
: std::format(" {:02X}", res.effective_rt_index);
|
||||
send_text_message_fmt(c, "$C5E-{:03X}{} {}", res.target_ene_st->e_id, rt_index_str, phosg::name_for_enum(type));
|
||||
? std::format(" $C4{:02X}->{:02X}$C5", cmd.rt_index, expected_rt_index)
|
||||
: std::format(" {:02X}", expected_rt_index);
|
||||
send_text_message_fmt(c, "$C5E-{:03X}{} {}",
|
||||
res.target_ene_st->e_id, rt_index_str, phosg::name_for_enum(res.effective_enemy_type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2963,7 +2964,7 @@ static asio::awaitable<void> on_entity_drop_item_request(shared_ptr<Client> c, S
|
||||
} else if (rec.target_ene_st) {
|
||||
l->log.info_f("Creating item from enemy {:04X} => E-{:03X} (area {:02X})",
|
||||
cmd.entity_index, rec.target_ene_st->e_id, cmd.effective_area);
|
||||
return l->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
|
||||
return l->item_creator->on_monster_item_drop(rec.effective_enemy_type, cmd.effective_area);
|
||||
} else {
|
||||
throw runtime_error("neither object nor enemy were present");
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct DropReconcileResult {
|
||||
// for drop computation (which may be the result of following an alias from the ref ene_st)
|
||||
std::shared_ptr<MapState::EnemyState> ref_ene_st;
|
||||
std::shared_ptr<MapState::EnemyState> target_ene_st;
|
||||
uint8_t effective_rt_index;
|
||||
EnemyType effective_enemy_type;
|
||||
bool should_drop;
|
||||
bool ignore_def;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user