diff --git a/README.md b/README.md index 662b3848..0c70cf6b 100644 --- a/README.md +++ b/README.md @@ -445,6 +445,8 @@ Some commands only work on the game server and not on the proxy server. The chat * `$qgwrite ` (game server only): Set the value of a quest counter ("global flag") for yourself. * `$qsync `: Set a quest register's value for yourself only. `` 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. * `$qsyncall `: Set a quest register's value for everyone in the game. `` 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. + * `$swset [floor] ` and `$swclear [floor] `: Set or clear a switch flag. If floor is not given, sets or clears the flag on your current floor. + * `$swsetall`: Sets all switch flags on your current floor. This unlocks all doors, disables all laser fences, triggers all light/poison switches, etc. * `$gc` (game server only): Send your own Guild Card to yourself. * `$sc `: Send a command to yourself. * `$ss `: Send a command to the remote server (if in a proxy session) or to the game server. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 4ac7a787..b5097db4 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -332,6 +332,122 @@ static void server_command_qcheck(shared_ptr c, const std::string& args) } } +static void server_command_swset_swclear(shared_ptr c, const std::string& args, bool should_set) { + check_debug_enabled(c); + 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, ' '); + uint8_t floor, flag_num; + if (tokens.size() == 1) { + floor = c->floor; + flag_num = stoul(tokens[0], nullptr, 0); + } else if (tokens.size() == 2) { + floor = stoul(tokens[0], nullptr, 0); + flag_num = stoul(tokens[1], nullptr, 0); + } else { + send_text_message(c, "$C4Incorrect parameters"); + return; + } + + if (should_set) { + l->switch_flags->set(floor, flag_num); + } else { + l->switch_flags->clear(floor, flag_num); + } + + uint8_t cmd_flags = should_set ? 0x01 : 0x00; + G_SwitchStateChanged_6x05 cmd = {{0x05, 0x03, 0xFFFF}, 0, 0, flag_num, floor, cmd_flags}; + send_command_t(l, 0x60, 0x00, cmd); +} + +static void server_command_swset(shared_ptr c, const std::string& args) { + return server_command_swset_swclear(c, args, true); +} + +static void server_command_swclear(shared_ptr c, const std::string& args) { + return server_command_swset_swclear(c, args, false); +} + +static void proxy_command_swset_swclear(shared_ptr ses, const std::string& args, bool should_set) { + if (!ses->is_in_game) { + send_text_message(ses->client_channel, "$C6This command cannot\nbe used in the lobby"); + return; + } + + auto tokens = split(args, ' '); + uint8_t floor, flag_num; + if (tokens.size() == 1) { + floor = ses->floor; + flag_num = stoul(tokens[0], nullptr, 0); + } else if (tokens.size() == 2) { + floor = stoul(tokens[0], nullptr, 0); + flag_num = stoul(tokens[1], nullptr, 0); + } else { + send_text_message(ses->client_channel, "$C4Incorrect parameters"); + return; + } + + uint8_t cmd_flags = should_set ? 0x01 : 0x00; + G_SwitchStateChanged_6x05 cmd = {{0x05, 0x03, 0xFFFF}, 0, 0, flag_num, floor, cmd_flags}; + ses->client_channel.send(0x60, 0x00, &cmd, sizeof(cmd)); + ses->server_channel.send(0x60, 0x00, &cmd, sizeof(cmd)); +} + +static void proxy_command_swset(shared_ptr ses, const std::string& args) { + return proxy_command_swset_swclear(ses, args, true); +} + +static void proxy_command_swclear(shared_ptr ses, const std::string& args) { + return proxy_command_swset_swclear(ses, args, false); +} + +static void server_command_swsetall(shared_ptr c, const std::string&) { + check_debug_enabled(c); + auto l = c->require_lobby(); + if (!l->is_game()) { + send_text_message(c, "$C6This command cannot\nbe used in the lobby"); + return; + } + + l->switch_flags->data[c->floor].clear(0xFF); + + parray cmds; + for (size_t z = 0; z < cmds.size(); z++) { + auto& cmd = cmds[z]; + cmd.header.subcommand = 0x05; + cmd.header.size = 0x03; + cmd.header.object_id = 0xFFFF; + cmd.switch_flag_floor = c->floor; + cmd.switch_flag_num = z; + cmd.flags = 0x01; + } + send_command_t(l, 0x6C, 0x00, cmds); +} + +static void proxy_command_swsetall(shared_ptr ses, const std::string&) { + if (!ses->is_in_game) { + send_text_message(ses->client_channel, "$C6This command cannot\nbe used in the lobby"); + return; + } + + parray cmds; + for (size_t z = 0; z < cmds.size(); z++) { + auto& cmd = cmds[z]; + cmd.header.subcommand = 0x05; + cmd.header.size = 0x03; + cmd.header.object_id = 0xFFFF; + cmd.switch_flag_floor = ses->floor; + cmd.switch_flag_num = z; + cmd.flags = 0x01; + } + ses->client_channel.send(0x6C, 0x00, &cmds, sizeof(cmds)); + ses->server_channel.send(0x6C, 0x00, &cmds, sizeof(cmds)); +} + static void server_command_qset_qclear(shared_ptr c, const std::string& args, bool should_set) { check_debug_enabled(c); auto l = c->require_lobby(); @@ -2257,6 +2373,9 @@ static const unordered_map chat_commands({ {"$stat", {server_command_get_ep3_battle_stat, nullptr}}, {"$surrender", {server_command_surrender, nullptr}}, {"$swa", {server_command_switch_assist, proxy_command_switch_assist}}, + {"$swclear", {server_command_swclear, proxy_command_swclear}}, + {"$swset", {server_command_swset, proxy_command_swset}}, + {"$swsetall", {server_command_swsetall, proxy_command_swsetall}}, {"$unset", {server_command_ep3_unset_field_character, nullptr}}, {"$variations", {server_command_variations, nullptr}}, {"$warp", {server_command_warpme, proxy_command_warpme}}, diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 90176423..bb7204d5 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3860,7 +3860,7 @@ struct G_SwitchStateChanged_6x05 { // Only two bits in flags have meanings: // 01 - set unlock flag (if not set, the flag is cleared instead) // 02 - play room unlock sound if floor matches client's floor - uint8_t flags = 0; // Bit field, with 2 lowest bits having meaning + uint8_t flags = 0; } __packed__; // 6x06: Send guild card