From 377d8beac316b32ac6d3a4bd7fa14a102001fa2a Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 14 Aug 2025 23:44:16 -0700 Subject: [PATCH] implement $switchchar command --- README.md | 1 + src/ChatCommands.cc | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/README.md b/README.md index 89ec82e9..766b6337 100644 --- a/README.md +++ b/README.md @@ -604,6 +604,7 @@ Some commands only work for clients not in proxy sessions. The chat commands are * `$patch `: Run a patch on your client. `` must exactly match the name of a patch on the server. * Character data commands (non-proxy only) + * `$switchchar ` (BB only): Switch to a different character from your account without logging out. * `$savechar `: Save your current character data on the server in the specified slot. See the [server-side saves section](#server-side-saves) for more details. * `$loadchar `: Load character data from the specified slot on the server, and replace your current character with it. See the [server-side saves section](#server-side-saves) for more details. * `$bbchar `: Save your current character data on the server in a different account's BB character slots. See the [server-side saves section](#server-side-saves) for more details. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index decc62b9..630c9f04 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -2640,6 +2640,46 @@ ChatCommandDefinition cc_swsetall( co_return; }); +ChatCommandDefinition cc_switchchar( + {"$switchchar"}, + +[](const Args& a) -> asio::awaitable { + auto l = a.c->require_lobby(); + auto s = a.c->require_server_state(); + + a.check_is_proxy(false); + a.check_is_game(false); + if (a.c->version() != Version::BB_V4) { + throw precondition_failed("This command can only\nbe used on BB"); + } + + int32_t index = stol(a.text, nullptr, 0) - 1; + if (index < 0) { + throw precondition_failed("Invalid slot number"); + } + auto filename = Client::character_filename(a.c->login->bb_license->username, index); + if (!std::filesystem::is_regular_file(filename)) { + throw precondition_failed("No character exists\nin that slot"); + } + + a.c->save_and_unload_character(); + a.c->bb_character_index = index; + + // TODO: This can trigger a client bug where the previous character's + // name label object isn't deleted if the leave and join notifications + // are received on the same frame. This results in the receiving player + // seeing both labels over the new character, with the latest one + // appearing on top. We could fix this by requiring each recipient to + // reply to a ping between the two commands, similar to how the 64 and + // 6x6D commands are split during game joining, but implementing that + // here seems not worth the effort given the low likelihood and impact of + // this bug. + send_complete_player_bb(a.c); + send_player_leave_notification(l, a.c->lobby_client_id); + s->send_lobby_join_notifications(l, a.c); + + co_return; + }); + ChatCommandDefinition cc_unset( {"$unset"}, +[](const Args& a) -> asio::awaitable {