diff --git a/src/native/skate3_native_ghost.cpp b/src/native/skate3_native_ghost.cpp index c7a2b3b..461a853 100644 --- a/src/native/skate3_native_ghost.cpp +++ b/src/native/skate3_native_ghost.cpp @@ -738,8 +738,15 @@ void GhostNetworkLoop() { RoomSet(room_buf, room_len, room_str.c_str()); { std::lock_guard lock(g_mu); - std::memcpy(g_room, room_buf, kGhostMaxRoomLen); - g_room_len = room_len; + // A non-empty room cvar is the user's explicit choice: sync it into + // the active room. An EMPTY cvar means "server auto-assigns" - keep + // the code the relay handed back on HELLO. Overwriting the assigned + // code with the empty cvar every 25 ms is what made the room code + // flicker in and out of the multiplayer menu. + if (room_len != 0) { + std::memcpy(g_room, room_buf, kGhostMaxRoomLen); + g_room_len = room_len; + } } if (sock == GHOST_INVALID_SOCKET) { diff --git a/src/skate3_native_ghost_dialog.cpp b/src/skate3_native_ghost_dialog.cpp index e1bfc22..d742487 100644 --- a/src/skate3_native_ghost_dialog.cpp +++ b/src/skate3_native_ghost_dialog.cpp @@ -45,7 +45,19 @@ void GhostDialog::Toggle() { void GhostDialog::SyncBuffersFromCvars() { const std::string room = REXCVAR_GET(skate3_ghost_room); - std::snprintf(room_input_, sizeof(room_input_), "%s", room.c_str()); + if (!room.empty()) { + std::snprintf(room_input_, sizeof(room_input_), "%s", room.c_str()); + } else { + // Auto-assigned room: the active code comes from the connection, not the + // (empty) cvar - surface it in the input so the user can see/copy it. + skate3::native_ghost::Status st; + if (skate3::native_ghost::GetStatus(&st) && st.room_len > 0) { + std::snprintf(room_input_, sizeof(room_input_), "%.*s", + int(st.room_len), st.room); + } else { + room_input_[0] = '\0'; + } + } const std::string server = REXCVAR_GET(skate3_ghost_server); std::snprintf(server_input_, sizeof(server_input_), "%s", server.c_str()); const std::string name = REXCVAR_GET(skate3_ghost_name); @@ -92,8 +104,21 @@ void GhostDialog::OnDraw(ImGuiIO& io) { ImGui::SetKeyboardFocusHere(-1); } ImGui::SameLine(); + // Copy the ACTIVE room (the one this player is actually in), which for an + // auto-assigned room lives in the connection state, not the cvar. if (ImGui::Button("Copy")) { - ImGui::SetClipboardText(REXCVAR_GET(skate3_ghost_room).c_str()); + skate3::native_ghost::Status st; + if (skate3::native_ghost::GetStatus(&st) && st.room_len > 0) { + char buf[skate3::native_ghost::kGhostMaxRoomLen + 1] = {}; + std::memcpy(buf, st.room, st.room_len); + ImGui::SetClipboardText(buf); + copy_feedback_ = true; + } + } + if (copy_feedback_) { + ImGui::SameLine(); + ImGui::TextDisabled("copied"); + copy_feedback_ = false; } ImGui::TextDisabled("Empty = the server assigns one; share it with friends."); diff --git a/src/skate3_native_ghost_dialog.h b/src/skate3_native_ghost_dialog.h index ec9355d..bbba525 100644 --- a/src/skate3_native_ghost_dialog.h +++ b/src/skate3_native_ghost_dialog.h @@ -27,6 +27,7 @@ class GhostDialog final : public rex::ui::ImGuiDialog { bool visible_ = false; bool synced_ = false; + bool copy_feedback_ = false; char room_input_[16] = {}; char server_input_[128] = {}; char name_input_[skate3::native_ghost::kGhostMaxNameLen + 1] = {};