add all BP indexes and fix incorrect RT indexes
This commit is contained in:
+56
-24
@@ -10,35 +10,67 @@
|
||||
using namespace std;
|
||||
|
||||
void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
||||
auto print_entry = [stream, episode](const PlayerStats& e, size_t z) {
|
||||
string names_str;
|
||||
for (auto type : enemy_types_for_battle_param_index(episode, z)) {
|
||||
if (!names_str.empty()) {
|
||||
names_str += ", ";
|
||||
}
|
||||
names_str += phosg::name_for_enum(type);
|
||||
}
|
||||
phosg::fwrite_fmt(stream,
|
||||
"{:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {}",
|
||||
e.char_stats.atp,
|
||||
e.char_stats.mst,
|
||||
e.char_stats.evp,
|
||||
e.char_stats.hp,
|
||||
e.char_stats.dfp,
|
||||
e.char_stats.ata,
|
||||
e.char_stats.lck,
|
||||
e.esp,
|
||||
e.experience,
|
||||
e.meseta,
|
||||
names_str);
|
||||
};
|
||||
|
||||
phosg::fwrite_fmt(stream, "========== STATS\n");
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
phosg::fwrite_fmt(stream, "{} ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF NAMES\n",
|
||||
abbreviation_for_difficulty(difficulty));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
const auto& e = this->stats[static_cast<size_t>(difficulty)][z];
|
||||
phosg::fwrite_fmt(stream, " {:02X} ", z);
|
||||
print_entry(this->stats[static_cast<size_t>(difficulty)][z], z);
|
||||
string names_str;
|
||||
for (auto type : enemy_types_for_battle_param_stats_index(episode, z)) {
|
||||
if (!names_str.empty()) {
|
||||
names_str += ", ";
|
||||
}
|
||||
names_str += phosg::name_for_enum(type);
|
||||
}
|
||||
phosg::fwrite_fmt(stream,
|
||||
"{:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {}",
|
||||
e.char_stats.atp, e.char_stats.mst, e.char_stats.evp, e.char_stats.hp, e.char_stats.dfp, e.char_stats.ata,
|
||||
e.char_stats.lck, e.esp, e.experience, e.meseta, names_str);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
|
||||
phosg::fwrite_fmt(stream, "========== ATTACK DATA\n");
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
phosg::fwrite_fmt(stream, "{} ZZ -A1- ATP- ATA+ -A4- -DIST-X- ANGLE-X- -DIST-Y- -A8- -A9- A10- A11- --A12--- --A13--- --A14--- --A15--- --A16---\n",
|
||||
abbreviation_for_difficulty(difficulty));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
const auto& e = this->attack_data[static_cast<size_t>(difficulty)][z];
|
||||
phosg::fwrite_fmt(stream,
|
||||
" {:02X} {:04X} {:04X} {:04X} {:04X} {:8.3f} {:08X} {:8.3f} {:04X} {:04X} {:04X} {:04X} {:08X} {:08X} {:08X} {:08X} {:08X}",
|
||||
z, e.unknown_a1, e.atp, e.ata_bonus, e.unknown_a4, e.distance_x, e.angle_x, e.distance_y, e.unknown_a8,
|
||||
e.unknown_a9, e.unknown_a10, e.unknown_a11, e.unknown_a12, e.unknown_a13, e.unknown_a14, e.unknown_a15,
|
||||
e.unknown_a16);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
|
||||
phosg::fwrite_fmt(stream, "========== RESIST DATA\n");
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
phosg::fwrite_fmt(stream, "{} ZZ EVP- EFR- EIC- ETH- ELT- EDK- ---A6--- ---A7--- ---A8--- ---A9--- --DFP---\n",
|
||||
abbreviation_for_difficulty(difficulty));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
const auto& e = this->resist_data[static_cast<size_t>(difficulty)][z];
|
||||
phosg::fwrite_fmt(stream,
|
||||
" {:02X} {:04X} {:04X} {:04X} {:04X} {:04X} {:04X} {:08X} {:08X} {:08X} {:08X} {:08X}",
|
||||
z, e.evp_bonus, e.efr, e.eic, e.eth, e.elt, e.edk, e.unknown_a6, e.unknown_a7, e.unknown_a8, e.unknown_a9,
|
||||
e.dfp_bonus);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
|
||||
phosg::fwrite_fmt(stream, "========== MOVEMENT DATA\n");
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
phosg::fwrite_fmt(stream, "{} ZZ IDLEMOVE IDLEANIM MOVE-SPD ANIM-SPD ---A1--- ---A2--- ---A3--- ---A4--- ---A5--- ---A6--- ---A7--- ---A8---\n",
|
||||
abbreviation_for_difficulty(difficulty));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
const auto& e = this->movement_data[static_cast<size_t>(difficulty)][z];
|
||||
phosg::fwrite_fmt(stream,
|
||||
" {:02X} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:08X} {:08X} {:08X} {:08X} {:08X} {:08X}",
|
||||
z, e.idle_move_speed, e.idle_animation_speed, e.move_speed, e.animation_speed, e.unknown_a1, e.unknown_a2,
|
||||
e.unknown_a3, e.unknown_a4, e.unknown_a5, e.unknown_a6, e.unknown_a7, e.unknown_a8);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
}
|
||||
|
||||
+232
-125
@@ -96,45 +96,87 @@ void from_json_into(const phosg::JSON& json, parray<parray<CommonItemSet::Table:
|
||||
}
|
||||
}
|
||||
|
||||
CommonItemSet::Table::Table(const phosg::JSON& json, Episode episode)
|
||||
CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg::JSON& json, Episode episode)
|
||||
: episode(episode) {
|
||||
from_json_into(json.at("BaseWeaponTypeProbTable"), this->base_weapon_type_prob_table);
|
||||
from_json_into(json.at("SubtypeBaseTable"), this->subtype_base_table);
|
||||
from_json_into(json.at("SubtypeAreaLengthTable"), this->subtype_area_length_table);
|
||||
from_json_into(json.at("GrindProbTable"), this->grind_prob_table);
|
||||
from_json_into(json.at("ArmorShieldTypeIndexProbTable"), this->armor_shield_type_index_prob_table);
|
||||
from_json_into(json.at("ArmorSlotCountProbTable"), this->armor_slot_count_prob_table);
|
||||
from_json_into(json.at("BoxMesetaRanges"), this->box_meseta_ranges);
|
||||
this->has_rare_bonus_value_prob_table = json.at("HasRareBonusValueProbTable").as_bool();
|
||||
from_json_into(json.at("BonusValueProbTable"), this->bonus_value_prob_table);
|
||||
from_json_into(json.at("NonRareBonusProbSpec"), this->nonrare_bonus_prob_spec);
|
||||
from_json_into(json.at("BonusTypeProbTable"), this->bonus_type_prob_table);
|
||||
from_json_into(json.at("SpecialMult"), this->special_mult);
|
||||
from_json_into(json.at("SpecialPercent"), this->special_percent);
|
||||
from_json_into(json.at("ToolClassProbTable"), this->tool_class_prob_table);
|
||||
from_json_into(json.at("TechniqueIndexProbTable"), this->technique_index_prob_table);
|
||||
from_json_into(json.at("TechniqueLevelRanges"), this->technique_level_ranges);
|
||||
this->armor_or_shield_type_bias = json.at("ArmorOrShieldTypeBias").as_int();
|
||||
from_json_into(json.at("UnitMaxStarsTable"), this->unit_max_stars_table);
|
||||
from_json_into(json.at("BoxItemClassProbTable"), this->box_item_class_prob_table);
|
||||
auto parse_field = [&]<typename T>(const std::string& key, T& field, const T* prev_field) {
|
||||
if (json.count(key)) {
|
||||
from_json_into(json.at(key), field);
|
||||
} else if (prev_field) {
|
||||
field = *prev_field;
|
||||
}
|
||||
};
|
||||
|
||||
const auto& enemy_meseta_ranges_json = json.at("EnemyMesetaRanges").as_dict();
|
||||
const auto& enemy_type_drop_probs_json = json.at("EnemyTypeDropProbs").as_dict();
|
||||
const auto& enemy_item_classes_json = json.at("EnemyItemClasses").as_dict();
|
||||
for (size_t z = 0; z < 0x64; z++) {
|
||||
auto types = enemy_types_for_rare_table_index(episode, z);
|
||||
parse_field("BaseWeaponTypeProbTable", this->base_weapon_type_prob_table, prev_table ? &prev_table->base_weapon_type_prob_table : nullptr);
|
||||
parse_field("SubtypeBaseTable", this->subtype_base_table, prev_table ? &prev_table->subtype_base_table : nullptr);
|
||||
parse_field("SubtypeAreaLengthTable", this->subtype_area_length_table, prev_table ? &prev_table->subtype_area_length_table : nullptr);
|
||||
parse_field("GrindProbTable", this->grind_prob_table, prev_table ? &prev_table->grind_prob_table : nullptr);
|
||||
parse_field("ArmorShieldTypeIndexProbTable", this->armor_shield_type_index_prob_table, prev_table ? &prev_table->armor_shield_type_index_prob_table : nullptr);
|
||||
parse_field("ArmorSlotCountProbTable", this->armor_slot_count_prob_table, prev_table ? &prev_table->armor_slot_count_prob_table : nullptr);
|
||||
parse_field("BoxMesetaRanges", this->box_meseta_ranges, prev_table ? &prev_table->box_meseta_ranges : nullptr);
|
||||
if (json.count("HasRareBonusValueProbTable")) {
|
||||
this->has_rare_bonus_value_prob_table = json.at("HasRareBonusValueProbTable").as_bool();
|
||||
} else if (prev_table) {
|
||||
this->has_rare_bonus_value_prob_table = prev_table->has_rare_bonus_value_prob_table;
|
||||
}
|
||||
parse_field("BonusValueProbTable", this->bonus_value_prob_table, prev_table ? &prev_table->bonus_value_prob_table : nullptr);
|
||||
parse_field("NonRareBonusProbSpec", this->nonrare_bonus_prob_spec, prev_table ? &prev_table->nonrare_bonus_prob_spec : nullptr);
|
||||
parse_field("BonusTypeProbTable", this->bonus_type_prob_table, prev_table ? &prev_table->bonus_type_prob_table : nullptr);
|
||||
parse_field("SpecialMult", this->special_mult, prev_table ? &prev_table->special_mult : nullptr);
|
||||
parse_field("SpecialPercent", this->special_percent, prev_table ? &prev_table->special_percent : nullptr);
|
||||
parse_field("ToolClassProbTable", this->tool_class_prob_table, prev_table ? &prev_table->tool_class_prob_table : nullptr);
|
||||
parse_field("TechniqueIndexProbTable", this->technique_index_prob_table, prev_table ? &prev_table->technique_index_prob_table : nullptr);
|
||||
parse_field("TechniqueLevelRanges", this->technique_level_ranges, prev_table ? &prev_table->technique_level_ranges : nullptr);
|
||||
if (json.count("ArmorOrShieldTypeBias")) {
|
||||
this->armor_or_shield_type_bias = json.at("ArmorOrShieldTypeBias").as_int();
|
||||
} else if (prev_table) {
|
||||
this->armor_or_shield_type_bias = prev_table->armor_or_shield_type_bias;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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}", abbreviation_for_episode(episode), z));
|
||||
names.emplace_back(std::format("!{:02X}", z));
|
||||
} else {
|
||||
for (auto type : types) {
|
||||
names.emplace_back(std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type)));
|
||||
names.emplace_back(phosg::name_for_enum(type));
|
||||
}
|
||||
}
|
||||
for (const auto& name : names) {
|
||||
from_json_into(*enemy_meseta_ranges_json.at(name), this->enemy_meseta_ranges[z]);
|
||||
this->enemy_type_drop_probs[z] = enemy_type_drop_probs_json.at(name)->as_int();
|
||||
this->enemy_item_classes[z] = enemy_item_classes_json.at(name)->as_int();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,7 +208,7 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
const auto& item_classes = this->enemy_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 < 0x64; z++) {
|
||||
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()) {
|
||||
@@ -174,12 +216,12 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
}
|
||||
enemies_str += phosg::name_for_enum(enemy_type);
|
||||
}
|
||||
if (drop_probs[z]) {
|
||||
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, enemies_str);
|
||||
phosg::fwrite_fmt(stream, " {:02X} ----- ----- 0% --\n", z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,89 +495,142 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
||||
}
|
||||
}
|
||||
|
||||
phosg::JSON CommonItemSet::Table::json() const {
|
||||
phosg::JSON enemy_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 < 0x64; z++) {
|
||||
for (Episode episode : ALL_EPISODES_V4) {
|
||||
auto types = enemy_types_for_rare_table_index(episode, z);
|
||||
phosg::JSON CommonItemSet::Table::json(std::shared_ptr<const Table> prev_table) const {
|
||||
auto ret = phosg::JSON::dict();
|
||||
|
||||
if (!prev_table || (this->base_weapon_type_prob_table != prev_table->base_weapon_type_prob_table)) {
|
||||
ret.emplace("BaseWeaponTypeProbTable", to_json(this->base_weapon_type_prob_table));
|
||||
}
|
||||
if (!prev_table || (this->subtype_base_table != prev_table->subtype_base_table)) {
|
||||
ret.emplace("SubtypeBaseTable", to_json(this->subtype_base_table));
|
||||
}
|
||||
if (!prev_table || (this->subtype_area_length_table != prev_table->subtype_area_length_table)) {
|
||||
ret.emplace("SubtypeAreaLengthTable", to_json(this->subtype_area_length_table));
|
||||
}
|
||||
if (!prev_table || (this->grind_prob_table != prev_table->grind_prob_table)) {
|
||||
ret.emplace("GrindProbTable", to_json(this->grind_prob_table));
|
||||
}
|
||||
if (!prev_table || (this->armor_shield_type_index_prob_table != prev_table->armor_shield_type_index_prob_table)) {
|
||||
ret.emplace("ArmorShieldTypeIndexProbTable", to_json(this->armor_shield_type_index_prob_table));
|
||||
}
|
||||
if (!prev_table || (this->armor_slot_count_prob_table != prev_table->armor_slot_count_prob_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();
|
||||
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}", abbreviation_for_episode(episode), z));
|
||||
names.emplace_back(std::format("!{:02X}", z));
|
||||
} else {
|
||||
for (auto type : types) {
|
||||
names.emplace_back(std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type)));
|
||||
names.emplace_back(phosg::name_for_enum(type));
|
||||
}
|
||||
}
|
||||
for (const auto& name : names) {
|
||||
enemy_meseta_ranges_json.emplace(name, to_json(this->enemy_meseta_ranges[z]));
|
||||
enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs[z]);
|
||||
enemy_item_classes_json.emplace(name, this->enemy_item_classes[z]);
|
||||
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 && (!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_meseta_ranges) {
|
||||
ret.emplace("EnemyMesetaRanges", std::move(enemy_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));
|
||||
}
|
||||
}
|
||||
return phosg::JSON::dict({
|
||||
{"BaseWeaponTypeProbTable", to_json(this->base_weapon_type_prob_table)},
|
||||
{"SubtypeBaseTable", to_json(this->subtype_base_table)},
|
||||
{"SubtypeAreaLengthTable", to_json(this->subtype_area_length_table)},
|
||||
{"GrindProbTable", to_json(this->grind_prob_table)},
|
||||
{"ArmorShieldTypeIndexProbTable", to_json(this->armor_shield_type_index_prob_table)},
|
||||
{"ArmorSlotCountProbTable", to_json(this->armor_slot_count_prob_table)},
|
||||
{"EnemyMesetaRanges", std::move(enemy_meseta_ranges_json)},
|
||||
{"EnemyTypeDropProbs", std::move(enemy_type_drop_probs_json)},
|
||||
{"EnemyItemClasses", std::move(enemy_item_classes_json)},
|
||||
{"BoxMesetaRanges", to_json(this->box_meseta_ranges)},
|
||||
{"HasRareBonusValueProbTable", this->has_rare_bonus_value_prob_table},
|
||||
{"BonusValueProbTable", to_json(this->bonus_value_prob_table)},
|
||||
{"NonRareBonusProbSpec", to_json(this->nonrare_bonus_prob_spec)},
|
||||
{"BonusTypeProbTable", to_json(this->bonus_type_prob_table)},
|
||||
{"SpecialMult", to_json(this->special_mult)},
|
||||
{"SpecialPercent", to_json(this->special_percent)},
|
||||
{"ToolClassProbTable", to_json(this->tool_class_prob_table)},
|
||||
{"TechniqueIndexProbTable", to_json(this->technique_index_prob_table)},
|
||||
{"TechniqueLevelRanges", to_json(this->technique_level_ranges)},
|
||||
{"ArmorOrShieldTypeBias", this->armor_or_shield_type_bias},
|
||||
{"UnitMaxStarsTable", to_json(this->unit_max_stars_table)},
|
||||
{"BoxItemClassProbTable", to_json(this->box_item_class_prob_table)},
|
||||
});
|
||||
|
||||
if (!prev_table || (this->box_meseta_ranges != prev_table->box_meseta_ranges)) {
|
||||
ret.emplace("BoxMesetaRanges", to_json(this->box_meseta_ranges));
|
||||
}
|
||||
if (!prev_table || (this->has_rare_bonus_value_prob_table != prev_table->has_rare_bonus_value_prob_table)) {
|
||||
ret.emplace("HasRareBonusValueProbTable", this->has_rare_bonus_value_prob_table);
|
||||
}
|
||||
if (!prev_table || (this->bonus_value_prob_table != prev_table->bonus_value_prob_table)) {
|
||||
ret.emplace("BonusValueProbTable", to_json(this->bonus_value_prob_table));
|
||||
}
|
||||
if (!prev_table || (this->nonrare_bonus_prob_spec != prev_table->nonrare_bonus_prob_spec)) {
|
||||
ret.emplace("NonRareBonusProbSpec", to_json(this->nonrare_bonus_prob_spec));
|
||||
}
|
||||
if (!prev_table || (this->bonus_type_prob_table != prev_table->bonus_type_prob_table)) {
|
||||
ret.emplace("BonusTypeProbTable", to_json(this->bonus_type_prob_table));
|
||||
}
|
||||
if (!prev_table || (this->special_mult != prev_table->special_mult)) {
|
||||
ret.emplace("SpecialMult", to_json(this->special_mult));
|
||||
}
|
||||
if (!prev_table || (this->special_percent != prev_table->special_percent)) {
|
||||
ret.emplace("SpecialPercent", to_json(this->special_percent));
|
||||
}
|
||||
if (!prev_table || (this->tool_class_prob_table != prev_table->tool_class_prob_table)) {
|
||||
ret.emplace("ToolClassProbTable", to_json(this->tool_class_prob_table));
|
||||
}
|
||||
if (!prev_table || (this->technique_index_prob_table != prev_table->technique_index_prob_table)) {
|
||||
ret.emplace("TechniqueIndexProbTable", to_json(this->technique_index_prob_table));
|
||||
}
|
||||
if (!prev_table || (this->technique_level_ranges != prev_table->technique_level_ranges)) {
|
||||
ret.emplace("TechniqueLevelRanges", to_json(this->technique_level_ranges));
|
||||
}
|
||||
if (!prev_table || (this->armor_or_shield_type_bias != prev_table->armor_or_shield_type_bias)) {
|
||||
ret.emplace("ArmorOrShieldTypeBias", this->armor_or_shield_type_bias);
|
||||
}
|
||||
if (!prev_table || (this->unit_max_stars_table != prev_table->unit_max_stars_table)) {
|
||||
ret.emplace("UnitMaxStarsTable", to_json(this->unit_max_stars_table));
|
||||
}
|
||||
if (!prev_table || (this->box_item_class_prob_table != prev_table->box_item_class_prob_table)) {
|
||||
ret.emplace("BoxItemClassProbTable", to_json(this->box_item_class_prob_table));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
phosg::JSON CommonItemSet::json() const {
|
||||
auto modes_dict = phosg::JSON::dict();
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
auto episodes_dict = phosg::JSON::dict();
|
||||
for (const auto& episode : ALL_EPISODES_V4) {
|
||||
auto difficulty_dict = phosg::JSON::dict();
|
||||
auto ret = phosg::JSON::dict();
|
||||
for (const auto& episode : ALL_EPISODES_V4) {
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
for (const auto& difficulty : ALL_DIFFICULTIES_V234) {
|
||||
auto section_id_dict = phosg::JSON::dict();
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
auto json_key = this->json_key_for_table(episode, mode, difficulty, section_id);
|
||||
try {
|
||||
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
||||
auto table = this->get_table(episode, mode, difficulty, section_id);
|
||||
section_id_dict.emplace(name_for_section_id(section_id), table->json());
|
||||
ret.emplace(json_key, table->json(prev_table));
|
||||
} catch (const runtime_error&) {
|
||||
}
|
||||
}
|
||||
difficulty_dict.emplace(token_name_for_difficulty(difficulty), std::move(section_id_dict));
|
||||
}
|
||||
episodes_dict.emplace(token_name_for_episode(episode), std::move(difficulty_dict));
|
||||
}
|
||||
modes_dict.emplace(name_for_mode(mode), std::move(episodes_dict));
|
||||
}
|
||||
|
||||
return modes_dict;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CommonItemSet::print(FILE* stream) const {
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
for (const auto& episode : ALL_EPISODES_V4) {
|
||||
for (const auto& episode : ALL_EPISODES_V4) {
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
auto table = this->get_table(episode, mode, difficulty, section_id);
|
||||
phosg::fwrite_fmt(stream, "============ {} {} {} {}\n",
|
||||
name_for_mode(mode),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
table->print(stream);
|
||||
@@ -548,8 +643,8 @@ void CommonItemSet::print(FILE* stream) const {
|
||||
}
|
||||
|
||||
void CommonItemSet::print_diff(FILE* stream, const CommonItemSet& other) const {
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
for (const auto& episode : ALL_EPISODES_V4) {
|
||||
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++) {
|
||||
shared_ptr<const Table> this_table;
|
||||
@@ -567,20 +662,20 @@ void CommonItemSet::print_diff(FILE* stream, const CommonItemSet& other) const {
|
||||
continue;
|
||||
} else if (!this_table) {
|
||||
phosg::fwrite_fmt(stream, "> Table present in other but not this: {} {} {} {}\n",
|
||||
name_for_mode(mode),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
} else if (!other_table) {
|
||||
phosg::fwrite_fmt(stream, "> Table present in this but not other: {} {} {} {}\n",
|
||||
name_for_mode(mode),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
} else if (*this_table != *other_table) {
|
||||
phosg::fwrite_fmt(stream, "> Tables do not match: {} {} {} {}\n",
|
||||
name_for_mode(mode),
|
||||
name_for_episode(episode),
|
||||
name_for_mode(mode),
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
this_table->print_diff(stream, *other_table);
|
||||
@@ -610,12 +705,13 @@ 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>>, 0x64>>(offsets.enemy_meseta_ranges_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};
|
||||
}
|
||||
this->enemy_type_drop_probs = r.pget<parray<uint8_t, 0x64>>(offsets.enemy_type_drop_probs_offset);
|
||||
this->enemy_item_classes = r.pget<parray<uint8_t, 0x64>>(offsets.enemy_item_classes_offset);
|
||||
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++) {
|
||||
@@ -665,13 +761,37 @@ uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, Difficulty
|
||||
(static_cast<uint16_t>(secid) & 0x000F));
|
||||
}
|
||||
|
||||
std::string CommonItemSet::json_key_for_table(
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) {
|
||||
return std::format("{}:{}:{}:{}", abbreviation_for_episode(episode), name_for_mode(mode),
|
||||
token_name_for_difficulty(difficulty), name_for_section_id(section_id));
|
||||
}
|
||||
|
||||
shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t secid) const {
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
||||
try {
|
||||
return this->tables.at(this->key_for_table(episode, mode, difficulty, secid));
|
||||
return this->tables.at(this->key_for_table(episode, mode, difficulty, section_id));
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}",
|
||||
name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), secid));
|
||||
name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), section_id));
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const CommonItemSet::Table> CommonItemSet::get_prev_table(
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
||||
if (section_id != 0) {
|
||||
// All section IDs are based on the previous, except Viridia
|
||||
return this->get_table(episode, mode, difficulty, section_id - 1);
|
||||
} else if (difficulty != Difficulty::NORMAL) {
|
||||
// All Viridia tables are based on the previous difficulty, except Normal
|
||||
auto prev_difficulty = static_cast<Difficulty>(static_cast<uint8_t>(difficulty) - 1);
|
||||
return this->get_table(episode, mode, prev_difficulty, 0);
|
||||
} else if (mode != GameMode::NORMAL) {
|
||||
// All Normal Viridia tables are based on the Normal game mode, except Normal itself
|
||||
return this->get_table(episode, GameMode::NORMAL, Difficulty::NORMAL, 0);
|
||||
} else {
|
||||
// There's no previous table
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,37 +915,27 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
}
|
||||
|
||||
JSONCommonItemSet::JSONCommonItemSet(const phosg::JSON& 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, Difficulty> difficulty_keys(
|
||||
{{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}});
|
||||
Difficulty 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);
|
||||
this->tables.emplace(
|
||||
this->key_for_table(episode, mode, difficulty, section_id),
|
||||
make_shared<Table>(*section_id_it.second, episode));
|
||||
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++) {
|
||||
try {
|
||||
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
||||
auto json_key = this->json_key_for_table(episode, mode, difficulty, section_id);
|
||||
auto key = this->key_for_table(episode, mode, difficulty, section_id);
|
||||
this->tables.emplace(key, make_shared<Table>(prev_table, json.at(json_key), episode));
|
||||
} catch (const runtime_error&) {
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RELFileSet::RELFileSet(std::shared_ptr<const std::string> data)
|
||||
: data(data), r(*this->data) {}
|
||||
RELFileSet::RELFileSet(std::shared_ptr<const std::string> data) : data(data), r(*this->data) {}
|
||||
|
||||
ArmorRandomSet::ArmorRandomSet(std::shared_ptr<const std::string> data)
|
||||
: RELFileSet(data) {
|
||||
ArmorRandomSet::ArmorRandomSet(std::shared_ptr<const std::string> data) : RELFileSet(data) {
|
||||
// For some reason the footer tables are doubly indirect in this file
|
||||
uint32_t specs_offset_offset = this->r.pget_u32b(data->size() - 0x10);
|
||||
uint32_t specs_offset = this->r.pget_u32b(specs_offset_offset);
|
||||
@@ -847,8 +957,7 @@ ArmorRandomSet::get_unit_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(this->tables->at(2), index);
|
||||
}
|
||||
|
||||
ToolRandomSet::ToolRandomSet(std::shared_ptr<const std::string> data)
|
||||
: RELFileSet(data) {
|
||||
ToolRandomSet::ToolRandomSet(std::shared_ptr<const std::string> data) : RELFileSet(data) {
|
||||
uint32_t specs_offset = r.pget_u32b(data->size() - 0x10);
|
||||
this->common_recovery_table_spec = &r.pget<TableSpec>(r.pget_u32b(specs_offset));
|
||||
this->rare_recovery_table_spec = &r.pget<TableSpec>(r.pget_u32b(specs_offset + sizeof(uint32_t)), 2 * sizeof(TableSpec));
|
||||
@@ -875,8 +984,7 @@ ToolRandomSet::get_tech_disk_level_table(size_t index) const {
|
||||
return this->get_table<TechDiskLevelEntry>(*this->tech_disk_level_table_spec, index);
|
||||
}
|
||||
|
||||
WeaponRandomSet::WeaponRandomSet(std::shared_ptr<const std::string> data)
|
||||
: RELFileSet(data) {
|
||||
WeaponRandomSet::WeaponRandomSet(std::shared_ptr<const std::string> data) : RELFileSet(data) {
|
||||
uint32_t offsets_offset = this->r.pget_u32b(data->size() - 0x10);
|
||||
this->offsets = &this->r.pget<Offsets>(offsets_offset);
|
||||
}
|
||||
@@ -916,8 +1024,7 @@ WeaponRandomSet::get_favored_grind_range(size_t index) const {
|
||||
return &this->r.pget<RangeTableEntry>(this->offsets->favored_grind_range_table + sizeof(RangeTableEntry) * index);
|
||||
}
|
||||
|
||||
TekkerAdjustmentSet::TekkerAdjustmentSet(std::shared_ptr<const std::string> data)
|
||||
: data(data), r(*data) {
|
||||
TekkerAdjustmentSet::TekkerAdjustmentSet(std::shared_ptr<const std::string> data) : data(data), r(*data) {
|
||||
this->offsets = &this->r.pget<Offsets>(this->r.pget_u32b(this->r.size() - 0x10));
|
||||
}
|
||||
|
||||
|
||||
+22
-12
@@ -4,6 +4,7 @@
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/JSON.hh>
|
||||
|
||||
#include "EnemyType.hh"
|
||||
#include "GSLArchive.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
@@ -15,7 +16,7 @@ public:
|
||||
class Table {
|
||||
public:
|
||||
Table() = delete;
|
||||
Table(const phosg::JSON& json, Episode episode);
|
||||
Table(std::shared_ptr<const Table> prev_table, const phosg::JSON& json, Episode episode);
|
||||
Table(const phosg::StringReader& r, bool big_endian, bool is_v3, Episode episode);
|
||||
|
||||
bool operator==(const Table& other) const = default;
|
||||
@@ -23,11 +24,15 @@ public:
|
||||
|
||||
template <typename IntT>
|
||||
struct Range {
|
||||
IntT min;
|
||||
IntT max;
|
||||
IntT min = 0;
|
||||
IntT max = 0;
|
||||
|
||||
bool operator==(const Range& other) const = default;
|
||||
bool operator!=(const Range& other) const = default;
|
||||
|
||||
inline bool empty() const {
|
||||
return ((this->min | this->max) == 0);
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
Episode episode;
|
||||
@@ -37,9 +42,9 @@ 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>, 0x64> enemy_meseta_ranges;
|
||||
parray<uint8_t, 0x64> enemy_type_drop_probs;
|
||||
parray<uint8_t, 0x64> enemy_item_classes;
|
||||
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;
|
||||
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;
|
||||
@@ -54,7 +59,7 @@ public:
|
||||
parray<uint8_t, 0x0A> unit_max_stars_table;
|
||||
parray<parray<uint8_t, 10>, 7> box_item_class_prob_table;
|
||||
|
||||
phosg::JSON json() const;
|
||||
phosg::JSON json(std::shared_ptr<const Table> prev_table) const;
|
||||
void print(FILE* stream) const;
|
||||
void print_diff(FILE* stream, const Table& other) const;
|
||||
|
||||
@@ -122,13 +127,13 @@ public:
|
||||
/* 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.
|
||||
// V2/V3: -> parray<Range<U16T>, 0x64>
|
||||
// V2/V3: -> parray<Range<U16T>, NUM_RT_INDEXES_V3>
|
||||
/* 18 */ U32T<BE> enemy_meseta_ranges_offset;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the percent chance that the enemy drops anything at
|
||||
// all. (This check is done before the rare drop check, so the chance of getting a rare item from an enemy is
|
||||
// essentially this probability multiplied by the rare drop rate.)
|
||||
// V2/V3: -> parray<uint8_t, 0x64>
|
||||
// V2/V3: -> parray<uint8_t, NUM_RT_INDEXES_V3>
|
||||
/* 1C */ U32T<BE> enemy_type_drop_probs_offset;
|
||||
|
||||
// Each byte in this table (indexed by enemy_type) represents the class of item that can drop. The values are:
|
||||
@@ -139,7 +144,7 @@ public:
|
||||
// 04 = tool
|
||||
// 05 = meseta
|
||||
// Anything else = no item
|
||||
// V2/V3: -> parray<uint8_t, 0x64>
|
||||
// V2/V3: -> parray<uint8_t, NUM_RT_INDEXES_V3>
|
||||
/* 20 */ U32T<BE> enemy_item_classes_offset;
|
||||
|
||||
// This table (indexed by area - 1) specifies the ranges of meseta values that can drop from boxes.
|
||||
@@ -248,7 +253,11 @@ public:
|
||||
bool operator==(const CommonItemSet& other) const = default;
|
||||
bool operator!=(const CommonItemSet& other) const = default;
|
||||
|
||||
std::shared_ptr<const Table> get_table(Episode episode, GameMode mode, Difficulty difficulty, uint8_t secid) const;
|
||||
std::shared_ptr<const Table> get_table(
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const;
|
||||
std::shared_ptr<const Table> get_prev_table(
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const;
|
||||
|
||||
phosg::JSON json() const;
|
||||
void print(FILE* stream) const;
|
||||
void print_diff(FILE* stream, const CommonItemSet& other) const;
|
||||
@@ -256,7 +265,8 @@ public:
|
||||
protected:
|
||||
CommonItemSet() = default;
|
||||
|
||||
static uint16_t key_for_table(Episode episode, GameMode mode, Difficulty difficulty, uint8_t secid);
|
||||
static uint16_t key_for_table(Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id);
|
||||
static std::string json_key_for_table(Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id);
|
||||
|
||||
std::unordered_map<uint16_t, std::shared_ptr<Table>> tables;
|
||||
};
|
||||
|
||||
+154
-155
@@ -15,138 +15,143 @@ static constexpr uint8_t EP4 = EnemyTypeDefinition::Flag::VALID_EP4;
|
||||
static constexpr uint8_t RARE = EnemyTypeDefinition::Flag::IS_RARE;
|
||||
static constexpr uint8_t BOSS = EnemyTypeDefinition::Flag::IS_BOSS;
|
||||
|
||||
static constexpr uint8_t NONE = 0xFF;
|
||||
static const vector<EnemyTypeDefinition> type_defs{
|
||||
// clang-format off
|
||||
// TYPE FLAGS RT BP ENUM NAME IN-GAME NAME ULTIMATE NAME
|
||||
{EnemyType::UNKNOWN, 0, 0xFF, 0xFF, "UNKNOWN", "__UNKNOWN__", nullptr},
|
||||
{EnemyType::NONE, 0, 0xFF, 0xFF, "NONE", "__NONE__", nullptr},
|
||||
{EnemyType::NON_ENEMY_NPC, EP1 | EP2 | EP4, 0xFF, 0xFF, "NON_ENEMY_NPC", "__NPC__", nullptr},
|
||||
{EnemyType::AL_RAPPY, EP1 | RARE, 0x06, 0x19, "AL_RAPPY", "Al Rappy", "Pal Rappy"},
|
||||
{EnemyType::ASTARK, EP4, 0x41, 0x09, "ASTARK", "Astark", nullptr},
|
||||
{EnemyType::BA_BOOTA, EP4, 0x4F, 0x03, "BA_BOOTA", "Ba Boota", nullptr},
|
||||
{EnemyType::BARBA_RAY, EP2 | BOSS, 0x49, 0x0F, "BARBA_RAY", "Barba Ray", nullptr},
|
||||
{EnemyType::BARBAROUS_WOLF, EP1 | EP2, 0x08, 0x03, "BARBAROUS_WOLF", "Barbarous Wolf", "Gulgus-gue"},
|
||||
{EnemyType::BEE_L, EP1 | EP2, 0xFF, 0xFF, "BEE_L", "Bee L", "Gee L"},
|
||||
{EnemyType::BEE_R, EP1 | EP2, 0xFF, 0xFF, "BEE_R", "Bee R", "Gee R"},
|
||||
{EnemyType::BOOMA, EP1, 0x09, 0x4B, "BOOMA", "Booma", "Bartle"},
|
||||
{EnemyType::BOOTA, EP4, 0x4D, 0x00, "BOOTA", "Boota", nullptr},
|
||||
{EnemyType::BULCLAW, EP1, 0x28, 0x1F, "BULCLAW", "Bulclaw", nullptr},
|
||||
{EnemyType::BULK, EP1, 0x27, 0x1F, "BULK", "Bulk", nullptr},
|
||||
{EnemyType::CANADINE, EP1, 0x1C, 0x07, "CANADINE", "Canadine", "Canabin"},
|
||||
{EnemyType::CANADINE_GROUP, EP1, 0x1C, 0x08, "CANADINE_GROUP", "Canadine (group)", "Canabin (group)"},
|
||||
{EnemyType::CANANE, EP1, 0x1D, 0x09, "CANANE", "Canane", "Canune"},
|
||||
{EnemyType::CHAOS_BRINGER, EP1, 0x24, 0x0D, "CHAOS_BRINGER", "Chaos Bringer", "Dark Bringer"},
|
||||
{EnemyType::CHAOS_SORCERER, EP1 | EP2, 0x1F, 0x0A, "CHAOS_SORCERER", "Chaos Sorceror", "Gran Sorceror"},
|
||||
{EnemyType::CLAW, EP1, 0x26, 0x20, "CLAW", "Claw", nullptr},
|
||||
{EnemyType::DARK_BELRA, EP1 | EP2, 0x25, 0x0E, "DARK_BELRA", "Dark Belra", "Indi Belra"},
|
||||
{EnemyType::DARK_FALZ_1, EP1 | BOSS, 0xFF, 0x36, "DARK_FALZ_1", "Dark Falz (phase 1)", nullptr},
|
||||
{EnemyType::DARK_FALZ_2, EP1 | BOSS, 0x2F, 0x37, "DARK_FALZ_2", "Dark Falz (phase 2)", nullptr},
|
||||
{EnemyType::DARK_FALZ_3, EP1 | BOSS, 0x2F, 0x38, "DARK_FALZ_3", "Dark Falz (phase 3)", nullptr},
|
||||
{EnemyType::DARK_GUNNER, EP1, 0x22, 0x1E, "DARK_GUNNER", "Dark Gunner", nullptr},
|
||||
{EnemyType::DARK_GUNNER_CONTROL, EP1, 0xFF, 0xFF, "DARK_GUNNER_CONTROL", "Dark Gunner (control)", nullptr},
|
||||
{EnemyType::DARVANT, EP1, 0xFF, 0x35, "DARVANT", "Darvant", nullptr},
|
||||
{EnemyType::DARVANT_ULTIMATE, EP1, 0xFF, 0x39, "DARVANT_ULTIMATE", "Darvant (ultimate)", nullptr},
|
||||
{EnemyType::DE_ROL_LE, EP1 | BOSS, 0x2D, 0x0F, "DE_ROL_LE", "De Rol Le", "Dal Ral Lie"},
|
||||
{EnemyType::DE_ROL_LE_BODY, EP1 | BOSS, 0xFF, 0xFF, "DE_ROL_LE_BODY", "De Rol Le (body)", "Dal Ral Lie (body)"},
|
||||
{EnemyType::DE_ROL_LE_MINE, EP1 | BOSS, 0xFF, 0xFF, "DE_ROL_LE_MINE", "De Rol Le (mine)", "Dal Ral Lie (mine)"},
|
||||
{EnemyType::DEATH_GUNNER, EP1, 0x23, 0x1E, "DEATH_GUNNER", "Death Gunner", nullptr},
|
||||
{EnemyType::DEL_LILY, EP2, 0x53, 0x25, "DEL_LILY", "Del Lily", nullptr},
|
||||
{EnemyType::DEL_RAPPY_CRATER, EP4, 0x57, 0x06, "DEL_RAPPY_CRATER", "Del Rappy (crater)", nullptr},
|
||||
{EnemyType::DEL_RAPPY_DESERT, EP4, 0x58, 0x18, "DEL_RAPPY_DESERT", "Del Rappy (desert)", nullptr},
|
||||
{EnemyType::DELBITER, EP2, 0x48, 0x0D, "DELBITER", "Delbiter", nullptr},
|
||||
{EnemyType::DELDEPTH, EP2, 0x47, 0x30, "DELDEPTH", "Deldepth", nullptr},
|
||||
{EnemyType::DELSABER, EP1 | EP2, 0x1E, 0x52, "DELSABER", "Delsaber", nullptr},
|
||||
{EnemyType::DIMENIAN, EP1 | EP2, 0x29, 0x53, "DIMENIAN", "Dimenian", "Arlan"},
|
||||
{EnemyType::DOLMDARL, EP2, 0x41, 0x50, "DOLMDARL", "Dolmdarl", nullptr},
|
||||
{EnemyType::DOLMOLM, EP2, 0x40, 0x4F, "DOLMOLM", "Dolmolm", nullptr},
|
||||
{EnemyType::DORPHON, EP4, 0x50, 0x0F, "DORPHON", "Dorphon", nullptr},
|
||||
{EnemyType::DORPHON_ECLAIR, EP4 | RARE, 0x51, 0x10, "DORPHON_ECLAIR", "Dorphon Eclair", nullptr},
|
||||
{EnemyType::DRAGON, EP1 | BOSS, 0x2C, 0x12, "DRAGON", "Dragon", "Sil Dragon"},
|
||||
{EnemyType::DUBCHIC, EP1 | EP2, 0x18, 0x1B, "DUBCHIC", "Dubchic", "Dubchich"},
|
||||
{EnemyType::DUBWITCH, EP1 | EP2, 0xFF, 0xFF, "DUBWITCH", "Dubwitch", "Duvuik"},
|
||||
{EnemyType::EGG_RAPPY, EP2, 0x51, 0x19, "EGG_RAPPY", "Egg Rappy", nullptr},
|
||||
{EnemyType::EPSIGARD, EP2, 0xFF, 0xFF, "EPSIGARD", "Episgard", nullptr},
|
||||
{EnemyType::EPSILON, EP2, 0x54, 0x23, "EPSILON", "Epsilon", nullptr},
|
||||
{EnemyType::EVIL_SHARK, EP1, 0x10, 0x4F, "EVIL_SHARK", "Evil Shark", "Vulmer"},
|
||||
{EnemyType::GAEL_OR_GIEL, EP2, 0xFF, 0x2E, "GAEL", "Gael/Giel", nullptr},
|
||||
{EnemyType::GAL_GRYPHON, EP2 | BOSS, 0x4D, 0x1E, "GAL_GRYPHON", "Gal Gryphon", nullptr},
|
||||
{EnemyType::GARANZ, EP1 | EP2, 0x19, 0x1D, "GARANZ", "Garanz", "Baranz"},
|
||||
{EnemyType::GEE, EP2, 0x36, 0x07, "GEE", "Gee", nullptr},
|
||||
{EnemyType::GI_GUE, EP2, 0x37, 0x1A, "GI_GUE", "Gi Gue", nullptr},
|
||||
{EnemyType::GIBBLES, EP2, 0x3D, 0x3D, "GIBBLES", "Gibbles", nullptr},
|
||||
{EnemyType::GIGOBOOMA, EP1, 0x0B, 0x4D, "GIGOBOOMA", "Gigobooma", "Tollaw"},
|
||||
{EnemyType::GILLCHIC, EP1 | EP2, 0x32, 0x1C, "GILLCHIC", "Gillchic", "Gillchich"},
|
||||
{EnemyType::GIRTABLULU, EP4, 0x48, 0x1F, "GIRTABLULU", "Girtablulu", nullptr},
|
||||
{EnemyType::GOBOOMA, EP1, 0x0A, 0x4C, "GOBOOMA", "Gobooma", "Barble"},
|
||||
{EnemyType::GOL_DRAGON, EP2 | BOSS, 0x4C, 0x12, "GOL_DRAGON", "Gol Dragon", nullptr},
|
||||
{EnemyType::GORAN, EP4, 0x52, 0x11, "GORAN", "Goran", nullptr},
|
||||
{EnemyType::GORAN_DETONATOR, EP4, 0x53, 0x13, "GORAN_DETONATOR", "Goran Detonator", nullptr},
|
||||
{EnemyType::GRASS_ASSASSIN, EP1 | EP2, 0x0C, 0x4E, "GRASS_ASSASSIN", "Grass Assassin", "Crimson Assassin"},
|
||||
{EnemyType::GUIL_SHARK, EP1, 0x12, 0x51, "GUIL_SHARK", "Guil Shark", "Melqueek"},
|
||||
{EnemyType::HALLO_RAPPY, EP2, 0x50, 0x19, "HALLO_RAPPY", "Hallo Rappy", nullptr},
|
||||
{EnemyType::HIDOOM, EP1 | EP2, 0x17, 0x32, "HIDOOM", "Hidoom", nullptr},
|
||||
{EnemyType::HILDEBEAR, EP1 | EP2, 0x01, 0x49, "HILDEBEAR", "Hildebear", "Hildelt"},
|
||||
{EnemyType::HILDEBLUE, EP1 | EP2 | RARE, 0x02, 0x4A, "HILDEBLUE", "Hildeblue", "Hildetorr"},
|
||||
{EnemyType::ILL_GILL, EP2, 0x52, 0x26, "ILL_GILL", "Ill Gill", nullptr},
|
||||
{EnemyType::KONDRIEU, EP4 | RARE | BOSS, 0x5B, 0x2A, "KONDRIEU", "Kondrieu", nullptr},
|
||||
{EnemyType::LA_DIMENIAN, EP1 | EP2, 0x2A, 0x54, "LA_DIMENIAN", "La Dimenian", "Merlan"},
|
||||
{EnemyType::LOVE_RAPPY, EP2, 0x33, 0x19, "LOVE_RAPPY", "Love Rappy", nullptr},
|
||||
{EnemyType::MERICARAND, EP2, 0x38, 0x3A, "MERICARAND", "Mericarand", nullptr},
|
||||
{EnemyType::MERICAROL, EP2, 0x38, 0x3A, "MERICAROL", "Mericarol", nullptr},
|
||||
{EnemyType::MERICUS, EP2 | RARE, 0x3A, 0x46, "MERICUS", "Mericus", nullptr},
|
||||
{EnemyType::MERIKLE, EP2 | RARE, 0x39, 0x45, "MERIKLE", "Merikle", nullptr},
|
||||
{EnemyType::MERILLIA, EP2, 0x34, 0x4B, "MERILLIA", "Merillia", nullptr},
|
||||
{EnemyType::MERILTAS, EP2, 0x35, 0x4C, "MERILTAS", "Meriltas", nullptr},
|
||||
{EnemyType::MERISSA_A, EP4, 0x46, 0x19, "MERISSA_A", "Merissa A", nullptr},
|
||||
{EnemyType::MERISSA_AA, EP4 | RARE, 0x47, 0x1A, "MERISSA_AA", "Merissa AA", nullptr},
|
||||
{EnemyType::MIGIUM, EP1 | EP2, 0x16, 0x33, "MIGIUM", "Migium", nullptr},
|
||||
{EnemyType::MONEST, EP1 | EP2, 0x04, 0x01, "MONEST", "Monest", "Mothvist"},
|
||||
{EnemyType::MORFOS, EP2, 0x42, 0x40, "MORFOS", "Morfos", nullptr},
|
||||
{EnemyType::MOTHMANT, EP1 | EP2, 0x03, 0x00, "MOTHMANT", "Mothmant", "Mothvert"},
|
||||
{EnemyType::NANO_DRAGON, EP1, 0x0F, 0x1A, "NANO_DRAGON", "Nano Dragon", nullptr},
|
||||
{EnemyType::NAR_LILY, EP1 | EP2 | RARE, 0x0E, 0x05, "NAR_LILY", "Nar Lily", "Mil Lily"},
|
||||
{EnemyType::OLGA_FLOW_1, EP2 | BOSS, 0xFF, 0x2B, "OLGA_FLOW_1", "Olga Flow (phase 1)", nullptr},
|
||||
{EnemyType::OLGA_FLOW_2, EP2 | BOSS, 0x4E, 0x2C, "OLGA_FLOW_2", "Olga Flow (phase 2)", nullptr},
|
||||
{EnemyType::PAL_SHARK, EP1, 0x11, 0x50, "PAL_SHARK", "Pal Shark", "Govulmer"},
|
||||
{EnemyType::PAN_ARMS, EP1 | EP2, 0x15, 0x31, "PAN_ARMS", "Pan Arms", nullptr},
|
||||
{EnemyType::PAZUZU_CRATER, EP4 | RARE, 0x4B, 0x08, "PAZUZU_CRATER", "Pazuzu (crater)", nullptr},
|
||||
{EnemyType::PAZUZU_DESERT, EP4 | RARE, 0x4C, 0x1C, "PAZUZU_DESERT", "Pazuzu (desert)", nullptr},
|
||||
{EnemyType::PIG_RAY, EP2, 0xFF, 0xFF, "PIG_RAY", "Pig Ray", nullptr},
|
||||
{EnemyType::POFUILLY_SLIME, EP1, 0x13, 0x30, "POFUILLY_SLIME", "Pofuilly Slime", nullptr},
|
||||
{EnemyType::POUILLY_SLIME, EP1 | RARE, 0x14, 0x2F, "POUILLY_SLIME", "Pouilly Slime", nullptr},
|
||||
{EnemyType::POISON_LILY, EP1 | EP2, 0x0D, 0x04, "POISON_LILY", "Poison Lily", "Ob Lily"},
|
||||
{EnemyType::PYRO_GORAN, EP4, 0x54, 0x12, "PYRO_GORAN", "Pyro Goran", nullptr},
|
||||
{EnemyType::RAG_RAPPY, EP1 | EP2, 0x05, 0x18, "RAG_RAPPY", "Rag Rappy", "El Rappy"},
|
||||
{EnemyType::RECOBOX, EP2, 0x43, 0x41, "RECOBOX", "Recobox", nullptr},
|
||||
{EnemyType::RECON, EP2, 0x44, 0x42, "RECON", "Recon", nullptr},
|
||||
{EnemyType::SAINT_MILION, EP4 | BOSS, 0x59, 0x22, "SAINT_MILION", "Saint-Milion", nullptr},
|
||||
{EnemyType::SAINT_RAPPY, EP2, 0x4F, 0x19, "SAINT_RAPPY", "Saint Rappy", nullptr},
|
||||
{EnemyType::SAND_RAPPY_CRATER, EP4, 0x55, 0x05, "SAND_RAPPY_CRATER", "Sand Rappy (crater)", nullptr},
|
||||
{EnemyType::SAND_RAPPY_DESERT, EP4, 0x56, 0x17, "SAND_RAPPY_DESERT", "Sand Rappy (desert)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_CRATER, EP4, 0x44, 0x0D, "SATELLITE_LIZARD_CRATER", "Satellite Lizard (crater)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_DESERT, EP4, 0x45, 0x1D, "SATELLITE_LIZARD_DESERT", "Satellite Lizard (desert)", nullptr},
|
||||
{EnemyType::SAVAGE_WOLF, EP1 | EP2, 0x07, 0x02, "SAVAGE_WOLF", "Savage Wolf", "Gulgus"},
|
||||
{EnemyType::SHAMBERTIN, EP4 | BOSS, 0x5A, 0x26, "SHAMBERTIN", "Shambertin", nullptr},
|
||||
{EnemyType::SINOW_BEAT, EP1, 0x1A, 0x06, "SINOW_BEAT", "Sinow Beat", "Sinow Blue"},
|
||||
{EnemyType::SINOW_BERILL, EP2, 0x3E, 0x06, "SINOW_BERILL", "Sinow Berill", nullptr},
|
||||
{EnemyType::SINOW_GOLD, EP1, 0x1B, 0x13, "SINOW_GOLD", "Sinow Gold", "Sinow Red"},
|
||||
{EnemyType::SINOW_SPIGELL, EP2, 0x3F, 0x13, "SINOW_SPIGELL", "Sinow Spigell", nullptr},
|
||||
{EnemyType::SINOW_ZELE, EP2, 0x46, 0x44, "SINOW_ZELE", "Sinow Zele", nullptr},
|
||||
{EnemyType::SINOW_ZOA, EP2, 0x45, 0x43, "SINOW_ZOA", "Sinow Zoa", nullptr},
|
||||
{EnemyType::SO_DIMENIAN, EP1 | EP2, 0x2B, 0x55, "SO_DIMENIAN", "So Dimenian", "Del-D"},
|
||||
{EnemyType::UL_GIBBON, EP2, 0x3B, 0x3B, "UL_GIBBON", "Ul Gibbon", nullptr},
|
||||
{EnemyType::VOL_OPT_1, EP1 | BOSS, 0xFF, 0xFF, "VOL_OPT_1", "Vol Opt (phase 1)", "Vol Opt ver.2 (phase 1)"},
|
||||
{EnemyType::VOL_OPT_2, EP1 | BOSS, 0x2E, 0x25, "VOL_OPT_2", "Vol Opt (phase 2)", "Vol Opt ver.2 (phase 2)"},
|
||||
{EnemyType::VOL_OPT_AMP, EP1 | BOSS, 0xFF, 0xFF, "VOL_OPT_AMP", "Vol Opt (amp)", "Vol Opt ver.2 (amp)"},
|
||||
{EnemyType::VOL_OPT_CORE, EP1 | BOSS, 0xFF, 0xFF, "VOL_OPT_CORE", "Vol Opt (core)", "Vol Opt ver.2 (core)"},
|
||||
{EnemyType::VOL_OPT_MONITOR, EP1 | BOSS, 0xFF, 0xFF, "VOL_OPT_MONITOR", "Vol Opt (monitor)", "Vol Opt ver.2 (monitor)"},
|
||||
{EnemyType::VOL_OPT_PILLAR, EP1 | BOSS, 0xFF, 0xFF, "VOL_OPT_PILLAR", "Vol Opt (pillar)", "Vol Opt ver.2 (pillar)"},
|
||||
{EnemyType::YOWIE_CRATER, EP4, 0x42, 0x0E, "YOWIE_CRATER", "Yowie (crater)", nullptr},
|
||||
{EnemyType::YOWIE_DESERT, EP4, 0x43, 0x1E, "YOWIE_DESERT", "Yowie (desert)", nullptr},
|
||||
{EnemyType::ZE_BOOTA, EP4, 0x4E, 0x01, "ZE_BOOTA", "Ze Boota", nullptr},
|
||||
{EnemyType::ZOL_GIBBON, EP2, 0x3C, 0x3C, "ZOL_GIBBON", "Zol Gibbon", nullptr},
|
||||
{EnemyType::ZU_CRATER, EP4, 0x49, 0x07, "ZU_CRATER", "Zu (crater)", nullptr},
|
||||
{EnemyType::ZU_DESERT, EP4, 0x4A, 0x1B, "ZU_DESERT", "Zu (desert)", nullptr},
|
||||
// TYPE FLAGS RT OLDRT BP-STATS BP-ATTACK BP-RESIST BP-MOVEMENT ENUM NAME IN-GAME NAME ULTIMATE NAME
|
||||
{EnemyType::UNKNOWN, 0, NONE, NONE, {}, {}, {}, {}, "UNKNOWN", "__UNKNOWN__", nullptr},
|
||||
{EnemyType::NONE, 0, NONE, NONE, {}, {}, {}, {}, "NONE", "__NONE__", nullptr},
|
||||
{EnemyType::NON_ENEMY_NPC, EP1 | EP2 | EP4, NONE, NONE, {}, {}, {}, {}, "NON_ENEMY_NPC", "__NPC__", nullptr},
|
||||
{EnemyType::AL_RAPPY, EP1 | RARE, 0x06, 0x06, {0x19}, {0x19}, {0x19}, {0x19}, "AL_RAPPY", "Al Rappy", "Pal Rappy"},
|
||||
{EnemyType::ASTARK, EP4, 0x58, 0x41, {0x09}, {0x0B, 0x0A, 0x0C}, {0x09}, {0x09}, "ASTARK", "Astark", nullptr},
|
||||
{EnemyType::BA_BOOTA, EP4, 0x62, 0x4F, {0x03}, {0x03, 0x02, 0x04}, {0x03}, {0x03}, "BA_BOOTA", "Ba Boota", nullptr},
|
||||
{EnemyType::BARBA_RAY, EP2 | BOSS, 0x49, 0x49, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "BARBA_RAY", "Barba Ray", nullptr},
|
||||
{EnemyType::BARBA_RAY_JOINT, EP2 | BOSS, 0x49, 0x49, {0x10}, {0x0F}, {0x10}, {0x0F}, "BARBA_RAY_JOINT", "Barba Ray (joint)", nullptr},
|
||||
{EnemyType::BARBAROUS_WOLF, EP1 | EP2, 0x08, 0x08, {0x03}, {0x03}, {0x03}, {0x03}, "BARBAROUS_WOLF", "Barbarous Wolf", "Gulgus-gue"},
|
||||
{EnemyType::BEE_L, EP1 | EP2, NONE, NONE, {0x0C}, {0x0C}, {0x0C}, {0x0C}, "BEE_L", "Bee L", "Gee L"},
|
||||
{EnemyType::BEE_R, EP1 | EP2, NONE, NONE, {0x0B}, {0x0B}, {0x0B}, {0x0B}, "BEE_R", "Bee R", "Gee R"},
|
||||
{EnemyType::BOOMA, EP1, 0x09, 0x09, {0x4B}, {0x4E}, {0x4A}, {0x4A}, "BOOMA", "Booma", "Bartle"},
|
||||
{EnemyType::BOOTA, EP4, 0x60, 0x4D, {0x00}, {0x00, 0x02, 0x04}, {0x00}, {0x00}, "BOOTA", "Boota", nullptr},
|
||||
{EnemyType::BULCLAW, EP1, 0x28, 0x28, {0x1F}, {0x1F}, {0x1F}, {0x1F}, "BULCLAW", "Bulclaw", nullptr},
|
||||
{EnemyType::BULK, EP1, 0x27, 0x27, {0x1F}, {0x1F}, {0x1F}, {0x1F}, "BULK", "Bulk", nullptr},
|
||||
{EnemyType::CANADINE, EP1, 0x1C, 0x1C, {0x07}, {0x07}, {0x07}, {0x07}, "CANADINE", "Canadine", "Canabin"},
|
||||
{EnemyType::CANADINE_GROUP, EP1, 0x1C, 0x1C, {0x08}, {0x08}, {0x08}, {0x08}, "CANADINE_GROUP", "Canadine (group)", "Canabin (group)"},
|
||||
{EnemyType::CANANE, EP1, 0x1D, 0x1D, {0x09}, {0x09}, {0x09}, {0x09}, "CANANE", "Canane", "Canune"},
|
||||
{EnemyType::CHAOS_BRINGER, EP1, 0x24, 0x24, {0x0D}, {0x0D}, {0x0D}, {0x0D}, "CHAOS_BRINGER", "Chaos Bringer", "Dark Bringer"},
|
||||
{EnemyType::CHAOS_SORCERER, EP1 | EP2, 0x1F, 0x1F, {0x0A}, {0x0A}, {0x0A}, {0x0A}, "CHAOS_SORCERER", "Chaos Sorceror", "Gran Sorceror"},
|
||||
{EnemyType::CLAW, EP1, 0x26, 0x26, {0x20}, {0x20}, {0x20}, {0x20}, "CLAW", "Claw", nullptr},
|
||||
{EnemyType::DARK_BELRA, EP1 | EP2, 0x25, 0x25, {0x0E}, {0x0E, 0x13}, {0x0E}, {0x0E}, "DARK_BELRA", "Dark Belra", "Indi Belra"},
|
||||
{EnemyType::DARK_FALZ_1, EP1 | BOSS, NONE, NONE, {0x36}, {0x36}, {0x36}, {0x35, 0x36}, "DARK_FALZ_1", "Dark Falz (phase 1)", nullptr},
|
||||
{EnemyType::DARK_FALZ_2, EP1 | BOSS, 0x2F, 0x2F, {0x37}, {0x37}, {0x37}, {0x35, 0x37}, "DARK_FALZ_2", "Dark Falz (phase 2)", nullptr},
|
||||
{EnemyType::DARK_FALZ_3, EP1 | BOSS, 0x2F, 0x2F, {0x38}, {0x38}, {0x38}, {0x35, 0x38}, "DARK_FALZ_3", "Dark Falz (phase 3)", nullptr},
|
||||
{EnemyType::DARK_GUNNER, EP1, 0x22, 0x22, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DARK_GUNNER", "Dark Gunner", nullptr},
|
||||
{EnemyType::DARK_GUNNER_CONTROL, EP1, NONE, NONE, {}, {}, {}, {}, "DARK_GUNNER_CONTROL", "Dark Gunner (control)", nullptr},
|
||||
{EnemyType::DARVANT, EP1, NONE, NONE, {0x35}, {0x35}, {0x35}, {0x35}, "DARVANT", "Darvant", nullptr},
|
||||
{EnemyType::DE_ROL_LE, EP1 | BOSS, 0x2D, 0x2D, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "DE_ROL_LE", "De Rol Le", "Dal Ral Lie"},
|
||||
{EnemyType::DE_ROL_LE_BODY, EP1 | BOSS, NONE, NONE, {0x10}, {0x0F}, {0x10}, {0x10}, "DE_ROL_LE_BODY", "De Rol Le (body)", "Dal Ral Lie (body)"},
|
||||
{EnemyType::DE_ROL_LE_MINE, EP1 | BOSS, NONE, NONE, {0x11}, {0x0F}, {0x11}, {0x11}, "DE_ROL_LE_MINE", "De Rol Le (mine)", "Dal Ral Lie (mine)"},
|
||||
{EnemyType::DEATH_GUNNER, EP1, 0x23, 0x23, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "DEATH_GUNNER", "Death Gunner", nullptr},
|
||||
{EnemyType::DEL_LILY, EP2, 0x53, 0x53, {0x25}, {0x25}, {0x25}, {0x25}, "DEL_LILY", "Del Lily", nullptr},
|
||||
{EnemyType::DEL_RAPPY_CRATER, EP4, 0x69, 0x57, {0x06}, {0x06}, {0x06}, {0x06}, "DEL_RAPPY_CRATER", "Del Rappy (crater)", nullptr},
|
||||
{EnemyType::DEL_RAPPY_DESERT, EP4, 0x69, 0x58, {0x18}, {0x18}, {0x18}, {0x18}, "DEL_RAPPY_DESERT", "Del Rappy (desert)", nullptr},
|
||||
{EnemyType::DELBITER, EP2, 0x48, 0x48, {0x0D}, {0x0D}, {0x0D}, {0x0D}, "DELBITER", "Delbiter", nullptr},
|
||||
{EnemyType::DELDEPTH, EP2, 0x47, 0x47, {0x30}, {0x30}, {0x30}, {0x30}, "DELDEPTH", "Deldepth", nullptr},
|
||||
{EnemyType::DELSABER, EP1 | EP2, 0x1E, 0x1E, {0x52}, {0x57, 0x58, 0x59}, {0x51}, {0x51}, "DELSABER", "Delsaber", nullptr},
|
||||
{EnemyType::DIMENIAN, EP1 | EP2, 0x29, 0x29, {0x53}, {0x5A}, {0x52}, {0x52}, "DIMENIAN", "Dimenian", "Arlan"},
|
||||
{EnemyType::DOLMDARL, EP2, 0x41, 0x41, {0x50}, {0x55}, {0x4F}, {0x4F}, "DOLMDARL", "Dolmdarl", nullptr},
|
||||
{EnemyType::DOLMOLM, EP2, 0x40, 0x40, {0x4F}, {0x54}, {0x4E}, {0x4E}, "DOLMOLM", "Dolmolm", nullptr},
|
||||
{EnemyType::DORPHON, EP4, 0x63, 0x50, {0x0F}, {0x0F}, {0x0F}, {0x0F}, "DORPHON", "Dorphon", nullptr},
|
||||
{EnemyType::DORPHON_ECLAIR, EP4 | RARE, 0x64, 0x51, {0x10}, {0x10}, {0x10}, {0x10}, "DORPHON_ECLAIR", "Dorphon Eclair", nullptr},
|
||||
{EnemyType::DRAGON, EP1 | BOSS, 0x2C, 0x2C, {0x12}, {0x12, 0x14, 0x15, 0x16, 0x17}, {0x12}, {0x11, 0x12, 0x13}, "DRAGON", "Dragon", "Sil Dragon"},
|
||||
{EnemyType::DUBCHIC, EP1 | EP2, 0x18, 0x18, {0x1B}, {0x1B}, {0x1B}, {0x1B}, "DUBCHIC", "Dubchic", "Dubchich"},
|
||||
{EnemyType::DUBWITCH, EP1 | EP2, NONE, NONE, {}, {}, {}, {}, "DUBWITCH", "Dubwitch", "Duvuik"},
|
||||
{EnemyType::EGG_RAPPY, EP2, 0x51, 0x51, {0x19}, {0x19}, {0x19}, {0x19}, "EGG_RAPPY", "Egg Rappy", nullptr},
|
||||
{EnemyType::EPSIGARD, EP2, NONE, NONE, {0x24}, {0x24}, {0x24}, {0x24}, "EPSIGARD", "Episgard", nullptr},
|
||||
{EnemyType::EPSILON, EP2, 0x54, 0x54, {0x23}, {0x23}, {0x23}, {0x23}, "EPSILON", "Epsilon", nullptr},
|
||||
{EnemyType::EVIL_SHARK, EP1, 0x10, 0x10, {0x4F}, {0x54}, {0x4E}, {0x4E}, "EVIL_SHARK", "Evil Shark", "Vulmer"},
|
||||
{EnemyType::GAEL_OR_GIEL, EP2, NONE, NONE, {0x2E}, {0x2E}, {0x2E}, {0x2E}, "GAEL_OR_GIEL", "Gael/Giel", nullptr},
|
||||
{EnemyType::GAL_GRYPHON, EP2 | BOSS, 0x4D, 0x4D, {0x1E}, {0x1E, 0x1F, 0x20, 0x21, 0x22}, {0x1E}, {0x1E, 0x1F, 0x20}, "GAL_GRYPHON", "Gal Gryphon", nullptr},
|
||||
{EnemyType::GARANZ, EP1 | EP2, 0x19, 0x19, {0x1D}, {0x1D}, {0x1D}, {0x1D}, "GARANZ", "Garanz", "Baranz"},
|
||||
{EnemyType::GEE, EP2, 0x36, 0x36, {0x07}, {0x07}, {0x07}, {0x07}, "GEE", "Gee", nullptr},
|
||||
{EnemyType::GI_GUE, EP2, 0x37, 0x37, {0x1A}, {0x1A}, {0x1A}, {0x1A}, "GI_GUE", "Gi Gue", nullptr},
|
||||
{EnemyType::GIBBLES, EP2, 0x3D, 0x3D, {0x3D}, {0x3D, 0x3E, 0x3F}, {0x3D}, {0x3D}, "GIBBLES", "Gibbles", nullptr},
|
||||
{EnemyType::GIGOBOOMA, EP1, 0x0B, 0x0B, {0x4D}, {0x50}, {0x4C}, {0x4C}, "GIGOBOOMA", "Gigobooma", "Tollaw"},
|
||||
{EnemyType::GILLCHIC, EP1 | EP2, 0x32, 0x32, {0x1C}, {0x1C}, {0x1C}, {0x1C}, "GILLCHIC", "Gillchic", "Gillchich"},
|
||||
{EnemyType::GIRTABLULU, EP4, 0x5D, 0x48, {0x1F}, {0x1F}, {0x1F}, {0x1F}, "GIRTABLULU", "Girtablulu", nullptr},
|
||||
{EnemyType::GOBOOMA, EP1, 0x0A, 0x0A, {0x4C}, {0x4F}, {0x4B}, {0x4B}, "GOBOOMA", "Gobooma", "Barble"},
|
||||
{EnemyType::GOL_DRAGON, EP2 | BOSS, 0x4C, 0x4C, {0x12}, {0x12, 0x14, 0x15, 0x16, 0x17}, {0x12}, {0x11, 0x12, 0x13}, "GOL_DRAGON", "Gol Dragon", nullptr},
|
||||
{EnemyType::GORAN, EP4, 0x65, 0x52, {0x11}, {0x11, 0x14}, {0x11}, {0x11}, "GORAN", "Goran", nullptr},
|
||||
{EnemyType::GORAN_DETONATOR, EP4, 0x66, 0x53, {0x13}, {0x13, 0x16}, {0x13}, {0x13}, "GORAN_DETONATOR", "Goran Detonator", nullptr},
|
||||
{EnemyType::GRASS_ASSASSIN, EP1 | EP2, 0x0C, 0x0C, {0x4E}, {0x51, 0x52, 0x53}, {0x4D}, {0x4D}, "GRASS_ASSASSIN", "Grass Assassin", "Crimson Assassin"},
|
||||
{EnemyType::GUIL_SHARK, EP1, 0x12, 0x12, {0x51}, {0x56}, {0x50}, {0x50}, "GUIL_SHARK", "Guil Shark", "Melqueek"},
|
||||
{EnemyType::HALLO_RAPPY, EP2, 0x50, 0x50, {0x19}, {0x19}, {0x19}, {0x19}, "HALLO_RAPPY", "Hallo Rappy", nullptr},
|
||||
{EnemyType::HIDOOM, EP1 | EP2, 0x17, 0x17, {0x32}, {0x32}, {0x32}, {0x32}, "HIDOOM", "Hidoom", nullptr},
|
||||
{EnemyType::HILDEBEAR, EP1 | EP2, 0x01, 0x01, {0x49}, {0x48, 0x49, 0x4A}, {0x48}, {0x48}, "HILDEBEAR", "Hildebear", "Hildelt"},
|
||||
{EnemyType::HILDEBLUE, EP1 | EP2 | RARE, 0x02, 0x02, {0x4A}, {0x4B, 0x4C, 0x4D}, {0x49}, {0x49}, "HILDEBLUE", "Hildeblue", "Hildetorr"},
|
||||
{EnemyType::ILL_GILL, EP2, 0x52, 0x52, {0x26}, {0x26, 0x27, 0x28, 0x29}, {0x26}, {0x26}, "ILL_GILL", "Ill Gill", nullptr},
|
||||
{EnemyType::KONDRIEU, EP4 | RARE | BOSS, 0x6C, 0x5B, {0x28, 0x2A}, {0x28, 0x2A}, {0x28, 0x2A}, {0x28, 0x2A}, "KONDRIEU", "Kondrieu", nullptr},
|
||||
{EnemyType::KONDRIEU_SPINNER, EP4 | RARE | BOSS, 0x6C, 0x5B, {0x29, 0x2B}, {0x29, 0x2B}, {0x29, 0x2B}, {0x29, 0x2B}, "KONDRIEU_SPINNER", "Kondrieu (spinner)", nullptr},
|
||||
{EnemyType::LA_DIMENIAN, EP1 | EP2, 0x2A, 0x2A, {0x54}, {0x5B}, {0x53}, {0x53}, "LA_DIMENIAN", "La Dimenian", "Merlan"},
|
||||
{EnemyType::LOVE_RAPPY, EP2, 0x33, 0x33, {0x19}, {0x19}, {0x19}, {0x19}, "LOVE_RAPPY", "Love Rappy", nullptr},
|
||||
{EnemyType::MERICARAND, EP2, 0x38, 0x38, {0x3A}, {0x3A}, {0x3A}, {0x3A}, "MERICARAND", "Mericarand", nullptr},
|
||||
{EnemyType::MERICAROL, EP2, 0x38, 0x38, {0x3A}, {0x3A}, {0x3A}, {0x3A}, "MERICAROL", "Mericarol", nullptr},
|
||||
{EnemyType::MERICUS, EP2 | RARE, 0x3A, 0x3A, {0x46}, {0x46}, {0x46}, {0x46}, "MERICUS", "Mericus", nullptr},
|
||||
{EnemyType::MERIKLE, EP2 | RARE, 0x39, 0x39, {0x45}, {0x45}, {0x45}, {0x45}, "MERIKLE", "Merikle", nullptr},
|
||||
{EnemyType::MERILLIA, EP2, 0x34, 0x34, {0x4B}, {0x4E}, {0x4A}, {0x4A}, "MERILLIA", "Merillia", nullptr},
|
||||
{EnemyType::MERILTAS, EP2, 0x35, 0x35, {0x4C}, {0x4F}, {0x4B}, {0x4B}, "MERILTAS", "Meriltas", nullptr},
|
||||
{EnemyType::MERISSA_A, EP4, 0x5B, 0x46, {0x19}, {0x19}, {0x19}, {0x19}, "MERISSA_A", "Merissa A", nullptr},
|
||||
{EnemyType::MERISSA_AA, EP4 | RARE, 0x5C, 0x47, {0x1A}, {0x1A}, {0x1A}, {0x1A}, "MERISSA_AA", "Merissa AA", nullptr},
|
||||
{EnemyType::MIGIUM, EP1 | EP2, 0x16, 0x16, {0x33}, {0x33}, {0x33}, {0x33}, "MIGIUM", "Migium", nullptr},
|
||||
{EnemyType::MONEST, EP1 | EP2, 0x04, 0x04, {0x01}, {0x01}, {0x01}, {0x01}, "MONEST", "Monest", "Mothvist"},
|
||||
{EnemyType::MORFOS, EP2, 0x42, 0x42, {0x40}, {0x40, 0x50}, {0x40}, {0x40}, "MORFOS", "Morfos", nullptr},
|
||||
{EnemyType::MOTHMANT, EP1 | EP2, 0x03, 0x03, {0x00}, {0x00}, {0x00}, {0x00}, "MOTHMANT", "Mothmant", "Mothvert"},
|
||||
{EnemyType::NANO_DRAGON, EP1, 0x0F, 0x0F, {0x1A}, {0x1A}, {0x1A}, {0x1A}, "NANO_DRAGON", "Nano Dragon", nullptr},
|
||||
{EnemyType::NAR_LILY, EP1 | EP2 | RARE, 0x0E, 0x0E, {0x05}, {0x05}, {0x05}, {0x05}, "NAR_LILY", "Nar Lily", "Mil Lily"},
|
||||
{EnemyType::OLGA_FLOW_1, EP2 | BOSS, NONE, NONE, {0x2B}, {0x2B}, {0x2B}, {0x2B}, "OLGA_FLOW_1", "Olga Flow (phase 1)", nullptr},
|
||||
{EnemyType::OLGA_FLOW_2, EP2 | BOSS, 0x4E, 0x4E, {0x2C}, {0x2C}, {0x2C}, {0x2C}, "OLGA_FLOW_2", "Olga Flow (phase 2)", nullptr},
|
||||
{EnemyType::PAL_SHARK, EP1, 0x11, 0x11, {0x50}, {0x55}, {0x4F}, {0x4F}, "PAL_SHARK", "Pal Shark", "Govulmer"},
|
||||
{EnemyType::PAN_ARMS, EP1 | EP2, 0x15, 0x15, {0x31}, {0x31}, {0x31}, {0x31}, "PAN_ARMS", "Pan Arms", nullptr},
|
||||
{EnemyType::PAZUZU_CRATER, EP4 | RARE, 0x5F, 0x4B, {0x08}, {0x08}, {0x08}, {0x08}, "PAZUZU_CRATER", "Pazuzu (crater)", nullptr},
|
||||
{EnemyType::PAZUZU_DESERT, EP4 | RARE, 0x5F, 0x4C, {0x1C}, {0x1C}, {0x1C}, {0x1C}, "PAZUZU_DESERT", "Pazuzu (desert)", nullptr},
|
||||
{EnemyType::PIG_RAY, EP2, 0x4A, NONE, {0x08}, {0x08}, {0x08}, {0x08}, "PIG_RAY", "Pig Ray", nullptr},
|
||||
{EnemyType::POFUILLY_SLIME, EP1, 0x13, 0x13, {0x30}, {0x30}, {0x30}, {0x30}, "POFUILLY_SLIME", "Pofuilly Slime", nullptr},
|
||||
{EnemyType::POUILLY_SLIME, EP1 | RARE, 0x14, 0x14, {0x34}, {0x34}, {0x34}, {0x34}, "POUILLY_SLIME", "Pouilly Slime", nullptr},
|
||||
{EnemyType::POISON_LILY, EP1 | EP2, 0x0D, 0x0D, {0x04}, {0x04}, {0x04}, {0x04}, "POISON_LILY", "Poison Lily", "Ob Lily"},
|
||||
{EnemyType::PYRO_GORAN, EP4, 0x67, 0x54, {0x12}, {0x12, 0x15}, {0x12}, {0x12}, "PYRO_GORAN", "Pyro Goran", nullptr},
|
||||
{EnemyType::RAG_RAPPY, EP1 | EP2, 0x05, 0x05, {0x18}, {0x18}, {0x18}, {0x18}, "RAG_RAPPY", "Rag Rappy", "El Rappy"},
|
||||
{EnemyType::RECOBOX, EP2, 0x43, 0x43, {0x41}, {0x41}, {0x41}, {0x41}, "RECOBOX", "Recobox", nullptr},
|
||||
{EnemyType::RECON, EP2, 0x44, 0x44, {0x42}, {0x42}, {0x42}, {0x42}, "RECON", "Recon", nullptr},
|
||||
{EnemyType::SAINT_MILION, EP4 | BOSS, 0x6A, 0x59, {0x20, 0x22}, {0x20, 0x22}, {0x20, 0x22}, {0x20, 0x22}, "SAINT_MILION", "Saint-Milion", nullptr},
|
||||
{EnemyType::SAINT_MILION_SPINNER, EP4 | BOSS, 0x6A, 0x59, {0x21, 0x23}, {0x21, 0x23}, {0x21, 0x23}, {0x21, 0x23}, "SAINT_MILION_SPINNER", "Saint-Milion (spinner)", nullptr},
|
||||
{EnemyType::SAINT_RAPPY, EP2, 0x4F, 0x4F, {0x19}, {0x19}, {0x19}, {0x19}, "SAINT_RAPPY", "Saint Rappy", nullptr},
|
||||
{EnemyType::SAND_RAPPY_CRATER, EP4, 0x68, 0x55, {0x05}, {0x05}, {0x05}, {0x05}, "SAND_RAPPY_CRATER", "Sand Rappy (crater)", nullptr},
|
||||
{EnemyType::SAND_RAPPY_DESERT, EP4, 0x68, 0x56, {0x17}, {0x17}, {0x17}, {0x17}, "SAND_RAPPY_DESERT", "Sand Rappy (desert)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_CRATER, EP4, 0x5A, 0x44, {0x0D}, {0x0D}, {0x0D}, {0x0D}, "SATELLITE_LIZARD_CRATER", "Satellite Lizard (crater)", nullptr},
|
||||
{EnemyType::SATELLITE_LIZARD_DESERT, EP4, 0x5A, 0x45, {0x1D}, {0x1D}, {0x1D}, {0x1D}, "SATELLITE_LIZARD_DESERT", "Satellite Lizard (desert)", nullptr},
|
||||
{EnemyType::SAVAGE_WOLF, EP1 | EP2, 0x07, 0x07, {0x02}, {0x02}, {0x02}, {0x02}, "SAVAGE_WOLF", "Savage Wolf", "Gulgus"},
|
||||
{EnemyType::SHAMBERTIN, EP4 | BOSS, 0x6B, 0x5A, {0x24, 0x26}, {0x24, 0x26}, {0x24, 0x26}, {0x24, 0x26}, "SHAMBERTIN", "Shambertin", nullptr},
|
||||
{EnemyType::SHAMBERTIN_SPINNER, EP4 | BOSS, 0x6B, 0x5A, {0x25, 0x27}, {0x25, 0x27}, {0x25, 0x27}, {0x25, 0x27}, "SHAMBERTIN_SPINNER", "Shambertin (spinner)", nullptr},
|
||||
{EnemyType::SINOW_BEAT, EP1, 0x1A, 0x1A, {0x06}, {0x06}, {0x06}, {0x06}, "SINOW_BEAT", "Sinow Beat", "Sinow Blue"},
|
||||
{EnemyType::SINOW_BERILL, EP2, 0x3E, 0x3E, {0x06}, {0x06}, {0x06}, {0x06}, "SINOW_BERILL", "Sinow Berill", nullptr},
|
||||
{EnemyType::SINOW_GOLD, EP1, 0x1B, 0x1B, {0x13}, {0x47}, {0x13}, {0x10}, "SINOW_GOLD", "Sinow Gold", "Sinow Red"},
|
||||
{EnemyType::SINOW_SPIGELL, EP2, 0x3F, 0x3F, {0x13}, {0x47}, {0x13}, {0x10}, "SINOW_SPIGELL", "Sinow Spigell", nullptr},
|
||||
{EnemyType::SINOW_ZELE, EP2, 0x46, 0x46, {0x44}, {0x44}, {0x44}, {0x44}, "SINOW_ZELE", "Sinow Zele", nullptr},
|
||||
{EnemyType::SINOW_ZOA, EP2, 0x45, 0x45, {0x43}, {0x43}, {0x43}, {0x43}, "SINOW_ZOA", "Sinow Zoa", nullptr},
|
||||
{EnemyType::SO_DIMENIAN, EP1 | EP2, 0x2B, 0x2B, {0x55}, {0x5C}, {0x54}, {0x54}, "SO_DIMENIAN", "So Dimenian", "Del-D"},
|
||||
{EnemyType::UL_GIBBON, EP2, 0x3B, 0x3B, {0x3B}, {0x3B}, {0x3B}, {0x3B}, "UL_GIBBON", "Ul Gibbon", nullptr},
|
||||
{EnemyType::UL_RAY, EP2, 0x4B, NONE, {0x09}, {0x09}, {0x09}, {0x09}, "UL_RAY", "Ul Ray", nullptr},
|
||||
{EnemyType::VOL_OPT_1, EP1 | BOSS, NONE, NONE, {0x21}, {0x21}, {0x21}, {0x21, 0x27, 0x28, 0x29}, "VOL_OPT_1", "Vol Opt (phase 1)", "Vol Opt ver.2 (phase 1)"},
|
||||
{EnemyType::VOL_OPT_2, EP1 | BOSS, 0x2E, 0x2E, {0x25}, {0x25}, {0x25}, {0x25, 0x2A}, "VOL_OPT_2", "Vol Opt (phase 2)", "Vol Opt ver.2 (phase 2)"},
|
||||
{EnemyType::VOL_OPT_AMP, EP1 | BOSS, NONE, NONE, {0x24}, {0x24}, {0x24}, {0x24}, "VOL_OPT_AMP", "Vol Opt (amp)", "Vol Opt ver.2 (amp)"},
|
||||
{EnemyType::VOL_OPT_CORE, EP1 | BOSS, NONE, NONE, {0x26}, {0x26}, {0x26}, {0x26}, "VOL_OPT_CORE", "Vol Opt (core)", "Vol Opt ver.2 (core)"},
|
||||
{EnemyType::VOL_OPT_MONITOR, EP1 | BOSS, NONE, NONE, {0x23}, {0x23}, {0x23}, {0x23}, "VOL_OPT_MONITOR", "Vol Opt (monitor)", "Vol Opt ver.2 (monitor)"},
|
||||
{EnemyType::VOL_OPT_PILLAR, EP1 | BOSS, NONE, NONE, {0x22}, {0x22}, {0x22}, {0x22}, "VOL_OPT_PILLAR", "Vol Opt (pillar)", "Vol Opt ver.2 (pillar)"},
|
||||
{EnemyType::YOWIE_CRATER, EP4, 0x59, 0x42, {0x0E}, {0x0E}, {0x0E}, {0x0E}, "YOWIE_CRATER", "Yowie (crater)", nullptr},
|
||||
{EnemyType::YOWIE_DESERT, EP4, 0x59, 0x43, {0x1E}, {0x1E}, {0x1E}, {0x1E}, "YOWIE_DESERT", "Yowie (desert)", nullptr},
|
||||
{EnemyType::ZE_BOOTA, EP4, 0x61, 0x4E, {0x01}, {0x01, 0x02, 0x04}, {0x01}, {0x01}, "ZE_BOOTA", "Ze Boota", nullptr},
|
||||
{EnemyType::ZOL_GIBBON, EP2, 0x3C, 0x3C, {0x3C}, {0x3C}, {0x3C}, {0x3C}, "ZOL_GIBBON", "Zol Gibbon", nullptr},
|
||||
{EnemyType::ZU_CRATER, EP4, 0x5E, 0x49, {0x07}, {0x07}, {0x07}, {0x07}, "ZU_CRATER", "Zu (crater)", nullptr},
|
||||
{EnemyType::ZU_DESERT, EP4, 0x5E, 0x4A, {0x1B}, {0x1B}, {0x1B}, {0x1B}, "ZU_DESERT", "Zu (desert)", nullptr},
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
@@ -173,23 +178,20 @@ EnemyType phosg::enum_for_name<EnemyType>(const char* name) {
|
||||
}
|
||||
|
||||
const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index) {
|
||||
const auto& generate_table = +[](Episode episode) -> vector<vector<EnemyType>> {
|
||||
vector<vector<EnemyType>> ret;
|
||||
static array<vector<vector<EnemyType>>, 5> data;
|
||||
auto& ret = data.at(static_cast<size_t>(episode));
|
||||
if (ret.empty()) {
|
||||
for (const auto& def : type_defs) {
|
||||
if (def.valid_in_episode(episode) && (def.rt_index != 0xFF)) {
|
||||
if (!def.valid_in_episode(episode)) {
|
||||
continue;
|
||||
}
|
||||
if (def.rt_index != 0xFF) {
|
||||
if (def.rt_index >= ret.size()) {
|
||||
ret.resize(def.rt_index + 1);
|
||||
}
|
||||
ret[def.rt_index].emplace_back(def.type);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
static array<vector<vector<EnemyType>>, 5> data;
|
||||
auto& ret = data.at(static_cast<size_t>(episode));
|
||||
if (ret.empty()) {
|
||||
ret = generate_table(episode);
|
||||
}
|
||||
try {
|
||||
return ret.at(rt_index);
|
||||
@@ -199,24 +201,21 @@ const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8
|
||||
}
|
||||
}
|
||||
|
||||
const vector<EnemyType>& enemy_types_for_battle_param_index(Episode episode, uint8_t bp_index) {
|
||||
const auto& generate_table = +[](Episode episode) -> vector<vector<EnemyType>> {
|
||||
vector<vector<EnemyType>> ret;
|
||||
for (const auto& def : type_defs) {
|
||||
if (def.valid_in_episode(episode) && (def.bp_index != 0xFF)) {
|
||||
if (def.bp_index >= ret.size()) {
|
||||
ret.resize(def.bp_index + 1);
|
||||
}
|
||||
ret[def.bp_index].emplace_back(def.type);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const vector<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index) {
|
||||
static array<vector<vector<EnemyType>>, 5> data;
|
||||
auto& ret = data.at(static_cast<size_t>(episode));
|
||||
if (ret.empty()) {
|
||||
ret = generate_table(episode);
|
||||
for (const auto& def : type_defs) {
|
||||
if (def.valid_in_episode(episode) && !def.bp_stats_indexes.empty()) {
|
||||
// Some enemies (e.g. Ep4 bosses) have multiple stats structures; we use the last one, since that's the only
|
||||
// one used when giving EXP
|
||||
size_t bp_index = def.bp_stats_indexes.back();
|
||||
if (bp_index >= ret.size()) {
|
||||
ret.resize(bp_index + 1);
|
||||
}
|
||||
ret[bp_index].emplace_back(def.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return ret.at(bp_index);
|
||||
|
||||
+17
-4
@@ -7,6 +7,11 @@
|
||||
#include "StaticGameData.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
// We don't know what the actual maximum was, since it was presumably only stored server-side on Sega's servers. The
|
||||
// client uses values up to 0x6C (Kondrieu), so we just choose a value larger than that.
|
||||
static constexpr size_t NUM_RT_INDEXES_V3 = 0x64;
|
||||
static constexpr size_t NUM_RT_INDEXES_V4 = 0x70;
|
||||
|
||||
enum class EnemyType : uint8_t {
|
||||
UNKNOWN = 0,
|
||||
NONE,
|
||||
@@ -15,6 +20,7 @@ enum class EnemyType : uint8_t {
|
||||
ASTARK,
|
||||
BA_BOOTA,
|
||||
BARBA_RAY,
|
||||
BARBA_RAY_JOINT,
|
||||
BARBAROUS_WOLF,
|
||||
BEE_L,
|
||||
BEE_R,
|
||||
@@ -35,7 +41,6 @@ enum class EnemyType : uint8_t {
|
||||
DARK_GUNNER,
|
||||
DARK_GUNNER_CONTROL,
|
||||
DARVANT,
|
||||
DARVANT_ULTIMATE,
|
||||
DE_ROL_LE,
|
||||
DE_ROL_LE_BODY,
|
||||
DE_ROL_LE_MINE,
|
||||
@@ -79,6 +84,7 @@ enum class EnemyType : uint8_t {
|
||||
HILDEBLUE,
|
||||
ILL_GILL,
|
||||
KONDRIEU,
|
||||
KONDRIEU_SPINNER,
|
||||
LA_DIMENIAN,
|
||||
LOVE_RAPPY,
|
||||
MERICARAND,
|
||||
@@ -110,6 +116,7 @@ enum class EnemyType : uint8_t {
|
||||
RECOBOX,
|
||||
RECON,
|
||||
SAINT_MILION,
|
||||
SAINT_MILION_SPINNER,
|
||||
SAINT_RAPPY,
|
||||
SAND_RAPPY_CRATER,
|
||||
SAND_RAPPY_DESERT,
|
||||
@@ -117,6 +124,7 @@ enum class EnemyType : uint8_t {
|
||||
SATELLITE_LIZARD_DESERT,
|
||||
SAVAGE_WOLF,
|
||||
SHAMBERTIN,
|
||||
SHAMBERTIN_SPINNER,
|
||||
SINOW_BEAT,
|
||||
SINOW_BERILL,
|
||||
SINOW_GOLD,
|
||||
@@ -125,6 +133,7 @@ enum class EnemyType : uint8_t {
|
||||
SINOW_ZOA,
|
||||
SO_DIMENIAN,
|
||||
UL_GIBBON,
|
||||
UL_RAY,
|
||||
VOL_OPT_1,
|
||||
VOL_OPT_2,
|
||||
VOL_OPT_AMP,
|
||||
@@ -150,8 +159,12 @@ struct EnemyTypeDefinition {
|
||||
};
|
||||
EnemyType type;
|
||||
uint8_t flags;
|
||||
uint8_t rt_index; // 0xFF if not valid (e.g. not an enemy)
|
||||
uint8_t bp_index; // 0xFF if not valid (e.g. not an enemy)
|
||||
uint8_t rt_index; // 0xFF if not valid (e.g. not an enemy, or has no drops)
|
||||
uint8_t legacy_rt_index; // Index used by Schtserv in their Ep4 .rel format
|
||||
std::vector<uint8_t> bp_stats_indexes;
|
||||
std::vector<uint8_t> bp_attack_data_indexes;
|
||||
std::vector<uint8_t> bp_resist_data_indexes;
|
||||
std::vector<uint8_t> bp_movement_data_indexes;
|
||||
const char* enum_name;
|
||||
const char* in_game_name;
|
||||
const char* ultimate_name; // May be null if same as in_game_name
|
||||
@@ -185,4 +198,4 @@ template <>
|
||||
EnemyType phosg::enum_for_name<EnemyType>(const char* name);
|
||||
|
||||
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
|
||||
const std::vector<EnemyType>& enemy_types_for_battle_param_index(Episode episode, uint8_t bp_index);
|
||||
const std::vector<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index);
|
||||
|
||||
+3
-1
@@ -218,7 +218,9 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
}
|
||||
string grave_enemy_types;
|
||||
if (p->challenge_records.grave_defeated_by_enemy_rt_index) {
|
||||
for (EnemyType type : enemy_types_for_rare_table_index(p->challenge_records.grave_is_ep2 ? Episode::EP2 : Episode::EP1, p->challenge_records.grave_defeated_by_enemy_rt_index)) {
|
||||
for (EnemyType type : enemy_types_for_rare_table_index(
|
||||
p->challenge_records.grave_is_ep2 ? Episode::EP2 : Episode::EP1,
|
||||
p->challenge_records.grave_defeated_by_enemy_rt_index)) {
|
||||
if (!grave_enemy_types.empty()) {
|
||||
grave_enemy_types += "/";
|
||||
}
|
||||
|
||||
+3
-2
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "EnemyType.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
@@ -190,7 +191,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area) {
|
||||
ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t 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 >= 0x64) {
|
||||
if (enemy_type >= NUM_RT_INDEXES_V4) {
|
||||
this->log.warning_f("Invalid enemy type: {:X}", enemy_type);
|
||||
return DropResult();
|
||||
}
|
||||
@@ -331,7 +332,7 @@ bool ItemCreator::should_allow_meseta_drops() const {
|
||||
|
||||
ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_type, uint8_t area) {
|
||||
ItemData item;
|
||||
if (this->are_rare_drops_allowed() && (enemy_type > 0) && (enemy_type < 0x64)) {
|
||||
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).
|
||||
|
||||
+56
-40
@@ -2176,7 +2176,6 @@ Action a_convert_rare_item_set(
|
||||
.gsl (PSO BB little-endian GSL archive)\n\
|
||||
.gslb (PSO GC big-endian GSL archive)\n\
|
||||
.afs (PSO V2 little-endian AFS archive)\n\
|
||||
.rel (Schtserv rare table; cannot be used in output filename)\n\
|
||||
.html (HTML rare table; cannot be used in input filename)\n\
|
||||
If the --multiply=X option is given, multiplies all drop rates by X (given\n\
|
||||
as a decimal value). The HTML drop tables will account for each enemy\'s\n\
|
||||
@@ -2283,7 +2282,13 @@ Action a_convert_common_item_set(
|
||||
.json (newserv JSON common item table)\n\
|
||||
.afs (PSO v2 AFS archive; --ct-filename is required in this case)\n\
|
||||
.gsl (PSO BB little-endian GSL archive)\n\
|
||||
.gslb (PSO GC big-endian GSL archive)\n",
|
||||
.gslb (PSO GC big-endian GSL archive)\n\
|
||||
Options:\n\
|
||||
--ct-filename=FILENAME: Required if the input is an AFS archive.\n\
|
||||
Specifies where to read the ItemCT (Challenge Mode) tables from.\n\
|
||||
Should be another AFS file.\n\
|
||||
--big-endian: If input is a GSL file, always decode it as big-endian\n\
|
||||
even if the file extension is not gslb.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
string input_filename = args.get<string>(1, false);
|
||||
if (input_filename.empty() || (input_filename == "-")) {
|
||||
@@ -2569,9 +2574,9 @@ Action a_print_level_stats(
|
||||
print_stats_set(level_200_limit_v4, "v4 limit ");
|
||||
});
|
||||
|
||||
Action a_print_item_parameter_tables(
|
||||
"show-item-tables", "\
|
||||
show-item-tables\n\
|
||||
Action a_show_item_parameter_tables(
|
||||
"show-item-parameter-tables", "\
|
||||
show-item-parameter-tables\n\
|
||||
Print the item parameter tables for each version in a semi-human-reatable\n\
|
||||
format.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
@@ -2955,41 +2960,6 @@ Action a_check_supermaps(
|
||||
|
||||
auto rand_crypt = make_shared<MT19937Generator>(phosg::random_object<uint32_t>());
|
||||
|
||||
SuperMap::EfficiencyStats all_free_maps_eff;
|
||||
for (const auto& it : s->supermap_for_free_play_key) {
|
||||
auto episode = static_cast<Episode>((it.first >> 28) & 7);
|
||||
auto mode = static_cast<GameMode>((it.first >> 26) & 3);
|
||||
Difficulty difficulty = static_cast<Difficulty>((it.first >> 24) & 3);
|
||||
uint8_t floor = (it.first >> 16) & 0xFF;
|
||||
uint8_t layout = (it.first >> 8) & 0xFF;
|
||||
uint8_t entities = (it.first >> 0) & 0xFF;
|
||||
|
||||
string filename_token;
|
||||
if (save_disassembly) {
|
||||
string filename = std::format(
|
||||
"supermap_{}_{}_{}_{:02X}_{:02X}_{:02X}.txt",
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities);
|
||||
auto f = phosg::fopen_unique(filename, "wt");
|
||||
it.second->print(f.get());
|
||||
filename_token = " => " + filename;
|
||||
}
|
||||
|
||||
auto eff = it.second->efficiency();
|
||||
all_free_maps_eff += eff;
|
||||
auto eff_str = eff.str();
|
||||
phosg::fwrite_fmt(stderr, "FREE MAP: {:08X} => {} {} {} floor={:02X} layout={:02X} entities={:02X} => {}{}\n",
|
||||
it.first,
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities, eff_str, filename_token);
|
||||
}
|
||||
string all_free_maps_eff_str = all_free_maps_eff.str();
|
||||
phosg::fwrite_fmt(stderr, "ALL FREE MAPS: {}\n", all_free_maps_eff_str);
|
||||
|
||||
// Generate MapStates for a few random variations
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
Episode episode = ALL_EPISODES_V4[phosg::random_object<uint32_t>() % ALL_EPISODES_V4.size()];
|
||||
@@ -3016,6 +2986,52 @@ Action a_check_supermaps(
|
||||
map_state->event_states.size());
|
||||
}
|
||||
|
||||
SuperMap::EfficiencyStats all_free_maps_eff;
|
||||
for (const auto& [key, supermap] : s->supermap_for_free_play_key) {
|
||||
auto episode = static_cast<Episode>((key >> 28) & 7);
|
||||
auto mode = static_cast<GameMode>((key >> 26) & 3);
|
||||
Difficulty difficulty = static_cast<Difficulty>((key >> 24) & 3);
|
||||
uint8_t floor = (key >> 16) & 0xFF;
|
||||
uint8_t layout = (key >> 8) & 0xFF;
|
||||
uint8_t entities = (key >> 0) & 0xFF;
|
||||
|
||||
if (supermap) {
|
||||
string filename_token;
|
||||
if (save_disassembly) {
|
||||
string filename = std::format(
|
||||
"supermap_{}_{}_{}_{:02X}_{:02X}_{:02X}.txt",
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities);
|
||||
auto f = phosg::fopen_unique(filename, "wt");
|
||||
supermap->print(f.get());
|
||||
filename_token = " => " + filename;
|
||||
}
|
||||
|
||||
auto eff = supermap->efficiency();
|
||||
all_free_maps_eff += eff;
|
||||
auto eff_str = eff.str();
|
||||
phosg::fwrite_fmt(stderr, "FREE MAP: {:08X} => {} {} {} floor={:02X} layout={:02X} entities={:02X} => {}{}\n",
|
||||
key,
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities, eff_str, filename_token);
|
||||
|
||||
} else {
|
||||
phosg::fwrite_fmt(stderr, "FREE MAP: {:08X} => {} {} {} floor={:02X} layout={:02X} entities={:02X} => NO MAP\n",
|
||||
key,
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities);
|
||||
}
|
||||
}
|
||||
|
||||
string all_free_maps_eff_str = all_free_maps_eff.str();
|
||||
phosg::fwrite_fmt(stderr, "ALL FREE MAPS: {}\n", all_free_maps_eff_str);
|
||||
|
||||
s->load_quest_index();
|
||||
|
||||
SuperMap::EfficiencyStats all_quests_eff;
|
||||
|
||||
+5
-11
@@ -4367,7 +4367,8 @@ shared_ptr<SuperMap::Object> SuperMap::add_object(
|
||||
return obj;
|
||||
}
|
||||
|
||||
void SuperMap::link_object_version(std::shared_ptr<Object> obj, Version version, const MapFile::ObjectSetEntry* set_entry) {
|
||||
void SuperMap::link_object_version(
|
||||
std::shared_ptr<Object> obj, Version version, const MapFile::ObjectSetEntry* set_entry) {
|
||||
// Add to version's entities list and set the object's per-version info
|
||||
auto& entities = this->version(version);
|
||||
auto& obj_ver = obj->version(version);
|
||||
@@ -6124,16 +6125,9 @@ void MapState::index_super_map(const FloorConfig& fc, shared_ptr<RandomGenerator
|
||||
}
|
||||
|
||||
// Handle random rare enemies and difficulty-based effects
|
||||
EnemyType type;
|
||||
switch (ene->type) {
|
||||
case EnemyType::DARK_FALZ_3:
|
||||
type = (this->difficulty == Difficulty::NORMAL) ? EnemyType::DARK_FALZ_2 : EnemyType::DARK_FALZ_3;
|
||||
break;
|
||||
case EnemyType::DARVANT:
|
||||
type = (this->difficulty == Difficulty::ULTIMATE) ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT;
|
||||
break;
|
||||
default:
|
||||
type = ene->type;
|
||||
EnemyType type = ene->type;
|
||||
if ((type == EnemyType::DARK_FALZ_3) && (this->difficulty == Difficulty::NORMAL)) {
|
||||
type = EnemyType::DARK_FALZ_2;
|
||||
}
|
||||
|
||||
uint8_t area = fc.super_map->area_for_floor(ene->floor);
|
||||
|
||||
+1
-1
@@ -469,7 +469,7 @@ string RareItemSet::serialize_html(
|
||||
EnemyType::DARK_BELRA,
|
||||
EnemyType::DARK_GUNNER, EnemyType::DARK_GUNNER_CONTROL, EnemyType::DEATH_GUNNER,
|
||||
EnemyType::CHAOS_BRINGER,
|
||||
EnemyType::DARVANT, EnemyType::DARVANT_ULTIMATE, EnemyType::DARK_FALZ_1, EnemyType::DARK_FALZ_2, EnemyType::DARK_FALZ_3,
|
||||
EnemyType::DARVANT, EnemyType::DARK_FALZ_1, EnemyType::DARK_FALZ_2, EnemyType::DARK_FALZ_3,
|
||||
}},
|
||||
}},
|
||||
{Episode::EP2, {
|
||||
|
||||
@@ -2848,8 +2848,8 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
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;
|
||||
// rt_indexes in Episode 4 don't match those sent in the command; we just ignore what the client sends.
|
||||
if ((area < 0x24) && (cmd.rt_index != res.effective_rt_index)) {
|
||||
bool mismatched_rt_index = false;
|
||||
if (cmd.rt_index != res.effective_rt_index) {
|
||||
// Special cases: BULCLAW => BULK and DARK_GUNNER => DEATH_GUNNER
|
||||
if (cmd.rt_index == 0x27 && type == EnemyType::BULCLAW) {
|
||||
c->log.info_f("E-{:03X} killed as BULK instead of BULCLAW", res.target_ene_st->e_id);
|
||||
@@ -2860,9 +2860,7 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
} 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);
|
||||
if (!is_v4(version)) {
|
||||
res.effective_rt_index = cmd.rt_index;
|
||||
}
|
||||
mismatched_rt_index = true;
|
||||
}
|
||||
}
|
||||
if (cmd.floor != res.target_ene_st->super_ene->floor) {
|
||||
@@ -2870,7 +2868,10 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
cmd.floor, res.target_ene_st->super_ene->floor);
|
||||
}
|
||||
if (c->check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_fmt(c, "$C5E-{:03X} {}", res.target_ene_st->e_id, phosg::name_for_enum(type));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3948,8 +3949,10 @@ static uint32_t base_exp_for_enemy_type(
|
||||
for (const auto& episode : episode_order) {
|
||||
try {
|
||||
const auto& bp_table = bp_index->get_table(is_solo, episode);
|
||||
uint32_t bp_index = type_definition_for_enemy(enemy_type).bp_index;
|
||||
return bp_table.stats_for_index(difficulty, bp_index).experience;
|
||||
const auto& bp_stats_indexes = type_definition_for_enemy(enemy_type).bp_stats_indexes;
|
||||
if (!bp_stats_indexes.empty()) {
|
||||
return bp_table.stats_for_index(difficulty, bp_stats_indexes.back()).experience;
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ItemPT-pc-v2.afs
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
ItemPT-gc-v3.gslb
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user