diff --git a/README.md b/README.md index fa2454f0..b5ee541a 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,10 @@ Some commands only work on the game server and not on the proxy server. The chat * Debugging commands * `$debug` (game server only): Enable or disable debug. You need the DEBUG permission in your user license to use this command. When debug is enabled, you'll see in-game messages from the server when you take certain actions. You'll also be placed into the highest available slot in lobbies and games instead of the lowest, which is useful for finding commands for which newserv doesn't handle client IDs properly. This setting also disables certain safeguards and allows you to do some things that might crash your client. - * `$call `: Call a quest function on your client. + * `$quest `: Load a quest by quest number. Can be used to load battle or challenge quests with only one player present. + * `$qcall `: Call a quest function on your client. + * `$qset ` or `$qclear `: Set or clear a global quest flag for everyone in the game. + * `$qsync `: Set a quest register's value on your client. `` should be either rXX (e.g. r60) or fXX (e.g. f60); if the latter, `` is parsed as a floating-point value instead of as an integer. * `$gc` (game server only): Send your own Guild Card to yourself. * `$persist` (game server only): Enable or disable persistence for the current lobby or game. This determines whether the lobby/game is deleted when the last player leaves. You need the DEBUG permission in your user license to use this command because there are no game state checks when you do this. For example, if you make a game persistent, start a quest, then leave the game, the game can't be joined by anyone but also can't be deleted. * `$sc `: Send a command to yourself. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 17431b46..3a9b942b 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -269,6 +269,91 @@ static void server_command_quest(shared_ptr c, const std::string& args) set_lobby_quest(c->require_lobby(), q); } +static void server_command_qset_qclear(shared_ptr c, const std::string& args, bool should_set) { + if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); + return; + } + auto l = c->require_lobby(); + if (!l->is_game()) { + send_text_message(c, "$C6This command cannot\nbe used in the lobby"); + return; + } + + uint16_t flag_num = stoul(args, nullptr, 0); + + if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { + G_SetQuestFlag_DC_PC_6x75 cmd = {{0x75, 0x02, 0x0000}, flag_num, should_set ? 0 : 1}; + send_command_t(l, 0x60, 0x00, cmd); + } else { + G_SetQuestFlag_V3_BB_6x75 cmd = {{{0x75, 0x02, 0x0000}, flag_num, should_set ? 0 : 1}, l->difficulty, 0x0000}; + send_command_t(l, 0x60, 0x00, cmd); + } +} + +static void server_command_qset(shared_ptr c, const std::string& args) { + return server_command_qset_qclear(c, args, true); +} + +static void server_command_qclear(shared_ptr c, const std::string& args) { + return server_command_qset_qclear(c, args, false); +} + +static void server_command_qsync(shared_ptr c, const std::string& args) { + if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); + return; + } + auto l = c->require_lobby(); + if (!l->is_game()) { + send_text_message(c, "$C6This command cannot\nbe used in the lobby"); + return; + } + + auto tokens = split(args, ' '); + if (tokens.size() != 2) { + send_text_message(c, "$C6Incorrect number of\narguments"); + return; + } + + uint16_t reg_num = stoul(tokens[0].substr(1), nullptr, 0); + uint32_t reg_val; + if (tokens[0][0] == 'r') { + reg_val = stoul(tokens[1], nullptr, 0); + } else if (tokens[0][0] == 'f') { + float float_val = stof(tokens[1]); + reg_val = *reinterpret_cast(&float_val); + } else { + send_text_message(c, "$C6First argument must\nbe a register"); + return; + } + + G_SyncQuestData_6x77 cmd = {{0x77, 0x03, 0x0000}, reg_num, 0, reg_val}; + send_command_t(c, 0x60, 0x00, cmd); +} + +static void server_command_qcall(shared_ptr c, const std::string& args) { + if (!c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message(c, "$C6This command can only\nbe run in debug mode\n(run %sdebug first)"); + return; + } + + auto l = c->require_lobby(); + if (l->is_game() && l->quest) { + send_quest_function_call(c, stoul(args, nullptr, 0)); + } else { + send_text_message(c, "$C6You must be in a\nquest to use this\ncommand"); + } +} + +static void proxy_command_qcall(shared_ptr ses, const std::string& args) { + if (ses->is_in_game && ses->is_in_quest) { + send_quest_function_call(ses->client_channel, stoul(args, nullptr, 0)); + } else { + send_text_message(ses->client_channel, "$C6You must be in a\nquest to use this\ncommand"); + } +} + static void server_command_show_material_counts(shared_ptr c, const std::string&) { auto p = c->game_data.character(); if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) { @@ -426,23 +511,6 @@ static void proxy_command_exit(shared_ptr ses, const } } -static void server_command_call(shared_ptr c, const std::string& args) { - auto l = c->require_lobby(); - if (l->is_game() && l->quest) { - send_quest_function_call(c, stoul(args, nullptr, 0)); - } else { - send_text_message(c, "$C6You must be in\nquest to use this\ncommand"); - } -} - -static void proxy_command_call(shared_ptr ses, const std::string& args) { - if (ses->is_in_game && ses->is_in_quest) { - send_quest_function_call(ses->client_channel, stoul(args, nullptr, 0)); - } else { - send_text_message(ses->client_channel, "$C6You must be in\nquest to use this\ncommand"); - } -} - static void server_command_get_self_card(shared_ptr c, const std::string&) { send_guild_card(c, c); } @@ -1594,7 +1662,6 @@ static const unordered_map chat_commands({ {"$ax", {server_command_ax, nullptr}}, {"$ban", {server_command_ban, nullptr}}, {"$bbchar", {server_command_convert_char_to_bb, nullptr}}, - {"$call", {server_command_call, proxy_command_call}}, {"$cheat", {server_command_cheat, nullptr}}, {"$debug", {server_command_debug, nullptr}}, {"$defrange", {server_command_ep3_set_def_dice_range, nullptr}}, @@ -1623,6 +1690,10 @@ static const unordered_map chat_commands({ {"$persist", {server_command_persist, nullptr}}, {"$ping", {server_command_ping, nullptr}}, {"$playrec", {server_command_playrec, nullptr}}, + {"$qcall", {server_command_qcall, proxy_command_qcall}}, + {"$qclear", {server_command_qclear, nullptr}}, + {"$qset", {server_command_qset, nullptr}}, + {"$qsync", {server_command_qsync, nullptr}}, {"$quest", {server_command_quest, nullptr}}, {"$rand", {server_command_rand, proxy_command_rand}}, {"$saverec", {server_command_saverec, nullptr}},