diff --git a/README.md b/README.md index 4e09d4f1..127898b6 100644 --- a/README.md +++ b/README.md @@ -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. -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) - `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 - `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)* 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.* -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. @@ -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. * 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-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). 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. diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 086bd9e9..904745a3 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1462,10 +1462,11 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, const string& data) break; case MenuID::QUEST: { 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); } else { - auto q = s->quest_index->get(cmd.item_id); + auto q = quest_index->get(cmd.item_id); if (!q) { send_quest_info(c, u"$C4Quest does not\nexist.", is_download_quest); } else { @@ -1716,8 +1717,13 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) } } if (num_ep3_categories == 1) { - auto quests = s->quest_index->filter(ep3_category_id, c->quest_version(), c->language()); - send_quest_menu(c, MenuID::QUEST, quests, true); + auto quest_index = s->quest_index_for_client(c); + 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; } } @@ -1962,12 +1968,13 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) case MenuID::QUEST_FILTER: { 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."); break; } shared_ptr 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 // client is not in any lobby @@ -1977,11 +1984,12 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, const string& data) case MenuID::QUEST: { 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."); break; } - auto q = s->quest_index->get(item_id); + auto q = quest_index->get(item_id); if (!q) { send_lobby_message_box(c, u"$C6Quest does not exist."); break; @@ -2328,11 +2336,6 @@ static void on_A2(shared_ptr c, uint16_t, uint32_t flag, const string& d check_size_v(data.size(), 0); 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(); if (!l || !l->is_game()) { send_lobby_message_box(c, u"$C6Quests are not available\nin lobbies."); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index dfc93785..d1edb47d 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -116,8 +116,8 @@ Server commands:\n\ battle-params - reload the enemy stats files\n\ level-table - reload the level-up tables\n\ item-tables - reload the item generation tables\n\ - ep3 - reload the Episode 3 card definitions and maps\n\ - quests - reindex all quests\n\ + ep3 - reload Episode 3 card definitions and maps (not download quests)\n\ + quests - reindex all quests (including Episode 3 download quests)\n\ functions - recompile all client-side functions\n\ dol-files - reindex all DOL files\n\ config - reload most fields from config.json\n\ diff --git a/src/ServerState.cc b/src/ServerState.cc index e7015ce8..31988bea 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -310,7 +310,7 @@ shared_ptr ServerState::find_client(const std::u16string* identifier, throw out_of_range("client not found"); } -uint32_t ServerState::connect_address_for_client(std::shared_ptr c) { +uint32_t ServerState::connect_address_for_client(std::shared_ptr c) const { if (c->channel.is_virtual_connection) { if (c->channel.remote_addr.ss_family != AF_INET) { throw logic_error("virtual connection is missing remote IPv4 address"); @@ -329,7 +329,7 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr c) { } } -std::shared_ptr ServerState::information_menu_for_version(GameVersion version) { +std::shared_ptr ServerState::information_menu_for_version(GameVersion version) const { if ((version == GameVersion::DC) || (version == GameVersion::PC)) { return this->information_menu_v2; } else if ((version == GameVersion::GC) || (version == GameVersion::XB)) { @@ -338,7 +338,7 @@ std::shared_ptr ServerState::information_menu_for_version(GameVersio throw out_of_range("no information menu exists for this version"); } -shared_ptr ServerState::proxy_destinations_menu_for_version(GameVersion version) { +shared_ptr ServerState::proxy_destinations_menu_for_version(GameVersion version) const { switch (version) { case GameVersion::DC: return this->proxy_destinations_menu_dc; @@ -353,7 +353,7 @@ shared_ptr ServerState::proxy_destinations_menu_for_version(GameVers } } -const vector>& ServerState::proxy_destinations_for_version(GameVersion version) { +const vector>& ServerState::proxy_destinations_for_version(GameVersion version) const { switch (version) { case GameVersion::DC: return this->proxy_destinations_dc; @@ -979,8 +979,10 @@ void ServerState::resolve_ep3_card_names() { } void ServerState::load_quest_index() { - config_log.info("Collecting quest metadata"); - this->quest_index.reset(new QuestIndex("system/quests", this->quest_category_index)); + config_log.info("Collecting quests"); + 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() { @@ -992,3 +994,9 @@ void ServerState::load_dol_files() { config_log.info("Loading DOL files"); this->dol_file_index.reset(new DOLFileIndex("system/dol")); } + +shared_ptr ServerState::quest_index_for_client(shared_ptr c) const { + return (c->flags & Client::Flag::IS_EPISODE_3) + ? this->ep3_download_quest_index + : this->default_quest_index; +} diff --git a/src/ServerState.hh b/src/ServerState.hh index 2f0d4077..bf70d1ae 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -84,7 +84,8 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr ep3_tournament_ex_values; std::shared_ptr ep3_tournament_final_round_ex_values; std::shared_ptr quest_category_index; - std::shared_ptr quest_index; + std::shared_ptr default_quest_index; + std::shared_ptr ep3_download_quest_index; std::shared_ptr level_table; std::shared_ptr battle_params; std::shared_ptr bb_data_gsl; @@ -185,14 +186,15 @@ struct ServerState : public std::enable_shared_from_this { uint64_t serial_number = 0, std::shared_ptr l = nullptr); - uint32_t connect_address_for_client(std::shared_ptr c); + uint32_t connect_address_for_client(std::shared_ptr c) const; - std::shared_ptr information_menu_for_version(GameVersion version); - std::shared_ptr proxy_destinations_menu_for_version(GameVersion version); - const std::vector>& proxy_destinations_for_version(GameVersion version); + std::shared_ptr information_menu_for_version(GameVersion version) const; + std::shared_ptr proxy_destinations_menu_for_version(GameVersion version) const; + const std::vector>& proxy_destinations_for_version(GameVersion version) const; - void set_port_configuration( - const std::vector& port_configs); + std::shared_ptr quest_index_for_client(std::shared_ptr c) const; + + void set_port_configuration(const std::vector& port_configs); std::shared_ptr load_bb_file( const std::string& patch_index_filename, diff --git a/system/quests/e765-dlt-gc3-j.mnm b/system/ep3/maps-download/e765-dlt-gc3-j.mnm similarity index 100% rename from system/quests/e765-dlt-gc3-j.mnm rename to system/ep3/maps-download/e765-dlt-gc3-j.mnm diff --git a/system/quests/e901-dl-gc3-e.mnm b/system/ep3/maps-download/e901-dl-gc3-e.mnm similarity index 100% rename from system/quests/e901-dl-gc3-e.mnm rename to system/ep3/maps-download/e901-dl-gc3-e.mnm diff --git a/system/quests/e901-dl-gc3-j.mnm b/system/ep3/maps-download/e901-dl-gc3-j.mnm similarity index 100% rename from system/quests/e901-dl-gc3-j.mnm rename to system/ep3/maps-download/e901-dl-gc3-j.mnm diff --git a/system/quests/e903-dl-gc3-e.mnm b/system/ep3/maps-download/e903-dl-gc3-e.mnm similarity index 100% rename from system/quests/e903-dl-gc3-e.mnm rename to system/ep3/maps-download/e903-dl-gc3-e.mnm diff --git a/system/quests/e903-dl-gc3-j.mnm b/system/ep3/maps-download/e903-dl-gc3-j.mnm similarity index 100% rename from system/quests/e903-dl-gc3-j.mnm rename to system/ep3/maps-download/e903-dl-gc3-j.mnm diff --git a/system/quests/e904-dl-gc3-e.mnm b/system/ep3/maps-download/e904-dl-gc3-e.mnm similarity index 100% rename from system/quests/e904-dl-gc3-e.mnm rename to system/ep3/maps-download/e904-dl-gc3-e.mnm diff --git a/system/quests/e904-dl-gc3-j.mnm b/system/ep3/maps-download/e904-dl-gc3-j.mnm similarity index 100% rename from system/quests/e904-dl-gc3-j.mnm rename to system/ep3/maps-download/e904-dl-gc3-j.mnm diff --git a/system/quests/e905-dl-gc3-e.mnm b/system/ep3/maps-download/e905-dl-gc3-e.mnm similarity index 100% rename from system/quests/e905-dl-gc3-e.mnm rename to system/ep3/maps-download/e905-dl-gc3-e.mnm diff --git a/system/quests/e905-dl-gc3-j.mnm b/system/ep3/maps-download/e905-dl-gc3-j.mnm similarity index 100% rename from system/quests/e905-dl-gc3-j.mnm rename to system/ep3/maps-download/e905-dl-gc3-j.mnm diff --git a/system/quests/e906-dl-gc3-e.mnm b/system/ep3/maps-download/e906-dl-gc3-e.mnm similarity index 100% rename from system/quests/e906-dl-gc3-e.mnm rename to system/ep3/maps-download/e906-dl-gc3-e.mnm diff --git a/system/quests/e906-dl-gc3-j.mnm b/system/ep3/maps-download/e906-dl-gc3-j.mnm similarity index 100% rename from system/quests/e906-dl-gc3-j.mnm rename to system/ep3/maps-download/e906-dl-gc3-j.mnm diff --git a/system/quests/e907-dl-gc3-e.mnm b/system/ep3/maps-download/e907-dl-gc3-e.mnm similarity index 100% rename from system/quests/e907-dl-gc3-e.mnm rename to system/ep3/maps-download/e907-dl-gc3-e.mnm diff --git a/system/quests/e907-dl-gc3-j.mnm b/system/ep3/maps-download/e907-dl-gc3-j.mnm similarity index 100% rename from system/quests/e907-dl-gc3-j.mnm rename to system/ep3/maps-download/e907-dl-gc3-j.mnm diff --git a/system/quests/e908-dl-gc3-e.mnm b/system/ep3/maps-download/e908-dl-gc3-e.mnm similarity index 100% rename from system/quests/e908-dl-gc3-e.mnm rename to system/ep3/maps-download/e908-dl-gc3-e.mnm diff --git a/system/quests/e908-dl-gc3-j.mnm b/system/ep3/maps-download/e908-dl-gc3-j.mnm similarity index 100% rename from system/quests/e908-dl-gc3-j.mnm rename to system/ep3/maps-download/e908-dl-gc3-j.mnm diff --git a/system/ep3/maps/e765-dlt-gc3-j.mnm b/system/ep3/maps/e765-dlt-gc3-j.mnm index cd65624b..75655d62 120000 --- a/system/ep3/maps/e765-dlt-gc3-j.mnm +++ b/system/ep3/maps/e765-dlt-gc3-j.mnm @@ -1 +1 @@ -../../quests/e765-dlt-gc3-j.mnm \ No newline at end of file +../maps-download/e765-dlt-gc3-j.mnm \ No newline at end of file diff --git a/system/ep3/maps/e901-dl-gc3-e.mnm b/system/ep3/maps/e901-dl-gc3-e.mnm index 1adb9eed..798a4d8a 120000 --- a/system/ep3/maps/e901-dl-gc3-e.mnm +++ b/system/ep3/maps/e901-dl-gc3-e.mnm @@ -1 +1 @@ -../../quests/e901-dl-gc3-e.mnm \ No newline at end of file +../maps-download/e901-dl-gc3-e.mnm \ No newline at end of file diff --git a/system/ep3/maps/e901-dl-gc3-j.mnm b/system/ep3/maps/e901-dl-gc3-j.mnm new file mode 120000 index 00000000..90dfcac7 --- /dev/null +++ b/system/ep3/maps/e901-dl-gc3-j.mnm @@ -0,0 +1 @@ +../maps-download/e901-dl-gc3-j.mnm \ No newline at end of file diff --git a/system/ep3/maps/e903-dl-gc3-e.mnm b/system/ep3/maps/e903-dl-gc3-e.mnm index f602cf2a..88504a1f 120000 --- a/system/ep3/maps/e903-dl-gc3-e.mnm +++ b/system/ep3/maps/e903-dl-gc3-e.mnm @@ -1 +1 @@ -../../quests/e903-dl-gc3-e.mnm \ No newline at end of file +../maps-download/e903-dl-gc3-e.mnm \ No newline at end of file diff --git a/system/ep3/maps/e903-dl-gc3-j.mnm b/system/ep3/maps/e903-dl-gc3-j.mnm new file mode 120000 index 00000000..20b4daf8 --- /dev/null +++ b/system/ep3/maps/e903-dl-gc3-j.mnm @@ -0,0 +1 @@ +../maps-download/e903-dl-gc3-j.mnm \ No newline at end of file diff --git a/system/ep3/maps/e904-dl-gc3-e.mnm b/system/ep3/maps/e904-dl-gc3-e.mnm index 2474c67e..81f028a7 120000 --- a/system/ep3/maps/e904-dl-gc3-e.mnm +++ b/system/ep3/maps/e904-dl-gc3-e.mnm @@ -1 +1 @@ -../../quests/e904-dl-gc3-e.mnm \ No newline at end of file +../maps-download/e904-dl-gc3-e.mnm \ No newline at end of file diff --git a/system/ep3/maps/e904-dl-gc3-j.mnm b/system/ep3/maps/e904-dl-gc3-j.mnm new file mode 120000 index 00000000..1cc3b955 --- /dev/null +++ b/system/ep3/maps/e904-dl-gc3-j.mnm @@ -0,0 +1 @@ +../maps-download/e904-dl-gc3-j.mnm \ No newline at end of file diff --git a/system/ep3/maps/e905-dl-gc3-e.mnm b/system/ep3/maps/e905-dl-gc3-e.mnm index c49439f1..caf65a7c 120000 --- a/system/ep3/maps/e905-dl-gc3-e.mnm +++ b/system/ep3/maps/e905-dl-gc3-e.mnm @@ -1 +1 @@ -../../quests/e905-dl-gc3-e.mnm \ No newline at end of file +../maps-download/e905-dl-gc3-e.mnm \ No newline at end of file diff --git a/system/ep3/maps/e905-dl-gc3-j.mnm b/system/ep3/maps/e905-dl-gc3-j.mnm new file mode 120000 index 00000000..fa3e96ae --- /dev/null +++ b/system/ep3/maps/e905-dl-gc3-j.mnm @@ -0,0 +1 @@ +../maps-download/e905-dl-gc3-j.mnm \ No newline at end of file diff --git a/system/ep3/maps/e906-dl-gc3-e.mnm b/system/ep3/maps/e906-dl-gc3-e.mnm index a991c6f1..c851b910 120000 --- a/system/ep3/maps/e906-dl-gc3-e.mnm +++ b/system/ep3/maps/e906-dl-gc3-e.mnm @@ -1 +1 @@ -../../quests/e906-dl-gc3-e.mnm \ No newline at end of file +../maps-download/e906-dl-gc3-e.mnm \ No newline at end of file diff --git a/system/ep3/maps/e906-dl-gc3-j.mnm b/system/ep3/maps/e906-dl-gc3-j.mnm new file mode 120000 index 00000000..71318a78 --- /dev/null +++ b/system/ep3/maps/e906-dl-gc3-j.mnm @@ -0,0 +1 @@ +../maps-download/e906-dl-gc3-j.mnm \ No newline at end of file diff --git a/system/ep3/maps/e907-dl-gc3-e.mnm b/system/ep3/maps/e907-dl-gc3-e.mnm index fbea39c9..260b0341 120000 --- a/system/ep3/maps/e907-dl-gc3-e.mnm +++ b/system/ep3/maps/e907-dl-gc3-e.mnm @@ -1 +1 @@ -../../quests/e907-dl-gc3-e.mnm \ No newline at end of file +../maps-download/e907-dl-gc3-e.mnm \ No newline at end of file diff --git a/system/ep3/maps/e907-dl-gc3-j.mnm b/system/ep3/maps/e907-dl-gc3-j.mnm new file mode 120000 index 00000000..f7599f34 --- /dev/null +++ b/system/ep3/maps/e907-dl-gc3-j.mnm @@ -0,0 +1 @@ +../maps-download/e907-dl-gc3-j.mnm \ No newline at end of file diff --git a/system/ep3/maps/e908-dl-gc3-e.mnm b/system/ep3/maps/e908-dl-gc3-e.mnm index ed56596c..fc02a119 120000 --- a/system/ep3/maps/e908-dl-gc3-e.mnm +++ b/system/ep3/maps/e908-dl-gc3-e.mnm @@ -1 +1 @@ -../../quests/e908-dl-gc3-e.mnm \ No newline at end of file +../maps-download/e908-dl-gc3-e.mnm \ No newline at end of file diff --git a/system/ep3/maps/e908-dl-gc3-j.mnm b/system/ep3/maps/e908-dl-gc3-j.mnm new file mode 120000 index 00000000..73b92566 --- /dev/null +++ b/system/ep3/maps/e908-dl-gc3-j.mnm @@ -0,0 +1 @@ +../maps-download/e908-dl-gc3-j.mnm \ No newline at end of file