diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 84db8a33..b3d56cf0 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -2105,14 +2105,11 @@ struct S_QuestMenuEntry_BB_A2_A4 { // A9 (C->S): Quest menu closed (canceled) // Internal name: SndQuestEnd // No arguments -// This command is sent when the in-game quest menu (A2) is closed. When the +// This command is sent when the in-game quest menu (A2) is closed. This is used +// by the server to unlock the game if the players don't select a quest, since +// players are forbidden from joining while the quest menu is open. When the // download quest menu is closed, either by downloading a quest or canceling, -// the client sends A0 instead. The existence of the A0 response on the download -// case makes sense, because the client may not be in a lobby and the server may -// need to send another menu or redirect the client. But for the online quest -// menu, the client is already in a game and can move normally after canceling -// the quest menu, so it's not obvious why A9 is needed at all. newserv (and -// probably all other private servers) ignores it. +// the client sends A0 instead. // Curiously, PSO GC sends uninitialized data in header.flag. // AA (C->S): Send quest statistic (V3/BB) diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 1239d9e6..ff7dcb3d 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -567,6 +567,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr l, shared_ptrname); ret.emplace("RandomSeed", l->random_seed); if (l->episode != Episode::EP3) { + ret.emplace("QuestSelectionInProgress", l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS)); ret.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)); ret.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)); auto variations_json = JSON::list(); @@ -846,6 +847,7 @@ JSON HTTPServer::generate_summary_json() const { game_json.emplace("MapNumber", (ep3s && ep3s->last_chosen_map) ? ep3s->last_chosen_map->map_number : JSON(nullptr)); game_json.emplace("Rules", (ep3s && ep3s->map_and_rules) ? ep3s->map_and_rules->rules.json() : nullptr); } else { + game_json.emplace("QuestSelectionInProgress", l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS)); game_json.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)); game_json.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)); game_json.emplace("SectionID", name_for_section_id(l->effective_section_id())); diff --git a/src/Lobby.cc b/src/Lobby.cc index 5593714a..f04afce9 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -798,6 +798,9 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr c, const s return JoinError::VERSION_CONFLICT; } if (this->is_game()) { + if (this->check_flag(Flag::QUEST_SELECTION_IN_PROGRESS)) { + return JoinError::QUEST_SELECTION_IN_PROGRESS; + } if (this->check_flag(Flag::QUEST_IN_PROGRESS)) { return JoinError::QUEST_IN_PROGRESS; } @@ -973,7 +976,9 @@ bool Lobby::compare_shared(const shared_ptr& a, const shared_ptr& l) -> size_t { - if (l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) || l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) { + if (l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS) || + l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) || + l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) { return 4; } size_t num_clients = l->count_clients(); diff --git a/src/Lobby.hh b/src/Lobby.hh index 4a2b7515..3c7cc5fd 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -67,15 +67,16 @@ struct Lobby : public std::enable_shared_from_this { PERSISTENT = 0x00000002, // Flags used only for games CHEATS_ENABLED = 0x00000100, - QUEST_IN_PROGRESS = 0x00000200, - BATTLE_IN_PROGRESS = 0x00000400, - JOINABLE_QUEST_IN_PROGRESS = 0x00000800, - IS_CLIENT_CUSTOMIZATION = 0x00001000, - IS_SPECTATOR_TEAM = 0x00002000, // episode must be EP3 also - SPECTATORS_FORBIDDEN = 0x00004000, - START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000, - CANNOT_CHANGE_CHEAT_MODE = 0x00010000, - USE_CREATOR_SECTION_ID = 0x00020000, + QUEST_SELECTION_IN_PROGRESS = 0x00000200, + QUEST_IN_PROGRESS = 0x00000400, + BATTLE_IN_PROGRESS = 0x00000800, + JOINABLE_QUEST_IN_PROGRESS = 0x00001000, + IS_CLIENT_CUSTOMIZATION = 0x00002000, + IS_SPECTATOR_TEAM = 0x00004000, // .episode must be EP3 also + SPECTATORS_FORBIDDEN = 0x00008000, + START_BATTLE_PLAYER_IMMEDIATELY = 0x00010000, + CANNOT_CHANGE_CHEAT_MODE = 0x00020000, + USE_CREATOR_SECTION_ID = 0x00040000, // Flags used only for lobbies PUBLIC = 0x01000000, DEFAULT = 0x02000000, @@ -282,6 +283,7 @@ struct Lobby : public std::enable_shared_from_this { ALLOWED = 0, FULL, VERSION_CONFLICT, + QUEST_SELECTION_IN_PROGRESS, QUEST_IN_PROGRESS, BATTLE_IN_PROGRESS, LOADING, diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 310d3b07..523fadc1 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1840,6 +1840,8 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { info += "$C6Quest in progress\n"; } else if (game->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) { info += "$C4Quest in progress\n"; + } else if (game->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS)) { + info += "$C4Selecting quest\n"; } switch (game->drop_mode) { @@ -2024,6 +2026,7 @@ void set_lobby_quest(shared_ptr l, shared_ptr q, bool substi } else { l->set_flag(Lobby::Flag::QUEST_IN_PROGRESS); } + l->clear_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS); l->clear_flag(Lobby::Flag::PERSISTENT); l->quest = q; @@ -2440,6 +2443,9 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { case Lobby::JoinError::VERSION_CONFLICT: send_lobby_message_box(c, "$C7You cannot join this\ngame because it is\nfor a different\nversion of PSO."); break; + case Lobby::JoinError::QUEST_SELECTION_IN_PROGRESS: + send_lobby_message_box(c, "$C7You cannot join this\ngame because the\nplayers are currently\nchoosing a quest."); + break; case Lobby::JoinError::QUEST_IN_PROGRESS: send_lobby_message_box(c, "$C7You cannot join this\ngame because a\nquest is already\nin progress."); break; @@ -2843,33 +2849,41 @@ static void on_A2(shared_ptr c, uint16_t, uint32_t flag, string& data) { return; } - // In Episode 3, there are no quest categories, so skip directly to the quest - // filter menu. if (is_ep3(c->version())) { send_lobby_message_box(c, "$C7Episode 3 does not\nprovide online quests\nvia this interface."); + return; + } + + QuestMenuType menu_type; + if ((c->version() == Version::BB_V4) && flag) { + menu_type = QuestMenuType::GOVERNMENT; } else { - QuestMenuType menu_type; - if ((c->version() == Version::BB_V4) && flag) { - menu_type = QuestMenuType::GOVERNMENT; - } else { - switch (l->mode) { - case GameMode::NORMAL: - menu_type = QuestMenuType::NORMAL; - break; - case GameMode::BATTLE: - menu_type = QuestMenuType::BATTLE; - break; - case GameMode::CHALLENGE: - menu_type = QuestMenuType::CHALLENGE; - break; - case GameMode::SOLO: - menu_type = QuestMenuType::SOLO; - break; - default: - throw logic_error("invalid game mode"); - } + switch (l->mode) { + case GameMode::NORMAL: + menu_type = QuestMenuType::NORMAL; + break; + case GameMode::BATTLE: + menu_type = QuestMenuType::BATTLE; + break; + case GameMode::CHALLENGE: + menu_type = QuestMenuType::CHALLENGE; + break; + case GameMode::SOLO: + menu_type = QuestMenuType::SOLO; + break; + default: + throw logic_error("invalid game mode"); } - send_quest_categories_menu(c, s->quest_index(c->version()), menu_type, l->episode); + } + + send_quest_categories_menu(c, s->quest_index(c->version()), menu_type, l->episode); + l->set_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS); +} + +static void on_A9(shared_ptr c, uint16_t, uint32_t, string&) { + auto l = c->require_lobby(); + if (l->is_game() && (c->lobby_client_id == l->leader_id)) { + l->clear_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS); } } @@ -5467,7 +5481,7 @@ static on_command_t handlers[0x100][NUM_NON_PATCH_VERSIONS] = { /* A6 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, on_44_A6_V3_BB, nullptr}, /* A7 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, on_13_A7_V3_BB, nullptr}, /* A8 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, -/* A9 */ {on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored}, +/* A9 */ {on_A9, on_A9, on_A9, on_A9, on_A9, on_A9, on_A9, on_A9, on_A9, on_A9, on_A9, on_A9}, /* AA */ {nullptr, nullptr, nullptr, nullptr, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA, on_AA}, /* AB */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}, /* AC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB, on_AC_V3_BB},