diff --git a/README.md b/README.md index 17713ffc..ba01c265 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,7 @@ There are many options available when starting a proxy session. All options are * **Block pings**: blocks automatic pings sent by the client, and responds to ping commands from the server automatically. This works around a bug in Sylverant's login server. * **Infinite HP**: automatically heals you whenever you get hit. An attack that kills you in one hit will still kill you, however. * **Infinite TP**: automatically restores your TP whenever you use any technique. -* **Switch assist**: attempts to unlock doors that require two players in a one-player game. +* **Switch assist**: attempts to unlock doors that require two or four players in a one-player game. * **Infinite Meseta** (Episode 3 only): gives you 1,000,000 Meseta, regardless of the value sent by the remote server. * **Block events**: disables holiday events sent by the remote server. * **Block patches**: prevents any B2 (patch) commands from reaching the client. @@ -449,7 +449,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$secid `: Sets your override section ID. After running this command, any games you create will use your override section ID for rare drops instead of your character's actual section ID. If you're in a game and you are the leader of the game, this also immediately changes the item tables used by the server when creating items. To revert to your actual section id, run `$secid` with no name after it. On the proxy server, this will not work if the remote server controls item drops (e.g. on BB, or on Schtserv with server drops enabled). If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing. * `$rand `: Sets your override random seed (specified as a 32-bit hex value). This will make any games you create use the given seed for rare enemies. This also makes item drops deterministic in Blue Burst games hosted by newserv. On the proxy server, this command can cause desyncs with other players in the same game, since they will not see the overridden random seed. To remove the override, run `$rand` with no arguments. If the server does not allow cheat mode anywhere (that is, "CheatModeBehavior" is "Off" in config.json), this command does nothing. * `$ln [name-or-type]`: Sets the lobby number. Visible only to you. This command exists because some non-lobby maps can be loaded as lobbies with invalid lobby numbers. See the "GC lobby types" and "Ep3 lobby types" entries in the information menu for acceptable values here. Note that non-lobby maps do not have a lobby counter, so there's no way to exit the lobby without using either `$ln` again or `$exit`. On the game server, `$ln` reloads the lobby immediately; on the proxy server, it doesn't take effect until you load another lobby yourself (which means you'll like have to use `$exit` to escape). Run this command with no argument to return to the default lobby. - * `$swa`: Enables or disables switch assist. When enabled, the server will attempt to automatically unlock two-player doors in non-quest games if you step on both switches sequentially. + * `$swa`: Enables or disables switch assist. When enabled, the server will attempt to automatically unlock two-player and four-player doors in non-quest games if you step on all the required switches sequentially. * `$exit`: If you're in a lobby, sends you to the main menu (which ends your proxy session, if you're in one). If you're in a game or spectator team, sends you to the lobby (but does not end your proxy session if you're in one). Does nothing if you're in a non-Episode 3 game and no quest is in progress. * `$patch `: Run a patch on your client. `` must exactly match the name of a patch on the server. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 539f3b39..eea60ec6 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -682,6 +682,7 @@ static void server_command_exit(shared_ptr c, const std::string&) { G_UnusedHeader cmd = {0x73, 0x01, 0x0000}; c->channel.send(0x60, 0x00, cmd); c->floor = 0; + c->recent_switch_flags.clear(); } else if (is_ep3(c->version())) { c->channel.send(0xED, 0x00); } else { diff --git a/src/Client.cc b/src/Client.cc index 636db5e9..b1aab3b5 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -215,7 +215,6 @@ Client::Client( } this->config.specific_version = default_specific_version_for_version(version, -1); - this->last_switch_enabled_command.header.subcommand = 0; memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr)); this->reschedule_save_game_data_event(); diff --git a/src/Client.hh b/src/Client.hh index 6be8deb5..0516c26d 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -250,7 +250,7 @@ public: // Miscellaneous (used by chat commands) uint32_t next_exp_value; // next EXP value to give - G_SwitchStateChanged_6x05 last_switch_enabled_command; + RecentSwitchFlags recent_switch_flags; // used for switch assist bool can_chat; struct PendingCharacterExport { std::shared_ptr license; diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index e0cc481c..43f0a998 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -1091,3 +1091,26 @@ SymbolChat::SymbolChat() : spec(0), corner_objects(0x00FF), face_parts() {} + +void RecentSwitchFlags::add(uint16_t flag_num) { + if ((flag_num != ((this->flag_nums >> 48) & 0xFFFF)) && + (flag_num != ((this->flag_nums >> 32) & 0xFFFF)) && + (flag_num != ((this->flag_nums >> 16) & 0xFFFF)) && + (flag_num != (this->flag_nums & 0xFFFF))) { + this->flag_nums = this->flag_nums << 16 | flag_num; + } +} + +string RecentSwitchFlags::enable_commands(uint8_t floor) const { + StringWriter w; + uint64_t flag_nums = this->flag_nums; + for (size_t z = 0; z < 4; z++) { + uint16_t flag_num = flag_nums; + if (flag_num == 0xFFFF) { + continue; + } + w.put(G_SwitchStateChanged_6x05{{0x05, 0x03, 0xFFFF}, 0, 0, flag_num, static_cast(floor), 0x01}); + flag_nums >>= 16; + } + return std::move(w.str()); +} diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 20a98f5c..1b82b5d6 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -733,3 +733,15 @@ struct SymbolChat { SymbolChat(); } __attribute__((packed)); + +struct RecentSwitchFlags { + uint64_t flag_nums = 0xFFFFFFFFFFFFFFFF; + + inline void clear() { + this->flag_nums = 0xFFFFFFFFFFFFFFFF; + } + + void add(uint16_t flag_num); + + std::string enable_commands(uint8_t floor) const; +}; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 9eab67ca..9f03dbf1 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1946,15 +1946,13 @@ HandlerResult C_6x(shared_ptr ses, uint16_t, u if (!data.empty()) { if ((data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) { auto& cmd = check_size_t(data); - if (cmd.flags && cmd.header.object_id != 0xFFFF) { - if (ses->last_switch_enabled_command.header.subcommand == 0x05) { - ses->log.info("Switch assist: replaying previous enable command"); - ses->server_channel.send(0x60, 0x00, &ses->last_switch_enabled_command, - sizeof(ses->last_switch_enabled_command)); - ses->client_channel.send(0x60, 0x00, &ses->last_switch_enabled_command, - sizeof(ses->last_switch_enabled_command)); + if ((cmd.flags & 1) && (cmd.header.object_id != 0xFFFF)) { + ses->recent_switch_flags.add(cmd.switch_flag_num); + string commands = ses->recent_switch_flags.enable_commands(ses->floor); + if (!commands.empty()) { + ses->server_channel.send(0x60, 0x00, commands); + ses->client_channel.send(0x60, 0x00, commands); } - ses->last_switch_enabled_command = cmd; } } else if (data[0] == 0x21) { diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 94e4f9d5..a4395930 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -537,7 +537,6 @@ ProxyServer::LinkedSession::LinkedSession( lobby_mode(GameMode::NORMAL), lobby_episode(Episode::EP1), lobby_random_seed(0) { - this->last_switch_enabled_command.header.subcommand = 0; memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes)); } diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 4fc618d1..59cda07f 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -70,7 +70,7 @@ public: Client::Config config; // A null handler in here means to forward the response to the remote server std::deque> function_call_return_handler_queue; - G_SwitchStateChanged_6x05 last_switch_enabled_command; + RecentSwitchFlags recent_switch_flags; // used for switch assist ItemData next_drop_item; uint32_t next_item_id; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 587749ed..c6e7e911 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1364,8 +1364,9 @@ static void on_change_floor_6x1F(shared_ptr c, uint8_t command, uint8_t } else { const auto& cmd = check_size_t(data, size); - if (cmd.floor >= 0) { + if (cmd.floor >= 0 && c->floor != static_cast(cmd.floor)) { c->floor = cmd.floor; + c->recent_switch_flags.clear(); } } forward_subcommand(c, command, flag, data, size); @@ -1373,8 +1374,9 @@ static void on_change_floor_6x1F(shared_ptr c, uint8_t command, uint8_t static void on_change_floor_6x21(shared_ptr c, uint8_t command, uint8_t flag, void* data, size_t size) { const auto& cmd = check_size_t(data, size); - if (cmd.floor >= 0) { + if (cmd.floor >= 0 && c->floor != static_cast(cmd.floor)) { c->floor = cmd.floor; + c->recent_switch_flags.clear(); } forward_subcommand(c, command, flag, data, size); } @@ -1551,18 +1553,17 @@ static void on_switch_state_changed(shared_ptr c, uint8_t command, uint8 } } - if (cmd.flags && cmd.header.object_id != 0xFFFF) { - if (!l->quest && - c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED) && - (c->last_switch_enabled_command.header.subcommand == 0x05)) { - c->log.info("[Switch assist] Replaying previous enable command"); + if ((cmd.flags & 1) && cmd.header.object_id != 0xFFFF) { + c->recent_switch_flags.add(cmd.switch_flag_num); + if (!l->quest && c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) { if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message(c, "$C5Switch assist"); } - forward_subcommand(c, command, flag, &c->last_switch_enabled_command, sizeof(c->last_switch_enabled_command)); - send_command_t(c, command, flag, c->last_switch_enabled_command); + string commands = c->recent_switch_flags.enable_commands(c->floor); + if (!commands.empty()) { + send_command(c, 0x60, 0x00, commands); + } } - c->last_switch_enabled_command = cmd; } } @@ -1587,8 +1588,9 @@ void on_movement_with_floor(shared_ptr c, uint8_t command, uint8_t flag, } c->x = cmd.x; c->z = cmd.z; - if (cmd.floor >= 0) { + if (cmd.floor >= 0 && c->floor != static_cast(cmd.floor)) { c->floor = cmd.floor; + c->recent_switch_flags.clear(); } forward_subcommand(c, command, flag, data, size); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 0ab119e4..fc1fb1a2 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2439,6 +2439,7 @@ void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private) void send_warp(shared_ptr c, uint32_t floor, bool is_private) { send_warp(c->channel, c->lobby_client_id, floor, is_private); c->floor = floor; + c->recent_switch_flags.clear(); } void send_warp(shared_ptr l, uint32_t floor, bool is_private) { diff --git a/tests/GC-PoisonRoom.test.txt b/tests/GC-PoisonRoom.test.txt index 3a604df9..dc8bd149 100644 --- a/tests/GC-PoisonRoom.test.txt +++ b/tests/GC-PoisonRoom.test.txt @@ -6244,6 +6244,8 @@ I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 0010 | 00 00 00 00 | I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) 0000 | 60 00 10 00 05 03 5A 42 00 00 00 00 65 00 03 01 | ` ZB e +I 56327 2024-03-03 23:40:27 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00) +0000 | 60 00 10 00 05 03 FF FF 00 00 00 00 65 00 03 01 | ` e I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 89 8E 65 C4 32 06 7B 43 | ` B e 2 {C I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) @@ -6260,7 +6262,8 @@ I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 10 00 05 03 59 42 00 00 00 00 64 00 03 01 | ` YB d I 56327 2024-03-03 23:40:27 - [C-2] [Switch assist] Replaying previous enable command I 56327 2024-03-03 23:40:27 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00) -0000 | 60 00 10 00 05 03 5A 42 00 00 00 00 65 00 03 01 | ` ZB e +0000 | 60 00 1C 00 05 03 FF FF 00 00 00 00 64 00 03 01 | ` d +0010 | 05 03 FF FF 00 00 00 00 65 00 03 01 | e I 56327 2024-03-03 23:40:28 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) 0000 | 60 00 10 00 0B 03 58 42 01 00 00 00 58 02 00 00 | ` XB X I 56327 2024-03-03 23:40:28 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) @@ -11671,7 +11674,9 @@ I 56327 2024-03-03 23:45:37 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 10 00 05 03 08 42 00 00 00 00 6C 00 03 01 | ` B l I 56327 2024-03-03 23:45:37 - [C-2] [Switch assist] Replaying previous enable command I 56327 2024-03-03 23:45:37 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00) -0000 | 60 00 10 00 05 03 59 42 00 00 00 00 64 00 03 01 | ` YB d +0000 | 60 00 28 00 05 03 FF FF 00 00 00 00 6C 00 03 01 | ` ( l +0010 | 05 03 FF FF 00 00 00 00 64 00 03 01 05 03 FF FF | d +0020 | 00 00 00 00 65 00 03 01 | e I 56327 2024-03-03 23:45:37 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) 0000 | 60 00 10 00 05 03 59 42 00 00 00 00 64 00 03 00 | ` YB d I 56327 2024-03-03 23:45:38 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) @@ -15242,7 +15247,7 @@ I 56327 2024-03-03 23:49:06 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 10 00 05 03 66 43 00 00 00 00 68 00 04 01 | ` fC h I 56327 2024-03-03 23:49:06 - [C-2] [Switch assist] Replaying previous enable command I 56327 2024-03-03 23:49:06 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00) -0000 | 60 00 10 00 05 03 08 42 00 00 00 00 6C 00 03 01 | ` B l +0000 | 60 00 10 00 05 03 FF FF 00 00 00 00 68 00 04 01 | ` h I 56327 2024-03-03 23:49:06 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) 0000 | 60 00 10 00 42 03 00 00 AB 88 9B C3 D3 0F 10 44 | ` B D I 56327 2024-03-03 23:49:06 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) @@ -15267,7 +15272,8 @@ I 56327 2024-03-03 23:49:08 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 10 00 05 03 65 43 00 00 00 00 67 00 04 01 | ` eC g I 56327 2024-03-03 23:49:08 - [C-2] [Switch assist] Replaying previous enable command I 56327 2024-03-03 23:49:08 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00) -0000 | 60 00 10 00 05 03 66 43 00 00 00 00 68 00 04 01 | ` fC h +0000 | 60 00 1C 00 05 03 FF FF 00 00 00 00 67 00 04 01 | ` g +0010 | 05 03 FF FF 00 00 00 00 68 00 04 01 | h I 56327 2024-03-03 23:49:08 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) 0000 | 60 00 10 00 0B 03 3B 43 01 00 00 00 3B 03 00 00 | ` ;C ; I 56327 2024-03-03 23:49:08 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) @@ -16093,7 +16099,9 @@ I 56327 2024-03-03 23:50:04 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 10 00 05 03 41 43 00 00 00 00 66 00 04 01 | ` AC f I 56327 2024-03-03 23:50:04 - [C-2] [Switch assist] Replaying previous enable command I 56327 2024-03-03 23:50:04 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00) -0000 | 60 00 10 00 05 03 65 43 00 00 00 00 67 00 04 01 | ` eC g +0000 | 60 00 28 00 05 03 FF FF 00 00 00 00 66 00 04 01 | ` ( f +0010 | 05 03 FF FF 00 00 00 00 67 00 04 01 05 03 FF FF | g +0020 | 00 00 00 00 68 00 04 01 | h I 56327 2024-03-03 23:50:04 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00) 0000 | 60 00 10 00 05 03 65 43 00 00 00 00 67 00 04 00 | ` eC g I 56327 2024-03-03 23:50:04 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)