split Ep3 and Ep1&2 quest indexes

This commit is contained in:
Martin Michelsen
2023-10-16 17:54:04 -07:00
parent 22ceb2d1f7
commit e2f72f3088
35 changed files with 60 additions and 39 deletions
+4 -3
View File
@@ -107,10 +107,10 @@ To use newserv in other ways (e.g. for translating data), see the end of this do
newserv automatically finds quests in the system/quests/ directory. To install your own quests, or to use quests you've saved using the proxy's "save files" option, just put them in that directory and name them appropriately. newserv automatically finds quests in the system/quests/ directory. To install your own quests, or to use quests you've saved using the proxy's "save files" option, just put them in that directory and name them appropriately.
Standard quest files should be named like `q###-CATEGORY-VERSION-LANGUAGE.EXT`, battle quests should be named like `b###-VERSION-LANGUAGE.EXT`, challenge quests should be named like `c###-VERSION-LANGUAGE.EXT` for Episode 1 or `d###-VERSION-LANGUAGE.EXT` for Episode 2, and Episode 3 download quests should be named like `e###-gc3-LANGUAGE.EXT`. The fields in each filename are: Standard quest files should be named like `q###-CATEGORY-VERSION-LANGUAGE.EXT`, battle quests should be named like `b###-VERSION-LANGUAGE.EXT`, challenge quests should be named like `c###-VERSION-LANGUAGE.EXT` for Episode 1 or `d###-VERSION-LANGUAGE.EXT` for Episode 2. The fields in each filename are:
- `###`: quest number (this doesn't really matter; it should just be unique across the PSO version) - `###`: quest number (this doesn't really matter; it should just be unique across the PSO version)
- `CATEGORY`: ret = Retrieval, ext = Extermination, evt = Events, shp = Shops, vr = VR, twr = Tower, gv1/gv2/gv4 = Government (BB only), dl = Download (these don't appear during online play), 1p = Solo (BB only) - `CATEGORY`: ret = Retrieval, ext = Extermination, evt = Events, shp = Shops, vr = VR, twr = Tower, gv1/gv2/gv4 = Government (BB only), dl = Download (these don't appear during online play), 1p = Solo (BB only)
- `VERSION`: dn = Dreamcast NTE, d1 = Dreamcast v1, dc = Dreamcast v2, pc = PC, gcn = GameCube Trial Edition, gc = GameCube Episodes 1 & 2, gc3 = Episode 3, xb = Xbox, bb = Blue Burst - `VERSION`: dn = Dreamcast NTE, d1 = Dreamcast v1, dc = Dreamcast v2, pc = PC, gcn = GameCube Trial Edition, gc = GameCube Episodes 1 & 2, gc3 = Episode 3 (see below), xb = Xbox, bb = Blue Burst
- `LANGUAGE`: j = Japanese, e = English, g = German, f = French, s = Spanish - `LANGUAGE`: j = Japanese, e = English, g = German, f = French, s = Spanish
- `EXT`: file extension (see table below) - `EXT`: file extension (see table below)
@@ -144,7 +144,7 @@ There are multiple PSO quest formats out there; newserv supports all of them. It
1. *This is the default format. You can convert these to uncompressed format by running `newserv decompress-prs FILENAME.bin FILENAME.bind` (and similarly for .dat -> .datd)* 1. *This is the default format. You can convert these to uncompressed format by running `newserv decompress-prs FILENAME.bin FILENAME.bind` (and similarly for .dat -> .datd)*
2. *Similar to (1), to compress an uncompressed quest file: `newserv compress-prs FILENAME.bind FILENAME.bin` (and likewise for .datd -> .dat)* 2. *Similar to (1), to compress an uncompressed quest file: `newserv compress-prs FILENAME.bind FILENAME.bin` (and likewise for .datd -> .dat)*
3. *Use the decode action to convert these quests to .bin/.dat format before putting them into the server's quests directory. If you know the encryption seed (serial number), pass it in as a hex string with the `--seed=` option. If you don't know the encryption seed, newserv will find it for you, which will likely take a long time.* 3. *Use the decode action to convert these quests to .bin/.dat format before putting them into the server's quests directory. If you know the encryption seed (serial number), pass it in as a hex string with the `--seed=` option. If you don't know the encryption seed, newserv will find it for you, which will likely take a long time.*
4. *Episode 3 online quests don't go in the system/quests directory; they instead go in the system/ep3/maps directory. If you want an Episode 3 quest to be available for both online play and for downloading, the file must exist in both system/quests and in system/ep3/maps (symbolic links are acceptable).* 4. *Episode 3 quests don't go in the system/quests directory. See the Episode 3 section below.*
Episode 3 download quests consist only of a .bin file - there is no corresponding .dat file. Episode 3 download quest files may be named with the .mnm extension instead of .bin, since the format is the same as the standard map files (in system/ep3/). These files can be encoded in any of the formats described above, except .qst. Episode 3 download quests consist only of a .bin file - there is no corresponding .dat file. Episode 3 download quest files may be named with the .mnm extension instead of .bin, since the format is the same as the standard map files (in system/ep3/). These files can be encoded in any of the formats described above, except .qst.
@@ -186,6 +186,7 @@ Episode 3 state and game data is stored in the system/ep3 directory. The files i
* card-text.mnrd: Decompressed card text archive; same format as TextCardE.bin. Generally only used for debugging. * card-text.mnrd: Decompressed card text archive; same format as TextCardE.bin. Generally only used for debugging.
* com-decks.json: COM decks used in tournaments. The default decks in this file come from logs from Sega's servers, so the file doesn't include every COM deck Sega ever made - the rest are probably lost to time. * com-decks.json: COM decks used in tournaments. The default decks in this file come from logs from Sega's servers, so the file doesn't include every COM deck Sega ever made - the rest are probably lost to time.
* maps/: Online free battle and quest maps (.mnm/.bin/.mnmd/.bind files). newserv comes with all the original online and offline maps, including Story Mode quests. If you don't want the offline maps and quests to be playable online, delete the .bind files system/ep3/maps. * maps/: Online free battle and quest maps (.mnm/.bin/.mnmd/.bind files). newserv comes with all the original online and offline maps, including Story Mode quests. If you don't want the offline maps and quests to be playable online, delete the .bind files system/ep3/maps.
* maps-download/: Download maps and quests (.mnm/.bin/.mnmd/.bind files). Files in this directory have the same format as those in the maps/ directory, but should be named like `e###-gc3-LANGUAGE.EXT` (similar to how non-Episode 3 quests are named in the system/quests/ directory). If you want a map to be available for online play and for downloading, the file must exist in both maps/ and maps-download/ (a symbolic link is acceptable).
* tournament-state.json: State of all active tournaments. This file is automatically written when any tournament changes state for any reason (e.g. a tournament is created/started/deleted or a match is resolved). * tournament-state.json: State of all active tournaments. This file is automatically written when any tournament changes state for any reason (e.g. a tournament is created/started/deleted or a match is resolved).
There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndexes.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available. There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndexes.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available.
+16 -13
View File
@@ -1462,10 +1462,11 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
break; break;
case MenuID::QUEST: { case MenuID::QUEST: {
bool is_download_quest = !c->lobby.lock(); bool is_download_quest = !c->lobby.lock();
if (!s->quest_index) { auto quest_index = s->quest_index_for_client(c);
if (!quest_index) {
send_quest_info(c, u"$C6Quests are not available.", is_download_quest); send_quest_info(c, u"$C6Quests are not available.", is_download_quest);
} else { } else {
auto q = s->quest_index->get(cmd.item_id); auto q = quest_index->get(cmd.item_id);
if (!q) { if (!q) {
send_quest_info(c, u"$C4Quest does not\nexist.", is_download_quest); send_quest_info(c, u"$C4Quest does not\nexist.", is_download_quest);
} else { } else {
@@ -1716,8 +1717,13 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
} }
} }
if (num_ep3_categories == 1) { if (num_ep3_categories == 1) {
auto quests = s->quest_index->filter(ep3_category_id, c->quest_version(), c->language()); auto quest_index = s->quest_index_for_client(c);
send_quest_menu(c, MenuID::QUEST, quests, true); if (quest_index) {
auto quests = quest_index->filter(ep3_category_id, c->quest_version(), c->language());
send_quest_menu(c, MenuID::QUEST, quests, true);
} else {
send_lobby_message_box(c, u"$C6Quests are not available.");
}
break; break;
} }
} }
@@ -1962,12 +1968,13 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
case MenuID::QUEST_FILTER: { case MenuID::QUEST_FILTER: {
auto s = c->require_server_state(); auto s = c->require_server_state();
if (!s->quest_index) { auto quest_index = s->quest_index_for_client(c);
if (!quest_index) {
send_lobby_message_box(c, u"$C6Quests are not available."); send_lobby_message_box(c, u"$C6Quests are not available.");
break; break;
} }
shared_ptr<Lobby> l = c->lobby.lock(); shared_ptr<Lobby> l = c->lobby.lock();
auto quests = s->quest_index->filter(item_id, c->quest_version(), c->language()); auto quests = quest_index->filter(item_id, c->quest_version(), c->language());
// Hack: Assume the menu to be sent is the download quest menu if the // Hack: Assume the menu to be sent is the download quest menu if the
// client is not in any lobby // client is not in any lobby
@@ -1977,11 +1984,12 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
case MenuID::QUEST: { case MenuID::QUEST: {
auto s = c->require_server_state(); auto s = c->require_server_state();
if (!s->quest_index) { auto quest_index = s->quest_index_for_client(c);
if (!quest_index) {
send_lobby_message_box(c, u"$C6Quests are not\navailable."); send_lobby_message_box(c, u"$C6Quests are not\navailable.");
break; break;
} }
auto q = s->quest_index->get(item_id); auto q = quest_index->get(item_id);
if (!q) { if (!q) {
send_lobby_message_box(c, u"$C6Quest does not exist."); send_lobby_message_box(c, u"$C6Quest does not exist.");
break; break;
@@ -2328,11 +2336,6 @@ static void on_A2(shared_ptr<Client> c, uint16_t, uint32_t flag, const string& d
check_size_v(data.size(), 0); check_size_v(data.size(), 0);
auto s = c->require_server_state(); auto s = c->require_server_state();
if (!s->quest_index) {
send_lobby_message_box(c, u"$C6Quests are not available.");
return;
}
auto l = c->lobby.lock(); auto l = c->lobby.lock();
if (!l || !l->is_game()) { if (!l || !l->is_game()) {
send_lobby_message_box(c, u"$C6Quests are not available\nin lobbies."); send_lobby_message_box(c, u"$C6Quests are not available\nin lobbies.");
+2 -2
View File
@@ -116,8 +116,8 @@ Server commands:\n\
battle-params - reload the enemy stats files\n\ battle-params - reload the enemy stats files\n\
level-table - reload the level-up tables\n\ level-table - reload the level-up tables\n\
item-tables - reload the item generation tables\n\ item-tables - reload the item generation tables\n\
ep3 - reload the Episode 3 card definitions and maps\n\ ep3 - reload Episode 3 card definitions and maps (not download quests)\n\
quests - reindex all quests\n\ quests - reindex all quests (including Episode 3 download quests)\n\
functions - recompile all client-side functions\n\ functions - recompile all client-side functions\n\
dol-files - reindex all DOL files\n\ dol-files - reindex all DOL files\n\
config - reload most fields from config.json\n\ config - reload most fields from config.json\n\
+14 -6
View File
@@ -310,7 +310,7 @@ shared_ptr<Client> ServerState::find_client(const std::u16string* identifier,
throw out_of_range("client not found"); throw out_of_range("client not found");
} }
uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) { uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) const {
if (c->channel.is_virtual_connection) { if (c->channel.is_virtual_connection) {
if (c->channel.remote_addr.ss_family != AF_INET) { if (c->channel.remote_addr.ss_family != AF_INET) {
throw logic_error("virtual connection is missing remote IPv4 address"); throw logic_error("virtual connection is missing remote IPv4 address");
@@ -329,7 +329,7 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
} }
} }
std::shared_ptr<const Menu> ServerState::information_menu_for_version(GameVersion version) { std::shared_ptr<const Menu> ServerState::information_menu_for_version(GameVersion version) const {
if ((version == GameVersion::DC) || (version == GameVersion::PC)) { if ((version == GameVersion::DC) || (version == GameVersion::PC)) {
return this->information_menu_v2; return this->information_menu_v2;
} else if ((version == GameVersion::GC) || (version == GameVersion::XB)) { } else if ((version == GameVersion::GC) || (version == GameVersion::XB)) {
@@ -338,7 +338,7 @@ std::shared_ptr<const Menu> ServerState::information_menu_for_version(GameVersio
throw out_of_range("no information menu exists for this version"); throw out_of_range("no information menu exists for this version");
} }
shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(GameVersion version) { shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(GameVersion version) const {
switch (version) { switch (version) {
case GameVersion::DC: case GameVersion::DC:
return this->proxy_destinations_menu_dc; return this->proxy_destinations_menu_dc;
@@ -353,7 +353,7 @@ shared_ptr<const Menu> ServerState::proxy_destinations_menu_for_version(GameVers
} }
} }
const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_version(GameVersion version) { const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_version(GameVersion version) const {
switch (version) { switch (version) {
case GameVersion::DC: case GameVersion::DC:
return this->proxy_destinations_dc; return this->proxy_destinations_dc;
@@ -979,8 +979,10 @@ void ServerState::resolve_ep3_card_names() {
} }
void ServerState::load_quest_index() { void ServerState::load_quest_index() {
config_log.info("Collecting quest metadata"); config_log.info("Collecting quests");
this->quest_index.reset(new QuestIndex("system/quests", this->quest_category_index)); this->default_quest_index.reset(new QuestIndex("system/quests", this->quest_category_index));
config_log.info("Collecting Episode 3 download quests");
this->ep3_download_quest_index.reset(new QuestIndex("system/ep3/maps-download", this->quest_category_index));
} }
void ServerState::compile_functions() { void ServerState::compile_functions() {
@@ -992,3 +994,9 @@ void ServerState::load_dol_files() {
config_log.info("Loading DOL files"); config_log.info("Loading DOL files");
this->dol_file_index.reset(new DOLFileIndex("system/dol")); this->dol_file_index.reset(new DOLFileIndex("system/dol"));
} }
shared_ptr<const QuestIndex> ServerState::quest_index_for_client(shared_ptr<Client> c) const {
return (c->flags & Client::Flag::IS_EPISODE_3)
? this->ep3_download_quest_index
: this->default_quest_index;
}
+9 -7
View File
@@ -84,7 +84,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_tournament_ex_values; std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_tournament_ex_values;
std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values; std::shared_ptr<const G_SetEXResultValues_GC_Ep3_6xB4x4B> ep3_tournament_final_round_ex_values;
std::shared_ptr<const QuestCategoryIndex> quest_category_index; std::shared_ptr<const QuestCategoryIndex> quest_category_index;
std::shared_ptr<const QuestIndex> quest_index; std::shared_ptr<const QuestIndex> default_quest_index;
std::shared_ptr<const QuestIndex> ep3_download_quest_index;
std::shared_ptr<const LevelTable> level_table; std::shared_ptr<const LevelTable> level_table;
std::shared_ptr<const BattleParamsIndex> battle_params; std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const GSLArchive> bb_data_gsl; std::shared_ptr<const GSLArchive> bb_data_gsl;
@@ -185,14 +186,15 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
uint64_t serial_number = 0, uint64_t serial_number = 0,
std::shared_ptr<Lobby> l = nullptr); std::shared_ptr<Lobby> l = nullptr);
uint32_t connect_address_for_client(std::shared_ptr<Client> c); uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
std::shared_ptr<const Menu> information_menu_for_version(GameVersion version); std::shared_ptr<const Menu> information_menu_for_version(GameVersion version) const;
std::shared_ptr<const Menu> proxy_destinations_menu_for_version(GameVersion version); std::shared_ptr<const Menu> proxy_destinations_menu_for_version(GameVersion version) const;
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(GameVersion version); const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(GameVersion version) const;
void set_port_configuration( std::shared_ptr<const QuestIndex> quest_index_for_client(std::shared_ptr<Client> c) const;
const std::vector<PortConfiguration>& port_configs);
void set_port_configuration(const std::vector<PortConfiguration>& port_configs);
std::shared_ptr<const std::string> load_bb_file( std::shared_ptr<const std::string> load_bb_file(
const std::string& patch_index_filename, const std::string& patch_index_filename,
+1 -1
View File
@@ -1 +1 @@
../../quests/e765-dlt-gc3-j.mnm ../maps-download/e765-dlt-gc3-j.mnm
+1 -1
View File
@@ -1 +1 @@
../../quests/e901-dl-gc3-e.mnm ../maps-download/e901-dl-gc3-e.mnm
+1
View File
@@ -0,0 +1 @@
../maps-download/e901-dl-gc3-j.mnm
+1 -1
View File
@@ -1 +1 @@
../../quests/e903-dl-gc3-e.mnm ../maps-download/e903-dl-gc3-e.mnm
+1
View File
@@ -0,0 +1 @@
../maps-download/e903-dl-gc3-j.mnm
+1 -1
View File
@@ -1 +1 @@
../../quests/e904-dl-gc3-e.mnm ../maps-download/e904-dl-gc3-e.mnm
+1
View File
@@ -0,0 +1 @@
../maps-download/e904-dl-gc3-j.mnm
+1 -1
View File
@@ -1 +1 @@
../../quests/e905-dl-gc3-e.mnm ../maps-download/e905-dl-gc3-e.mnm
+1
View File
@@ -0,0 +1 @@
../maps-download/e905-dl-gc3-j.mnm
+1 -1
View File
@@ -1 +1 @@
../../quests/e906-dl-gc3-e.mnm ../maps-download/e906-dl-gc3-e.mnm
+1
View File
@@ -0,0 +1 @@
../maps-download/e906-dl-gc3-j.mnm
+1 -1
View File
@@ -1 +1 @@
../../quests/e907-dl-gc3-e.mnm ../maps-download/e907-dl-gc3-e.mnm
+1
View File
@@ -0,0 +1 @@
../maps-download/e907-dl-gc3-j.mnm
+1 -1
View File
@@ -1 +1 @@
../../quests/e908-dl-gc3-e.mnm ../maps-download/e908-dl-gc3-e.mnm
+1
View File
@@ -0,0 +1 @@
../maps-download/e908-dl-gc3-j.mnm