From a7d436a89497aadb3ea5a5c0222d967590f24887 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 16 Oct 2024 23:23:19 -0700 Subject: [PATCH] use object flags for switch assist; closes #571 --- README.md | 4 ++-- src/ChatCommands.cc | 1 - src/Client.hh | 1 - src/Map.cc | 41 +++++++++++++++++++++++++++++++++++- src/Map.hh | 3 +++ src/PlayerSubordinates.cc | 23 -------------------- src/PlayerSubordinates.hh | 12 ----------- src/ProxyCommands.cc | 21 ++++++++++++------ src/ProxyServer.hh | 1 - src/ReceiveSubcommands.cc | 38 +++++++++++++++++++++------------ src/SendCommands.cc | 1 - tests/GC-PoisonRoom.test.txt | 23 ++------------------ 12 files changed, 87 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index f68d62c8..7865b589 100644 --- a/README.md +++ b/README.md @@ -497,7 +497,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. * **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 or four players in a one-player game. +* **Switch assist**: unlocks doors that require two or four players in a one-player game, when you step on one of the switches. * **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. @@ -566,7 +566,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$secid `: Set 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 `: Set 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]`: Set 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`: Enable or disable 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. + * `$swa`: Enable or disable switch assist. When enabled, the server will unlock two-player and four-player doors in non-quest games when you step on any of the required switches. * `$exit`: If you're in a lobby, send you to the main menu (which ends your proxy session, if you're in one). If you're in a game or spectator team, send 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 ee7103c3..005b120c 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -864,7 +864,6 @@ 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.hh b/src/Client.hh index e8bcd7a7..77ad3a8b 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -258,7 +258,6 @@ public: // Miscellaneous (used by chat commands) uint32_t next_exp_value; // next EXP value to give - RecentSwitchFlags recent_switch_flags; // used for switch assist bool can_chat; struct PendingCharacterExport { std::shared_ptr dest_account; diff --git a/src/Map.cc b/src/Map.cc index f6ecf3db..5acea568 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -746,7 +746,7 @@ void Map::add_objects_from_owned_map_data(uint8_t floor, const void* data, size_ const auto* objects = reinterpret_cast(data); for (size_t z = 0; z < entry_count; z++) { uint16_t object_id = this->objects.size(); - this->objects.emplace_back(Object{ + const auto& object = this->objects.emplace_back(Object{ .args = &objects[z], .source_index = z, .floor = floor, @@ -755,8 +755,37 @@ void Map::add_objects_from_owned_map_data(uint8_t floor, const void* data, size_ .set_flags = 0, .item_drop_checked = false, }); + uint64_t k = section_index_key(floor, objects[z].section, objects[z].group); this->floor_section_and_group_to_object_index.emplace(k, object_id); + + uint32_t base_switch_flag = 0; + uint32_t num_switch_flags = 0; + switch (object.args->base_type) { + case 0x00C1: // TODoorCave01 + base_switch_flag = object.args->param4; + num_switch_flags = (4 - clamp(object.args->param5, 0, 4)); + break; + + case 0x14A: // TODoorAncient08 + case 0x14B: // TODoorAncient09 + base_switch_flag = object.args->param4; + num_switch_flags = (object.args->base_type == 0x14A) ? 4 : 2; + break; + + case 0x1AB: // TODoorFourLightRuins + case 0x1C0: // TODoorFourLightSpace + case 0x221: // TODoorFourLightSeabed + case 0x222: // TODoorFourLightSeabedU + base_switch_flag = object.args->param4; + num_switch_flags = object.args->param5; + break; + } + if ((num_switch_flags > 1) && !(base_switch_flag & 0xFFFFFF00)) { + for (size_t z = 0; z < num_switch_flags; z++) { + this->floor_and_switch_flag_to_door_index.emplace((floor << 8) | (base_switch_flag + z), object_id); + } + } } } @@ -1748,6 +1777,16 @@ std::vector Map::get_events(uint8_t floor) { return ret; } +vector Map::doors_for_switch_flag(uint8_t floor, uint8_t switch_flag) { + vector ret; + for (auto its = this->floor_and_switch_flag_to_door_index.equal_range((floor << 8) | switch_flag); + its.first != its.second; + its.first++) { + ret.emplace_back(&this->objects[its.first->second]); + } + return ret; +} + template static string disassemble_vector_file_t(const void* data, size_t size, size_t* entry_number, char type_ch) { deque ret; diff --git a/src/Map.hh b/src/Map.hh index 38b35451..19f226d9 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -369,6 +369,8 @@ struct Map { std::vector get_events(uint8_t floor, uint16_t section, uint16_t wave_number); std::vector get_events(uint8_t floor); + std::vector doors_for_switch_flag(uint8_t floor, uint8_t switch_flag); + static std::string disassemble_objects_data(const void* data, size_t size, size_t* object_number = nullptr); static std::string disassemble_enemies_data(const void* data, size_t size, size_t* enemy_number = nullptr); static std::string disassemble_wave_events_data(const void* data, size_t size, uint8_t floor = 0xFF); @@ -389,6 +391,7 @@ struct Map { std::unordered_multimap floor_section_and_group_to_object_index; std::unordered_multimap floor_section_and_wave_number_to_enemy_index; std::unordered_multimap floor_section_and_wave_number_to_event_index; + std::unordered_multimap floor_and_switch_flag_to_door_index; }; class SetDataTableBase { diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 61b26863..ba744086 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -699,29 +699,6 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver } } -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 { - phosg::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()); -} - const QuestFlagsForDifficulty bb_quest_flag_apply_mask{{ // clang-format off /* 0000 */ 0x00, 0x3F, 0xFF, 0xE3, 0xE0, 0xFF, 0xFF, 0x00, diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 2be94481..43c86d48 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -994,16 +994,4 @@ using SymbolChatBE = SymbolChatT; check_struct_size(SymbolChat, 0x3C); check_struct_size(SymbolChatBE, 0x3C); -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; -}; - extern const QuestFlagsForDifficulty bb_quest_flag_apply_mask; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 1cd2c18d..ddbe7aa3 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -2006,12 +2006,21 @@ 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 & 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); + if (ses->map && (cmd.flags & 1) && (cmd.header.object_id != 0xFFFF)) { + for (auto* door : ses->map->doors_for_switch_flag(cmd.switch_flag_floor, cmd.switch_flag_num)) { + if (door->game_flags & 0x0001) { + continue; + } + door->game_flags |= 1; + + G_UpdateObjectState_6x0B cmd0B; + cmd0B.header.subcommand = 0x0B; + cmd0B.header.size = sizeof(cmd0B) / 4; + cmd0B.header.client_id = door->object_id | 0x4000; + cmd0B.flags = door->game_flags; + cmd0B.object_index = door->object_id; + ses->client_channel.send(0x60, 0x00, &cmd0B, sizeof(cmd0B)); + ses->server_channel.send(0x60, 0x00, &cmd0B, sizeof(cmd0B)); } } diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 1962c6a3..0103be02 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -70,7 +70,6 @@ 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; - 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 d15e51c3..c1e67e38 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1410,7 +1410,6 @@ static void on_change_floor_6x1F(shared_ptr c, uint8_t command, uint8_t const auto& cmd = check_size_t(data, size); 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); @@ -1420,7 +1419,6 @@ static void on_change_floor_6x21(shared_ptr c, uint8_t command, uint8_t const auto& cmd = check_size_t(data, size); 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); } @@ -1581,7 +1579,30 @@ static void on_switch_state_changed(shared_ptr c, uint8_t command, uint8 return; } - forward_subcommand(c, command, flag, data, size); + if (!l->quest && + (cmd.flags & 1) && + (cmd.header.object_id != 0xFFFF) && + (cmd.switch_flag_num < 0x100) && + c->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) { + for (auto* door : l->map->doors_for_switch_flag(cmd.switch_flag_floor, cmd.switch_flag_num)) { + if (door->game_flags & 0x0001) { + continue; + } + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + send_text_message_printf(c, "$C5SWA K-%hX %02hhX %02hX", + door->object_id, cmd.switch_flag_floor, cmd.switch_flag_num.load()); + } + door->game_flags |= 1; + + G_UpdateObjectState_6x0B cmd0B; + cmd0B.header.subcommand = 0x0B; + cmd0B.header.size = sizeof(cmd0B) / 4; + cmd0B.header.client_id = door->object_id | 0x4000; + cmd0B.flags = door->game_flags; + cmd0B.object_index = door->object_id; + send_command_t(l, 0x60, 0x00, cmd0B); + } + } if (l->switch_flags) { if (cmd.flags & 1) { @@ -1597,15 +1618,7 @@ static void on_switch_state_changed(shared_ptr c, uint8_t command, uint8 } } - 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)) { - string commands = c->recent_switch_flags.enable_commands(c->floor); - if (!commands.empty()) { - send_command(c, 0x60, 0x00, commands); - } - } - } + forward_subcommand(c, command, flag, data, size); } static void on_play_sound_from_player(shared_ptr c, uint8_t command, uint8_t flag, void* data, size_t size) { @@ -1640,7 +1653,6 @@ void on_movement_with_floor(shared_ptr c, uint8_t command, uint8_t flag, c->z = cmd.z; 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 36d31491..88e94d23 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2567,7 +2567,6 @@ 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 8a776f33..eff25e4f 100644 --- a/tests/GC-PoisonRoom.test.txt +++ b/tests/GC-PoisonRoom.test.txt @@ -6245,7 +6245,7 @@ I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 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 +0000 | 60 00 10 00 0B 03 58 42 01 00 00 00 58 02 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 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,10 +6260,6 @@ I 56327 2024-03-03 23:40:27 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 10 00 42 03 00 00 24 F5 64 C4 92 44 5E 43 | ` B $ d D^C 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 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 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) @@ -11672,11 +11668,6 @@ I 56327 2024-03-03 23:45:37 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 0C 00 50 02 00 00 76 7E 00 00 | ` P v~ 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 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 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) @@ -15245,9 +15236,8 @@ I 56327 2024-03-03 23:49:06 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 10 00 42 03 00 00 56 37 98 C3 1D DD 10 44 | ` B V7 D 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 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 FF FF 00 00 00 00 68 00 04 01 | ` h +0000 | 60 00 10 00 0B 03 3B 43 01 00 00 00 3B 03 00 00 | 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) @@ -15270,10 +15260,6 @@ I 56327 2024-03-03 23:49:08 - [Commands] Received from C-2 (Jess) (version=GC_V3 0010 | 00 00 00 00 | 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 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 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) @@ -16097,11 +16083,6 @@ I 56327 2024-03-03 23:50:03 - [Commands] Received from C-2 (Jess) (version=GC_V3 0000 | 60 00 0C 00 50 02 00 00 7B C0 00 00 | ` P { 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 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 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)