diff --git a/README.md b/README.md index 6daeb073..56bed702 100644 --- a/README.md +++ b/README.md @@ -479,7 +479,7 @@ Some commands only work on the game server and not on the proxy server. The chat * You'll be placed into the highest available slot in lobbies and games instead of the lowest, unless you're joining a BB solo-mode game. * You'll be able to join games with any PSO version, not only those for which crossplay is normally supported. Be prepared for client crashes and other client-side brokenness if you do this. Please do not submit any issues for broken behaviors in crossplay, unless the situation is explicitly supported (see the "Cross-version play" section above). * The rest of the commands in this section are enabled on the game server. (They are always enabled on the proxy server.) - * `$quest ` (game server only): Load a quest by quest number. Can be used to load battle or challenge quests with only one player present. + * `$quest ` (game server only): Load a quest by quest number. Can be used to load battle or challenge quests with only one player present. Debug is not required to be enabled if the specified quest has the AllowStartFromChatCommand field set in its metadata file. * `$qcall `: Call a quest function on your client. * `$qcheck ` (game server only): Show the value of a quest flag. This command can be used without debug mode enabled. If you're in a game, show the value of the flag in that game; if you're in the lobby, show the saved value of that quest flag for your character (BB only). * `$qset ` or `$qclear `: Set or clear a quest flag for everyone in the game. If you're in the lobby and on BB, set or clear the saved value of a quest flag in your character file. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 7ffe05dd..d48136f9 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -298,18 +298,29 @@ static void server_command_debug(shared_ptr c, const std::string&) { } static void server_command_quest(shared_ptr c, const std::string& args) { - check_debug_enabled(c); - - Version effective_version = is_ep3(c->version()) ? Version::GC_V3 : c->version(); - auto s = c->require_server_state(); auto l = c->require_lobby(); + if (!l->is_game()) { + throw precondition_failed("$C6Quests cannot be\nstarted from the\nlobby"); + } + + Version effective_version = is_ep3(c->version()) ? Version::GC_V3 : c->version(); auto q = s->quest_index(effective_version)->get(stoul(args)); if (!q) { send_text_message(c, "$C6Quest not found"); - } else { - set_lobby_quest(c->require_lobby(), q, true); + return; } + + if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + if (l->count_clients() > 1) { + throw precondition_failed("$C6This command can only\nbe used with no\nother players present"); + } + if (!q->allow_start_from_chat_command) { + throw precondition_failed("$C6This quest cannot\nbe started with the\n%squest command"); + } + } + + set_lobby_quest(c->require_lobby(), q, true); } static void server_command_qcheck(shared_ptr c, const std::string& args) { diff --git a/src/Quest.cc b/src/Quest.cc index 4deaec08..c506434c 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -206,11 +206,13 @@ VersionedQuest::VersionedQuest( ssize_t challenge_template_index, std::shared_ptr available_expression, std::shared_ptr enabled_expression, + bool allow_start_from_chat_command, bool force_joinable, int16_t lock_status_register) : quest_number(quest_number), category_id(category_id), episode(Episode::NONE), + allow_start_from_chat_command(allow_start_from_chat_command), joinable(force_joinable), lock_status_register(lock_status_register), version(version), @@ -384,6 +386,7 @@ Quest::Quest(shared_ptr initial_version) : quest_number(initial_version->quest_number), category_id(initial_version->category_id), episode(initial_version->episode), + allow_start_from_chat_command(initial_version->allow_start_from_chat_command), joinable(initial_version->joinable), lock_status_register(initial_version->lock_status_register), name(initial_version->name), @@ -408,6 +411,9 @@ void Quest::add_version(shared_ptr vq) { if (this->episode != vq->episode) { throw runtime_error("quest version is in a different episode"); } + if (this->allow_start_from_chat_command != vq->allow_start_from_chat_command) { + throw runtime_error("quest version has a different allow_start_from_chat_command state"); + } if (this->joinable != vq->joinable) { throw runtime_error("quest version has a different joinability state"); } @@ -676,6 +682,7 @@ QuestIndex::QuestIndex( ssize_t challenge_template_index = -1; shared_ptr available_expression; shared_ptr enabled_expression; + bool allow_start_from_chat_command = false; bool force_joinable = false; int16_t lock_status_register = -1; try { @@ -708,6 +715,10 @@ QuestIndex::QuestIndex( enabled_expression = make_shared(metadata_json.get_string("EnabledIf")); } catch (const out_of_range&) { } + try { + allow_start_from_chat_command = metadata_json.get_bool("AllowStartFromChatCommand"); + } catch (const out_of_range&) { + } try { force_joinable = metadata_json.get_bool("Joinable"); } catch (const out_of_range&) { @@ -730,6 +741,7 @@ QuestIndex::QuestIndex( challenge_template_index, available_expression, enabled_expression, + allow_start_from_chat_command, force_joinable, lock_status_register); diff --git a/src/Quest.hh b/src/Quest.hh index 00d6296d..bce7daee 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -63,6 +63,7 @@ struct VersionedQuest { uint32_t quest_number; uint32_t category_id; Episode episode; + bool allow_start_from_chat_command; bool joinable; int16_t lock_status_register; std::string name; @@ -92,6 +93,7 @@ struct VersionedQuest { ssize_t challenge_template_index = -1, std::shared_ptr available_expression = nullptr, std::shared_ptr enabled_expression = nullptr, + bool allow_start_from_chat_command = false, bool force_joinable = false, int16_t lock_status_register = -1); @@ -123,6 +125,7 @@ public: uint32_t quest_number; uint32_t category_id; Episode episode; + bool allow_start_from_chat_command; bool joinable; int16_t lock_status_register; std::string name; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 0b9d5838..d0fb16b7 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2055,6 +2055,7 @@ void set_lobby_quest(shared_ptr l, shared_ptr q, bool substi lc->should_disconnect = true; break; } + lc->log.info("Sending %c version of quest \"%s\"", char_for_language_code(vq->language), vq->name.c_str()); string bin_filename = vq->bin_filename(); string dat_filename = vq->dat_filename(); diff --git a/system/quests/battle/b88001.json b/system/quests/battle/b88001.json index d0c94dc2..dc3dad03 100644 --- a/system/quests/battle/b88001.json +++ b/system/quests/battle/b88001.json @@ -67,4 +67,11 @@ // set to to zero to allow joins again. This field is ignored for quests // that do not have the Joinable otion set above. // "LockStatusRegister": 249, + + // Normally, the $quest command requires $debug to be enabled, but if this + // option is set, this quest may be loaded via $quest even if $debug is off, + // but only if there are no other players present in the game. When a quest + // is loaded via $quest, AvailableIf and EnabledIf are not checked, so it's + // inadvisable to set this to true if either of those fields are also used. + // "AllowStartFromChatCommand": true, }