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
File diff suppressed because one or more lines are too long
+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);
+1 -1
View File
@@ -351,7 +351,7 @@
["Event names", "$C7Show lobby event\nnames", "Use these names with %sevent and %sallevent.\n\nnone - no event\nxmas - Christmas event\nval - Valentine's Day\neaster - Easter Sunday event\nhallo - Halloween event\nsonic - Sonic Adventure DX event\nnewyear - New Year's event\nbval - White Day\nwedding - Wedding Day event\nspring - spring event\ns-spring - spring event with striped background\nsummer - summer event\ns-summer - summer event with striped background\nfall - fall event"],
["GC lobby types", "$C7Show lobby type\nlist for Episodes\nI & II", "Use these names with %sln on Episodes 1 & 2.\n$C6*$C7 indicates lobbies where players can't move.\n\nnormal - standard lobby\ninormal - under standard lobby $C6*$C7\nipc - under PC lobby $C6*$C7\niball - under soccer lobby $C6*$C7\ncave1 - Cave 1 $C6*$C7\ncave2u - Cave 2 Ultimate $C6*$C7\ndragon - Dragon stage (floor is black)\nderolle - De Rol Le stage (water/walls are gone)\nvolopt - Vol Opt stage\ndarkfalz - Dark Falz stage"],
["Ep3 lobby types", "$C7Show lobby type\nlist for Episode III", "Use these names with %sln on Episode 3.\n\nnormal - Standard lobby\nplanet - Blank Ragol lobby\nclouds - Blank sky lobby\ncave - Unguis Lapis (platform missing)\njungle - Nebula Montana 1 (Ep2 Jungle)\nforest2-1 - Lupus Silva 2 (Ep1 Forest 2)\nforest2-2 - Lupus Silva 1 (Ep1 Forest 2)\nwindpower - Molae Venti\noverview - Nebula Montana 2\nseaside - Tener Sinus (Ep2 Seaside)\nfons - Mortis Fons\ndmorgue - Destroyed Morgue (column missing)\ncaelum - Tower of Caelum (top)\ncyber - Cyber\nboss1 - Castor/Pollux map\nboss2 - Amplum Umbra map\ndolor - Dolor Odor\nravum - Ravum Aedes Sacra\nsky - Via Tubus (tube missing)\nmorgue - Morgue (column missing)"],
["Area list", "$C7Show stage code\nlist", "Use these names with %swarpme and %swarpall.\n$C2Green$C7 areas will be empty unless you are in a quest.\n$C6Yellow$C7 areas will not allow you to move.\n\n $C8Episode 1 / Episode 2 / Episode 4$C7\n0: Pioneer 2 / Pioneer 2 / Pioneer 2\n1: Forest 1 / Temple Alpha / Crater East\n2: Forest 2 / Temple Beta / Crater West\n3: Caves 1 / Spaceship Alpha / Crater South\n4: Caves 2 / Spaceship Beta / Crater North\n5: Caves 3 / CCA / Crater Interior\n6: Mines 1 / Jungle North / Desert 1\n7: Mines 2 / Jungle South / Desert 2\n8: Ruins 1 / Mountain / Desert 3\n9: Ruins 2 / Seaside / Saint Million\n10: Ruins 3 / Seabed Upper / $C6Purgatory$C7\n11: Dragon / Seabed Lower\n12: De Rol Le / Gal Gryphon\n13: Vol Opt / Olga Flow\n14: Dark Falz / Barba Ray\n15: $C2Lobby$C7 / Gol Dragon\n16: $C6Battle 1$C7 / $C6Seaside Night$C7\n17: $C6Battle 2$C7 / $C2Tower$C7"],
["Area list", "$C7Show stage code\nlist", "Use these names with %swarpme and %swarpall.\n$C2Green$C7 areas will be empty unless you are in a quest.\n$C6Yellow$C7 areas will not allow you to move.\n\n $C8Episode 1 / Episode 2 / Episode 4$C7\n0: Pioneer 2 / Pioneer 2 / Pioneer 2\n1: Forest 1 / Temple Alpha / Crater East\n2: Forest 2 / Temple Beta / Crater West\n3: Caves 1 / Spaceship Alpha / Crater South\n4: Caves 2 / Spaceship Beta / Crater North\n5: Caves 3 / CCA / Crater Interior\n6: Mines 1 / Jungle North / Desert 1\n7: Mines 2 / Jungle South / Desert 2\n8: Ruins 1 / Mountain / Desert 3\n9: Ruins 2 / Seaside / Saint-Milion\n10: Ruins 3 / Seabed Upper / $C6Test map$C7\n11: Dragon / Seabed Lower\n12: De Rol Le / Gal Gryphon\n13: Vol Opt / Olga Flow\n14: Dark Falz / Barba Ray\n15: $C2Lobby$C7 / Gol Dragon\n16: $C6Battle 1$C7 / $C6Seaside Night$C7\n17: $C6Battle 2$C7 / $C2Tower$C7"],
],
// Welcome message. If not blank, this message will be shown to PSO GC users
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -185,7 +185,7 @@
["Event values", "$C7Display lobby event\nlist", "These values can be used with the $C6%sevent$C7 command.\n\nnone - no event\nxmas - Christmas event\nval - Valentine's Day\neaster - Easter Sunday event\nhallo - Halloween event\nsonic - Sonic Adventure DX event\nnewyear - New Year's event\nbval - White Day\nwedding - Wedding Day event\nspring - spring event\ns-spring - spring event with striped background\nsummer - summer event\ns-summer - summer event with striped background\nfall - fall event"],
["GC lobby types", "$C7Display lobby type\nlist for Episodes\nI & II", "These values can be used with the %stype command.\n$C6*$C7 indicates lobbies where players can't move.\n$C2Green$C7 indicates Episode 1 & 2 (GC) only lobbies.\n\nnormal - standard lobby\n$C2inormal$C7 - under standard lobby $C6*$C7\n$C2ipc$C7 - under PC lobby $C6*$C7\n$C2iball$C7 - under soccer lobby $C6*$C7\n$C2cave1$C7 - Cave 1 $C6*$C7\n$C2cave2u$C7 - Cave 2 Ultimate $C6*$C7\n$C2dragon$C7 - Dragon stage (floor is black)\n$C2derolle$C7 - De Rol Le stage (water/walls are gone)\n$C2volopt$C7 - Vol Opt stage\n$C2darkfalz$C7 - Dark Falz stage"],
["Ep3 lobby types", "$C7Display lobby type\nlist for Episode\nIII", "These values can be used with the %sln command.\n$C6*$C7 indicates lobbies where players can't move.\n$C8Pink$C7 indicates Episode 3 only lobbies.\n\nnormal - standard lobby\n$C8planet$C7 - Blank Ragol Lobby\n$C8clouds$C7 - Blank Sky Lobby\n$C8cave$C7 - Unguis Lapis\n$C8jungle$C7 - Episode 2 Jungle\n$C8forest2-1$C7 - Episode 1 Forest 2 (ground)\n$C8forest2-2$C7 - Episode 1 Forest 2 (near Dome)\n$C8windpower$C7\n$C8overview$C7\n$C8seaside$C7 - Episode 2 Seaside\n$C8some?$C7\n$C8dmorgue$C7 - Destroyed Morgue\n$C8caelum$C7 - Caelum\n$C8digital$C7\n$C8boss1$C7\n$C8boss2$C7\n$C8boss3$C7\n$C8knight$C7 - Leukon Knight stage\n$C8sky$C7 - Via Tubus\n$C8morgue$C7 - Morgue"],
["Area list", "$C7Display stage code\nlist", "These values can be used with the $C6%swarp$C7 command.\n\n$C2Green$C7 areas will be empty unless you are in a quest.\n$C6Yellow$C7 areas will not allow you to move.\n\n $C8Episode 1 / Episode 2 / Episode 4$C7\n0: Pioneer 2 / Pioneer 2 / Pioneer 2\n1: Forest 1 / Temple Alpha / Crater East\n2: Forest 2 / Temple Beta / Crater West\n3: Caves 1 / Spaceship Alpha / Crater South\n4: Caves 2 / Spaceship Beta / Crater North\n5: Caves 3 / CCA / Crater Interior\n6: Mines 1 / Jungle North / Desert 1\n7: Mines 2 / Jungle South / Desert 2\n8: Ruins 1 / Mountain / Desert 3\n9: Ruins 2 / Seaside / Saint Million\n10: Ruins 3 / Seabed Upper / $C6Purgatory$C7\n11: Dragon / Seabed Lower\n12: De Rol Le / Gal Gryphon\n13: Vol Opt / Olga Flow\n14: Dark Falz / Barba Ray\n15: $C2Lobby$C7 / Gol Dragon\n16: $C6Battle 1$C7 / $C6Seaside Night$C7\n17: $C6Battle 2$C7 / $C2Tower$C7"],
["Area list", "$C7Display stage code\nlist", "These values can be used with the $C6%swarp$C7 command.\n\n$C2Green$C7 areas will be empty unless you are in a quest.\n$C6Yellow$C7 areas will not allow you to move.\n\n $C8Episode 1 / Episode 2 / Episode 4$C7\n0: Pioneer 2 / Pioneer 2 / Pioneer 2\n1: Forest 1 / Temple Alpha / Crater East\n2: Forest 2 / Temple Beta / Crater West\n3: Caves 1 / Spaceship Alpha / Crater South\n4: Caves 2 / Spaceship Beta / Crater North\n5: Caves 3 / CCA / Crater Interior\n6: Mines 1 / Jungle North / Desert 1\n7: Mines 2 / Jungle South / Desert 2\n8: Ruins 1 / Mountain / Desert 3\n9: Ruins 2 / Seaside / Saint-Milion\n10: Ruins 3 / Seabed Upper / $C6Test map$C7\n11: Dragon / Seabed Lower\n12: De Rol Le / Gal Gryphon\n13: Vol Opt / Olga Flow\n14: Dark Falz / Barba Ray\n15: $C2Lobby$C7 / Gol Dragon\n16: $C6Battle 1$C7 / $C6Seaside Night$C7\n17: $C6Battle 2$C7 / $C2Tower$C7"],
["Debug commands", "$C7Display commands\nfor debugging\nnewserv itself", "The following commands may be useful for\ninvestigating bugs in newserv.\n\n%sdbgid: Enable or disable high ID preference.\n When enabled, you'll be placed into the\n latest slot in lobbies/games instead of\n the earliest.\n%sgc: Send your own Guild Card to yourself.\n%srand <value in hex>: Set the random seed for\n all games you create."]
],
"WelcomeMessage": "You are connected to $C6Alexandria$C7.",