fix incorrect box drop areas in rare tables

This commit is contained in:
Martin Michelsen
2025-02-22 15:39:08 -08:00
parent fa22c3563d
commit 104e31028b
16 changed files with 2382 additions and 2368 deletions
+5 -5
View File
@@ -1515,7 +1515,7 @@ ChatCommandDefinition cc_next(
a.check_is_game(true);
a.check_cheats_enabled(s->cheat_flags.warp);
size_t limit = floor_limit_for_episode(l->episode);
size_t limit = FloorDefinition::limit_for_episode(l->episode);
if (limit == 0) {
return;
}
@@ -1528,7 +1528,7 @@ ChatCommandDefinition cc_next(
throw precondition_failed("$C6You must be in a\ngame to use this\ncommand");
}
size_t limit = floor_limit_for_episode(a.ses->lobby_episode);
size_t limit = FloorDefinition::limit_for_episode(a.ses->lobby_episode);
if (limit == 0) {
return;
}
@@ -2693,7 +2693,7 @@ static void server_command_warp(const ServerArgs& a, bool is_warpall) {
return;
}
size_t limit = floor_limit_for_episode(l->episode);
size_t limit = FloorDefinition::limit_for_episode(l->episode);
if (limit == 0) {
return;
} else if (floor > limit) {
@@ -2777,14 +2777,14 @@ ChatCommandDefinition cc_where(
auto l = a.c->require_lobby();
send_text_message_printf(a.c, "$C7%01" PRIX32 ":%s X:%" PRId32 " Z:%" PRId32,
a.c->floor,
short_name_for_floor(l->episode, a.c->floor),
FloorDefinition::get(l->episode, a.c->floor).short_name,
static_cast<int32_t>(a.c->pos.x.load()),
static_cast<int32_t>(a.c->pos.z.load()));
for (auto lc : l->clients) {
if (lc && (lc != a.c)) {
string name = lc->character()->disp.name.decode(lc->language());
send_text_message_printf(a.c, "$C6%s$C7 %01" PRIX32 ":%s",
name.c_str(), lc->floor, short_name_for_floor(l->episode, lc->floor));
name.c_str(), lc->floor, FloorDefinition::get(l->episode, lc->floor).short_name);
}
}
},
+3 -3
View File
@@ -6261,13 +6261,13 @@ struct G_ExchangeItemInQuest_BB_6xDB {
le_uint32_t amount = 0;
} __packed_ws__(G_ExchangeItemInQuest_BB_6xDB, 0x10);
// 6xDC: Saint-Million boss actions (BB)
// 6xDC: Saint-Milion/Shambertin/Kondrieu boss actions (BB)
struct G_SaintMillionBossActions_BB_6xDC {
struct G_Episode4BossActions_BB_6xDC {
G_UnusedHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t unknown_a2 = 0;
} __packed_ws__(G_SaintMillionBossActions_BB_6xDC, 8);
} __packed_ws__(G_Episode4BossActions_BB_6xDC, 8);
// 6xDD: Set EXP multiplier (BB)
// header.param specifies the EXP multiplier. It is 1-based, so the value 2
+1 -1
View File
@@ -299,7 +299,7 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area_nor
}
auto rare_specs = this->rare_item_set->get_box_specs(
this->mode, this->episode, this->difficulty, this->section_id, area_norm + 1);
this->mode, this->episode, this->difficulty, this->section_id, area_norm);
for (const auto& spec : rare_specs) {
item = this->check_rate_and_create_rare_item(spec, area_norm);
if (!item.empty()) {
+1
View File
@@ -807,6 +807,7 @@ string ItemData::short_hex() const {
size_t offset = ret.find_last_not_of('0');
if (offset != string::npos) {
offset += (offset & 1) ? 1 : 2;
offset = std::max<size_t>(offset, 6);
if (offset < ret.size()) {
ret.resize(offset);
}
+4 -1
View File
@@ -1990,9 +1990,12 @@ Action a_convert_rare_item_set(
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
for (Episode episode : episodes) {
for (size_t difficulty = 0; difficulty < (is_v1 ? 3 : 4); difficulty++) {
if (!rs->has_entries_for_game_config(mode, episode, difficulty)) {
continue;
}
auto item_name_index = s->item_name_index(get_cli_version(args, Version::BB_V4));
string data = rs->serialize_html(mode, episode, difficulty, item_name_index);
string out_filename = output_filename.substr(0, output_filename.size() - 5) + "." + name_for_mode(mode) + "." + name_for_episode(episode) + "." + name_for_difficulty(difficulty) + output_filename.substr(output_filename.size() - 5);
string out_filename = output_filename.substr(0, output_filename.size() - 5) + "." + name_for_mode(mode) + "." + abbreviation_for_episode(episode) + "." + abbreviation_for_difficulty(difficulty) + output_filename.substr(output_filename.size() - 5);
phosg::save_file(out_filename, data);
phosg::log_info("... %s", out_filename.c_str());
}
+73 -22
View File
@@ -324,7 +324,7 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
for (const auto& item_it : section_id_it.second->as_dict()) {
vector<ExpandedDrop>* target;
if (phosg::starts_with(item_it.first, "Box-")) {
uint8_t area = floor_for_name(item_it.first.substr(4));
uint8_t area = FloorDefinition::get(episode, item_it.first.substr(4)).drop_area_norm;
if (collection.box_area_to_specs.size() <= area) {
collection.box_area_to_specs.resize(area + 1);
}
@@ -547,12 +547,12 @@ string RareItemSet::serialize_html(
}}};
// clang-format on
static const std::array<uint32_t, 10> bg_colors{
static const std::array<uint32_t, 10> secid_colors{
// Vrd Grn Sky Blu Prp Pnk Red Orn Ylw Wht
0x00A562, 0x76FE43, 0x59F9F9, 0x4488FF, 0xCC00FF, 0xFF87CB, 0xF70F0F, 0xF7830F, 0xF7F715, 0xFFFFFF};
static const std::array<uint32_t, 10> text_colors{
static const std::array<uint32_t, 10> secid_bg_colors{
// Vrd Grn Sky Blu Prp Pnk Red Orn Ylw Wht
0xFFFFFF, 0x000000, 0x000000, 0xFFFFFF, 0xFFFFFF, 0x000000, 0xFFFFFF, 0x000000, 0x000000, 0x000000};
0x002918, 0x1D3F10, 0x163E3E, 0x11223F, 0x33003F, 0x3F2132, 0x3D0303, 0x3D2003, 0x3D3D05, 0x404040};
deque<string> blocks;
blocks.emplace_back(phosg::string_printf("\
@@ -562,7 +562,7 @@ string RareItemSet::serialize_html(
<style type=\"text/css\">\n\
body {\n\
background-color: #222222;\n\
color: #EEEEEE;\n\
color: #CCCCCC;\n\
}\n\
table {\n\
border: 1px #222222;\n\
@@ -573,7 +573,7 @@ string RareItemSet::serialize_html(
td th {\n\
border: 1px #222222;\n\
text-align: center;\n\
padding: 4px;\n\
padding: 10px;\n\
}\n\
th {\n\
font-size: 18px;\n\
@@ -581,6 +581,23 @@ string RareItemSet::serialize_html(
th.space {\n\
background-color: #222222;\n\
height: 20px;\n\
}\n\
.title {\n\
font-family: sans-serif;\n\
text-align: center;\n\
font-size: 24px;\n\
font-weight: bold;\n\
}\n\
.loc {\n\
background-color: #444444;\n\
font-weight: bold;\n\
}\n\
.locheader {\n\
background-color: #CCCCCC;\n\
color: #222222;\n\
}\n\
.item {\n\
font-weight: bold;\n\
}\n",
name_for_episode(episode),
name_for_difficulty(difficulty)));
@@ -589,21 +606,33 @@ string RareItemSet::serialize_html(
.sec%zu {\n\
background-color: #%06" PRIX32 ";\n\
color: #%06" PRIX32 ";\n\
padding: 10px;\n\
}\n\
.sec%zuheader {\n\
background-color: #%06" PRIX32 ";\n\
color: #222222;\n\
padding: 10px;\n\
}\n",
z, bg_colors[z], text_colors[z]));
z, secid_bg_colors[z], secid_colors[z], z, secid_colors[z]));
}
blocks.emplace_back("\
</style>\n\
</head><body>\n");
blocks.emplace_back("<table>");
blocks.emplace_back(phosg::string_printf(
"<div class=\"title\">%s %s drop chart (%s mode)</div>",
name_for_episode(episode),
name_for_difficulty(difficulty),
name_for_mode(mode)));
blocks.emplace_back("<div><table>");
auto add_location_header = [&](const char* location_name) -> void {
blocks.emplace_back("<tr><th class=\"space\" colspan=\"11\" /></tr>");
blocks.emplace_back("<tr><th>");
blocks.emplace_back("<tr><th class=\"locheader\">");
blocks.emplace_back(location_name);
blocks.emplace_back("</th>");
for (size_t z = 0; z < 10; z++) {
blocks.emplace_back(phosg::string_printf("<th class=\"sec%zu\">%s</th>", z, name_for_section_id(z)));
blocks.emplace_back(phosg::string_printf("<th class=\"sec%zuheader\">%s</th>", z, name_for_section_id(z)));
}
blocks.emplace_back("</tr>");
};
@@ -622,6 +651,10 @@ string RareItemSet::serialize_html(
blocks.emplace_back(phosg::string_printf("<td class=\"sec%hhu\">", section_id));
vector<string> tokens;
for (const auto& spec : specs_lists[section_id]) {
if (!tokens.empty()) {
tokens.emplace_back("");
}
auto frac = phosg::reduce_fraction<uint64_t>(spec.probability, 0x100000000);
ItemData example_item = spec.data;
@@ -634,13 +667,16 @@ string RareItemSet::serialize_html(
}
}
tokens.emplace_back(name_index->describe_item(example_item, false, true));
string hex = example_item.short_hex();
string desc = name_index->describe_item(example_item, false, true);
tokens.emplace_back(phosg::string_printf("<span class=\"item\" title=\"Hex: %s\">%s</span>", hex.c_str(), desc.c_str()));
float denom = static_cast<float>(frac.second) / static_cast<double>(frac.first);
string denom_token = (floor(denom) == denom)
? phosg::string_printf("1 / %g", denom)
: phosg::string_printf("1 / %.02f", denom);
tokens.emplace_back(phosg::string_printf(
"<span class=\"rate\" title=\"True rate: %" PRIu64 " / %" PRIu64 "\">%s</span>",
"<span class=\"rate\" title=\"Exact rate: %" PRIu64 " / %" PRIu64 "\">%s</span>",
frac.first, frac.second, denom_token.c_str()));
}
if (!blocks.empty()) {
@@ -663,18 +699,24 @@ string RareItemSet::serialize_html(
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);
}
add_specs_row(phosg::name_for_enum(type), specs_lists);
const auto& type_def = type_definition_for_enemy(type);
const char* name = (difficulty == 3 && type_def.ultimate_name) ? type_def.ultimate_name : type_def.in_game_name;
add_specs_row(name, specs_lists);
}
for (uint8_t floor : zone_type.floors) {
const auto& floor_def = FloorDefinition::get(episode, floor);
if (floor_def.drop_area_norm == 0xFF) {
throw runtime_error("zone includes floors with no drop area");
}
array<vector<ExpandedDrop>, 10> specs_lists;
for (uint8_t section_id = 0; section_id < 10; section_id++) {
specs_lists[section_id] = this->get_box_specs(mode, episode, difficulty, section_id, floor);
specs_lists[section_id] = this->get_box_specs(mode, episode, difficulty, section_id, floor_def.drop_area_norm);
}
auto loc_name = phosg::string_printf("%s (box)", name_for_floor(episode, floor));
auto loc_name = phosg::string_printf("%s (box)", floor_def.in_game_name);
add_specs_row(loc_name.c_str(), specs_lists);
}
}
blocks.emplace_back("</table></body></html>");
blocks.emplace_back("</table></div></body></html>");
return phosg::join(blocks, "");
}
@@ -720,10 +762,10 @@ phosg::JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const
}
}
for (size_t area = 0; area < 0x12; area++) {
for (size_t area_norm = 0; area_norm < 0x0A; area_norm++) {
auto area_list = phosg::JSON::list();
for (const auto& spec : this->get_box_specs(GameMode::NORMAL, episode, difficulty, section_id, area)) {
for (const auto& spec : this->get_box_specs(GameMode::NORMAL, episode, difficulty, section_id, area_norm)) {
if (spec.data.empty()) {
continue;
}
@@ -742,7 +784,7 @@ phosg::JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const
if (!area_list.empty()) {
collection_dict.emplace(
phosg::string_printf("Box-%s", name_for_floor(episode, area)),
phosg::string_printf("Box-%s", FloorDefinition::get_by_drop_area_norm(episode, area_norm).json_name),
std::move(area_list));
}
}
@@ -818,7 +860,7 @@ void RareItemSet::print_collection(
for (size_t area = 0; area < collection->box_area_to_specs.size(); area++) {
for (const auto& spec : collection->box_area_to_specs[area]) {
string s = name_index ? spec.str(name_index) : spec.str();
fprintf(stream, " (area %02zX) %s\n", area, s.c_str());
fprintf(stream, " (area-norm %02zX) %s\n", area, s.c_str());
}
}
}
@@ -851,15 +893,24 @@ std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_enemy_specs(
}
std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_box_specs(
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const {
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area_norm) const {
try {
return this->get_collection(mode, episode, difficulty, secid).box_area_to_specs.at(area);
return this->get_collection(mode, episode, difficulty, secid).box_area_to_specs.at(area_norm);
} catch (const out_of_range&) {
static const std::vector<ExpandedDrop> empty_vector;
return empty_vector;
}
}
bool RareItemSet::has_entries_for_game_config(GameMode mode, Episode episode, uint8_t difficulty) const {
for (uint8_t section_id = 0; section_id < 10; section_id++) {
if (this->collections.count(this->key_for_params(mode, episode, difficulty, section_id))) {
return true;
}
}
return false;
}
const RareItemSet::SpecCollection& RareItemSet::get_collection(
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const {
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid));
+6 -2
View File
@@ -32,8 +32,12 @@ public:
RareItemSet(const phosg::JSON& json, std::shared_ptr<const ItemNameIndex> name_index = nullptr);
~RareItemSet() = default;
std::vector<ExpandedDrop> get_enemy_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
std::vector<ExpandedDrop> get_box_specs(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area) const;
std::vector<ExpandedDrop> get_enemy_specs(
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const;
std::vector<ExpandedDrop> get_box_specs(
GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area_norm) const;
bool has_entries_for_game_config(GameMode mode, Episode episode, uint8_t difficulty) const;
std::string serialize_afs(bool is_v1) const;
std::string serialize_gsl(bool big_endian) const;
+122 -189
View File
@@ -650,209 +650,142 @@ const unordered_map<string, uint8_t> mag_color_for_name({
{"costume-color", 0x12},
});
uint8_t floor_for_name(const std::string& name) {
static const unordered_map<string, uint8_t> floors({
{"pioneer2", 0x00},
{"p2", 0x00},
{"forest1", 0x01},
{"f1", 0x01},
{"forest2", 0x02},
{"f2", 0x02},
{"caves1", 0x03},
{"cave1", 0x03},
{"c1", 0x03},
{"caves2", 0x04},
{"cave2", 0x04},
{"c2", 0x04},
{"caves3", 0x05},
{"cave3", 0x05},
{"c3", 0x05},
{"mines1", 0x06},
{"mine1", 0x06},
{"m1", 0x06},
{"mines2", 0x07},
{"mine2", 0x07},
{"m2", 0x07},
{"ruins1", 0x08},
{"ruin1", 0x08},
{"r1", 0x08},
{"ruins2", 0x09},
{"ruin2", 0x09},
{"r2", 0x09},
{"ruins3", 0x0A},
{"ruin3", 0x0A},
{"r3", 0x0A},
{"dragon", 0x0B},
{"derolle", 0x0C},
{"volopt", 0x0D},
{"darkfalz", 0x0E},
{"lobby", 0x0F},
{"battle1", 0x10},
{"battle2", 0x11},
static constexpr uint8_t F_CITY = FloorDefinition::Flag::CITY;
static constexpr uint8_t F_LOBBY = FloorDefinition::Flag::LOBBY;
static constexpr uint8_t F_BOSS = FloorDefinition::Flag::BOSS_ARENA;
static constexpr uint8_t F_V1 = FloorDefinition::Flag::EXISTS_ON_V1 | FloorDefinition::Flag::EXISTS_ON_V2 | FloorDefinition::Flag::EXISTS_ON_GC_NTE | FloorDefinition::Flag::EXISTS_ON_V3 | FloorDefinition::Flag::EXISTS_ON_V4;
static constexpr uint8_t F_V2 = FloorDefinition::Flag::EXISTS_ON_V2 | FloorDefinition::Flag::EXISTS_ON_GC_NTE | FloorDefinition::Flag::EXISTS_ON_V3 | FloorDefinition::Flag::EXISTS_ON_V4;
static constexpr uint8_t F_GCN = FloorDefinition::Flag::EXISTS_ON_GC_NTE | FloorDefinition::Flag::EXISTS_ON_V3 | FloorDefinition::Flag::EXISTS_ON_V4;
static constexpr uint8_t F_V3 = FloorDefinition::Flag::EXISTS_ON_V3 | FloorDefinition::Flag::EXISTS_ON_V4;
static constexpr uint8_t F_V4 = FloorDefinition::Flag::EXISTS_ON_V4;
{"pioneer2", 0x00},
{"p2", 0x00},
{"vrtemplealpha", 0x01},
{"templealpha", 0x01},
{"vrtemplebeta", 0x02},
{"templebeta", 0x02},
{"vrspaceshipalpha", 0x03},
{"spaceshipalpha", 0x03},
{"vrspaceshipbeta", 0x04},
{"spaceshipbeta", 0x04},
{"centralcontrolarea", 0x05},
{"cca", 0x05},
{"junglenorth", 0x06},
{"jungleeast", 0x07},
{"mountain", 0x08},
{"seaside", 0x09},
{"seabedupper", 0x0A},
{"seabedlower", 0x0B},
{"galgryphon", 0x0C},
{"olgaflow", 0x0D},
{"barbaray", 0x0E},
{"goldragon", 0x0F},
{"seasidenight", 0x10},
{"tower", 0x11},
static const std::vector<FloorDefinition> floor_defs{
// clang-format off
{Episode::EP1, 0x00, 0x00, 0x00, 0xFF, F_V1 | F_CITY, "Pioneer2", "P2", "Pioneer 2", {"pioneer2", "p2", "city"}},
{Episode::EP1, 0x01, 0x01, 0x01, 0x00, F_V1, "Forest1", "F1", "Forest 1", {"forest1", "f1"}},
{Episode::EP1, 0x02, 0x02, 0x02, 0x01, F_V1, "Forest2", "F2", "Forest 2", {"forest2", "f2"}},
{Episode::EP1, 0x03, 0x03, 0x03, 0x02, F_V1, "Cave1", "C1", "Cave 1", {"caves1", "cave1", "c1"}},
{Episode::EP1, 0x04, 0x04, 0x04, 0x03, F_V1, "Cave2", "C2", "Cave 2", {"caves2", "cave2", "c2"}},
{Episode::EP1, 0x05, 0x05, 0x05, 0x04, F_V1, "Cave3", "C3", "Cave 3", {"caves3", "cave3", "c3"}},
{Episode::EP1, 0x06, 0x06, 0x06, 0x05, F_V1, "Mine1", "M1", "Mine 1", {"mines1", "mine1", "m1"}},
{Episode::EP1, 0x07, 0x07, 0x07, 0x06, F_V1, "Mine2", "M2", "Mine 2", {"mines2", "mine2", "m2"}},
{Episode::EP1, 0x08, 0x08, 0x08, 0x07, F_V1, "Ruins1", "R1", "Ruins 1", {"ruins1", "ruin1", "r1"}},
{Episode::EP1, 0x09, 0x09, 0x09, 0x08, F_V1, "Ruins2", "R2", "Ruins 2", {"ruins2", "ruin2", "r2"}},
{Episode::EP1, 0x0A, 0x0A, 0x0A, 0x09, F_V1, "Ruins3", "R3", "Ruins 3", {"ruins3", "ruin3", "r3"}},
{Episode::EP1, 0x0B, 0x0B, 0x0B, 0x02, F_V1 | F_BOSS, "Dragon", "Dgn", "Under the Dome (Dragon)", {"dragon"}},
{Episode::EP1, 0x0C, 0x0C, 0x0C, 0x05, F_V1 | F_BOSS, "DeRolLe", "DRL", "Underground Channel (De Rol Le)", {"derolle"}},
{Episode::EP1, 0x0D, 0x0D, 0x0D, 0x07, F_V1 | F_BOSS, "VolOpt", "VO", "Monitor Room (Vol Opt)", {"volopt"}},
{Episode::EP1, 0x0E, 0x0E, 0x0E, 0x09, F_V1 | F_BOSS, "DarkFalz", "DF", "???? (Dark Falz)", {"darkfalz"}},
{Episode::EP1, 0x0F, 0x0F, 0x0F, 0xFF, F_V1 | F_LOBBY, "Lobby", "Lby", "Lobby", {"lobby"}},
{Episode::EP1, 0x10, 0x10, 0x10, 0x09, F_V2, "Battle1", "B1", "Spaceship", {"battle1"}},
{Episode::EP1, 0x11, 0x11, 0x11, 0x09, F_V2, "Battle2", "B2", "Palace", {"battle2"}},
{"pioneer2", 0x00},
{"p2", 0x00},
{"cratereast", 0x01},
{"ce", 0x01},
{"craterwest", 0x02},
{"cw", 0x02},
{"cratersouth", 0x03},
{"cs", 0x03},
{"craternorth", 0x04},
{"cn", 0x04},
{"craterinterior", 0x05},
{"ci", 0x05},
{"desert1", 0x06},
{"d1", 0x06},
{"desert2", 0x07},
{"d2", 0x07},
{"desert3", 0x08},
{"d3", 0x08},
{"saintmillion", 0x09},
{"purgatory", 0x0A},
});
return floors.at(phosg::tolower(name));
{Episode::EP2, 0x00, 0x00, 0x12, 0xFF, F_GCN | F_CITY, "Pioneer2", "Lab", "Lab", {"pioneer2", "p2", "lab", "labo"}},
{Episode::EP2, 0x01, 0x13, 0x13, 0x00, F_GCN, "VRTempleAlpha", "VRTA", "VR Temple Alpha", {"vrtemplealpha", "templealpha", "vrta"}},
{Episode::EP2, 0x02, 0x14, 0x14, 0x01, F_GCN, "VRTempleBeta", "VRTB", "VR Temple Beta", {"vrtemplebeta", "templebeta", "vrtb"}},
{Episode::EP2, 0x03, 0x15, 0x15, 0x02, F_GCN, "VRSpaceshipAlpha", "VRSA", "VR Spaceship Alpha", {"vrspaceshipalpha", "spaceshipalpha", "vrsa"}},
{Episode::EP2, 0x04, 0x16, 0x16, 0x03, F_GCN, "VRSpaceshipBeta", "VRSB", "VR Spaceship Beta", {"vrspaceshipbeta", "spaceshipbeta", "vrsb"}},
{Episode::EP2, 0x05, 0x17, 0x17, 0x07, F_GCN, "CentralControlArea", "CCA", "Central Control Area", {"centralcontrolarea", "cca"}},
{Episode::EP2, 0x06, 0x18, 0x18, 0x04, F_GCN, "JungleNorth", "JN", "Jungle Area North", {"junglenorth"}},
{Episode::EP2, 0x07, 0x19, 0x19, 0x05, F_GCN, "JungleEast", "JE", "Jungle Area East", {"jungleeast"}},
{Episode::EP2, 0x08, 0x1A, 0x1A, 0x06, F_GCN, "Mountain", "Mtn", "Mountain Area", {"mountain"}},
{Episode::EP2, 0x09, 0x1B, 0x1B, 0x07, F_GCN, "Seaside", "SS", "Seaside Area", {"seaside"}},
{Episode::EP2, 0x0A, 0x1C, 0x1C, 0x08, F_GCN, "SeabedUpper", "SU", "Seabed Upper Levels", {"seabedupper"}},
{Episode::EP2, 0x0B, 0x1D, 0x1D, 0x09, F_GCN, "SeabedLower", "SL", "Seabed Lower Levels", {"seabedlower"}},
{Episode::EP2, 0x0C, 0x1E, 0x1E, 0x08, F_GCN | F_BOSS, "GalGryphon", "GG", "Cliffs of Gal Da Val (Gal Gryphon)", {"galgryphon"}},
{Episode::EP2, 0x0D, 0x1F, 0x1F, 0x09, F_GCN | F_BOSS, "OlgaFlow", "OF", "Test Subject Disposal Area (Olga Flow)", {"olgaflow"}},
{Episode::EP2, 0x0E, 0x20, 0x20, 0x02, F_GCN | F_BOSS, "BarbaRay", "BR", "VR Temple Final (Barba Ray)", {"barbaray"}},
{Episode::EP2, 0x0F, 0x21, 0x21, 0x04, F_GCN | F_BOSS, "GolDragon", "GD", "VR Spaceship Final (Gol Dragon)", {"goldragon"}},
{Episode::EP2, 0x10, 0xFF, 0x22, 0x07, F_V3, "SeasideNight", "SSN", "Seaside Area (night)", {"seasidenight"}},
{Episode::EP2, 0x11, 0xFF, 0x23, 0x09, F_V3, "Tower", "Twr", "Control Tower", {"tower"}},
{Episode::EP4, 0x00, 0xFF, 0x2D, 0xFF, F_V4 | F_CITY, "Pioneer2", "P2", "Pioneer 2", {"pioneer2", "p2", "city"}},
{Episode::EP4, 0x01, 0xFF, 0x24, 0x01, F_V4, "CraterEast", "CE", "Crater (Eastern Route)", {"cratereast", "ce"}},
{Episode::EP4, 0x02, 0xFF, 0x25, 0x02, F_V4, "CraterWest", "CW", "Crater (Western Route)", {"craterwest", "cw"}},
{Episode::EP4, 0x03, 0xFF, 0x26, 0x03, F_V4, "CraterSouth", "CS", "Crater (Southern Route)", {"cratersouth", "cs"}},
{Episode::EP4, 0x04, 0xFF, 0x27, 0x04, F_V4, "CraterNorth", "CN", "Crater (Northern Route)", {"craternorth", "cn"}},
{Episode::EP4, 0x05, 0xFF, 0x28, 0x05, F_V4, "CraterInterior", "CI", "Crater Interior", {"craterinterior", "ci"}},
{Episode::EP4, 0x06, 0xFF, 0x29, 0x06, F_V4, "Desert1", "D1", "Subterranean Desert 1", {"desert1", "d1"}},
{Episode::EP4, 0x07, 0xFF, 0x2A, 0x07, F_V4, "Desert2", "D2", "Subterranean Desert 2", {"desert2", "d2"}},
{Episode::EP4, 0x08, 0xFF, 0x2B, 0x08, F_V4, "Desert3", "D3", "Subterranean Desert 3", {"desert3", "d3"}},
{Episode::EP4, 0x09, 0xFF, 0x2C, 0x09, F_V4 | F_BOSS, "SaintMilion", "SM", "Meteor Impact Site", {"saintmilion"}},
{Episode::EP4, 0x0A, 0xFF, 0x2E, 0xFF, F_V4, "TestMap", "TM", "Test Map", {"purgatory"}},
// clang-format on
};
const FloorDefinition& FloorDefinition::get(Episode episode, uint8_t floor) {
if (floor >= FloorDefinition::limit_for_episode(episode)) {
throw runtime_error("invalid floor number");
}
switch (episode) {
case Episode::EP1:
return floor_defs.at(floor);
case Episode::EP2:
return floor_defs.at(floor + 0x12);
case Episode::EP4:
return floor_defs.at(floor + 0x24);
default:
throw logic_error("invalid episode");
}
}
static const array<const char*, 0x12> ep1_floor_names = {
"Pioneer2",
"Forest1",
"Forest2",
"Caves1",
"Caves2",
"Caves3",
"Mines1",
"Mines2",
"Ruins1",
"Ruins2",
"Ruins3",
"Dragon",
"DeRolLe",
"VolOpt",
"DarkFalz",
"Lobby",
"Battle1",
"Battle2",
};
const FloorDefinition& FloorDefinition::get_by_drop_area_norm(Episode episode, uint8_t area_norm) {
switch (episode) {
case Episode::EP1:
return floor_defs[area_norm + 1];
case Episode::EP2:
return floor_defs[0x13 + (area_norm >= 4) + area_norm];
case Episode::EP4:
return floor_defs[area_norm + 0x24];
default:
throw logic_error("invalid episode number");
}
}
static const array<const char*, 0x12> ep2_floor_names = {
"Pioneer2",
"VRTempleAlpha",
"VRTempleBeta",
"VRSpaceshipAlpha",
"VRSpaceshipBeta",
"CentralControlArea",
"JungleNorth",
"JungleEast",
"Mountain",
"Seaside",
"SeabedUpper",
"SeabedLower",
"GalGryphon",
"OlgaFlow",
"BarbaRay",
"GolDragon",
"SeasideNight",
"Tower",
};
const FloorDefinition& FloorDefinition::get(Episode episode, const std::string& name) {
static unordered_map<std::string, size_t> index_ep1;
static unordered_map<std::string, size_t> index_ep2;
static unordered_map<std::string, size_t> index_ep4;
static const array<const char*, 0x0B> ep4_floor_names = {
"Pioneer2",
"CraterEast",
"CraterWest",
"CraterSouth",
"CraterNorth",
"CraterInterior",
"Desert1",
"Desert2",
"Desert3",
"SaintMillion",
"Purgatory",
};
unordered_map<std::string, size_t>* index;
switch (episode) {
case Episode::EP1:
index = &index_ep1;
break;
case Episode::EP2:
index = &index_ep2;
break;
case Episode::EP4:
index = &index_ep4;
break;
default:
throw logic_error("invalid episode");
}
static const array<const char*, 0x12> ep1_floor_names_short = {
"P2", "F1", "F2", "C1", "C2", "C3", "M1", "M2", "R1", "R2", "R3", "Dgn", "DRL", "VO", "DF", "Lby", "B1", "B2"};
if (index->empty()) {
for (size_t z = 0; z < floor_defs.size(); z++) {
const auto& def = floor_defs[z];
if (def.episode != episode) {
continue;
}
for (const auto& alias : def.aliases) {
index->emplace(alias, z);
}
}
}
static const array<const char*, 0x12> ep2_floor_names_short = {
"Lab", "VRTA", "VRTB", "VRSA", "VRSB", "CCA", "JN", "JE", "Mtn", "SS", "SU", "SL", "GG", "OF", "BR", "GD", "SSN", "Twr"};
return floor_defs[index->at(phosg::tolower(name))];
}
static const array<const char*, 0x0B> ep4_floor_names_short = {
"P2", "CE", "CW", "CS", "CN", "CI", "D1", "D2", "D3", "SM", "Pg"};
size_t floor_limit_for_episode(Episode ep) {
size_t FloorDefinition::limit_for_episode(Episode ep) {
switch (ep) {
case Episode::EP1:
return ep1_floor_names.size() - 1;
case Episode::EP2:
return ep2_floor_names.size() - 1;
return 0x11;
case Episode::EP4:
return ep4_floor_names.size() - 1;
return 0x0B;
default:
return 0;
}
}
const char* name_for_floor(Episode episode, uint8_t floor) {
switch (episode) {
case Episode::EP1:
return ep1_floor_names.at(floor);
case Episode::EP2:
return ep2_floor_names.at(floor);
case Episode::EP4:
return ep4_floor_names.at(floor);
default:
throw logic_error("invalid episode for drop floor");
}
}
const char* short_name_for_floor(Episode episode, uint8_t floor) {
switch (episode) {
case Episode::EP1:
return ep1_floor_names_short.at(floor);
case Episode::EP2:
return ep2_floor_names_short.at(floor);
case Episode::EP4:
return ep4_floor_names_short.at(floor);
default:
throw logic_error("invalid episode for floor");
}
}
bool floor_is_boss_arena(Episode episode, uint8_t floor) {
switch (episode) {
case Episode::EP1:
return (floor >= 0x0B) && (floor <= 0x0E);
case Episode::EP2:
return (floor >= 0x0C) && (floor <= 0x0F);
case Episode::EP4:
return (floor == 0x09);
default:
return false;
return 0x00;
}
}
+27 -5
View File
@@ -74,11 +74,33 @@ uint8_t language_code_for_char(char language_char);
extern const std::vector<const char*> name_for_mag_color;
extern const std::unordered_map<std::string, uint8_t> mag_color_for_name;
size_t floor_limit_for_episode(Episode ep);
uint8_t floor_for_name(const std::string& name);
const char* name_for_floor(Episode episode, uint8_t floor);
const char* short_name_for_floor(Episode episode, uint8_t floor);
bool floor_is_boss_arena(Episode episode, uint8_t floor);
struct FloorDefinition {
enum Flag {
CITY = 0x01,
LOBBY = 0x02,
BOSS_ARENA = 0x04,
EXISTS_ON_V1 = 0x08,
EXISTS_ON_V2 = 0x10,
EXISTS_ON_GC_NTE = 0x20,
EXISTS_ON_V3 = 0x40,
EXISTS_ON_V4 = 0x80,
};
Episode episode;
uint8_t floor;
uint8_t gc_nte_area;
uint8_t area;
uint8_t drop_area_norm;
uint8_t flags;
const char* json_name;
const char* short_name;
const char* in_game_name;
std::vector<const char*> aliases;
static const FloorDefinition& get(Episode episode, uint8_t floor);
static const FloorDefinition& get(Episode episode, const std::string& name);
static const FloorDefinition& get_by_drop_area_norm(Episode episode, uint8_t area_norm);
static size_t limit_for_episode(Episode ep);
};
uint32_t class_flags_for_class(uint8_t char_class);