diff --git a/src/native/skate3_native_ghost.cpp b/src/native/skate3_native_ghost.cpp index a158e56..873b3a1 100644 --- a/src/native/skate3_native_ghost.cpp +++ b/src/native/skate3_native_ghost.cpp @@ -733,11 +733,27 @@ void GhostNetworkLoop() { } const bool debug = Debug(); - const uint16_t port = uint16_t(REXCVAR_GET(skate3_ghost_port)); std::string server = REXCVAR_GET(skate3_ghost_server); if (server.empty()) { server = kGhostDefaultServer; } + // A server string may carry its own port ("host:port"); prefer it over + // the port slider so the relay resolve AND the game's netcode transport + // agree on the endpoint. + std::string host = server; + uint16_t port = uint16_t(REXCVAR_GET(skate3_ghost_port)); + const size_t colon = server.rfind(':'); + if (colon != std::string::npos && colon != 0 && + colon != server.size() - 1) { + host = server.substr(0, colon); + try { + const int p = std::stoi(server.substr(colon + 1)); + if (p > 0 && p <= 65535) { + port = uint16_t(p); + } + } catch (...) { + } + } const std::string room_str = REXCVAR_GET(skate3_ghost_room); char room_buf[kGhostMaxRoomLen] = {}; uint8_t room_len = 0; @@ -754,6 +770,13 @@ void GhostNetworkLoop() { g_room_len = room_len; } } + // A typed/clicked room change re-joins the relay on the next tick + // instead of waiting up to the heartbeat interval. + static std::string s_last_room; + if (room_len != 0 && s_last_room != room_str) { + s_last_room = room_str; + g_peer_public.last_join = 0.0; + } if (sock == GHOST_INVALID_SOCKET) { sock = GhostSocketCreate(/*allow_broadcast=*/true); @@ -768,7 +791,7 @@ void GhostNetworkLoop() { if (server != last_server || port != last_port) { GhostAddr a; g_peer_public.configured = - GhostResolve(server + ":" + std::to_string(port), port, &a); + GhostResolve(host + ":" + std::to_string(port), port, &a); if (g_peer_public.configured) { g_peer_public.addr = a; } else { @@ -793,7 +816,7 @@ void GhostNetworkLoop() { std::lock_guard lock(g_mu); std::memcpy(active_room, g_room, g_room_len); } - rex::system::XNetTransport::Instance().SetRelay(server.c_str(), port, active_room); + rex::system::XNetTransport::Instance().SetRelay(host.c_str(), port, active_room); if (now - g_peer_public.last_join >= kJoinIntervalSec) { SendJoin(sock, g_peer_public.addr, /*ping=*/false); g_peer_public.last_join = now; diff --git a/src/skate3_native_ghost_dialog.cpp b/src/skate3_native_ghost_dialog.cpp index d742487..7965d07 100644 --- a/src/skate3_native_ghost_dialog.cpp +++ b/src/skate3_native_ghost_dialog.cpp @@ -97,11 +97,12 @@ void GhostDialog::OnDraw(ImGuiIO& io) { ImGui::Separator(); ImGui::Text("Room code"); + // Commits on every change so the relay follows the typed code immediately + // (no Enter required - Enter was flaky here). Partial codes are harmless: + // the relay just moves you to each intermediate room as you type. if (ImGui::InputText("##ghost_room", room_input_, sizeof(room_input_), - ImGuiInputTextFlags_EnterReturnsTrue | - ImGuiInputTextFlags_CharsUppercase)) { + ImGuiInputTextFlags_CharsUppercase)) { REXCVAR_SET(skate3_ghost_room, std::string(room_input_)); - ImGui::SetKeyboardFocusHere(-1); } ImGui::SameLine(); // Copy the ACTIVE room (the one this player is actually in), which for an @@ -125,10 +126,8 @@ void GhostDialog::OnDraw(ImGuiIO& io) { ImGui::Separator(); ImGui::Text("Display name"); - if (ImGui::InputText("##ghost_name", name_input_, sizeof(name_input_), - ImGuiInputTextFlags_EnterReturnsTrue)) { + if (ImGui::InputText("##ghost_name", name_input_, sizeof(name_input_))) { REXCVAR_SET(skate3_ghost_name, std::string(name_input_)); - ImGui::SetKeyboardFocusHere(-1); } ImGui::TextDisabled("Shown on your nameplate above the ghost. Empty = " "your OS user name."); @@ -174,14 +173,16 @@ void GhostDialog::OnDraw(ImGuiIO& io) { ImGui::Text("Relay server (optional)"); ImGui::PushItemWidth(-1.0f); - if (ImGui::InputText("##ghost_server", server_input_, sizeof(server_input_), - ImGuiInputTextFlags_EnterReturnsTrue)) { + // Commit on Enter OR when the field loses focus (clicking away), so a + // swallowed Enter cannot leave the old server active. Not commit-on-change: + // partial hostnames would flap the connection while typing. + ImGui::InputText("##ghost_server", server_input_, sizeof(server_input_)); + if (ImGui::IsItemDeactivatedAfterEdit()) { REXCVAR_SET(skate3_ghost_server, std::string(server_input_)); - ImGui::SetKeyboardFocusHere(-1); } ImGui::PopItemWidth(); - ImGui::TextDisabled("Empty = built-in default relay. No port forwarding " - "needed on any client."); + ImGui::TextDisabled("Empty = built-in default relay. Press Enter or click " + "away to apply. No port forwarding needed on any client."); ImGui::Separator();