netcode-revival: make room/server fields actually apply

Typing a room or relay and pressing Enter did not commit: the fields only
wrote the cvar on ImGuiInputTextFlags_EnterReturnsTrue, and Enter was flaky
in the game. Now:

- Room code commits on every keystroke (uppercase), so the relay follows the
  typed code immediately. A room change re-joins the relay on the next tick
  instead of waiting up to the 5s heartbeat.
- Relay server commits on Enter OR when the field loses focus (clicking
  away), so a swallowed Enter can't leave the old relay active; kept
  commit-on-deactivate (not per keystroke) so partial hostnames don't flap
  the connection while typing.
- Display name commits on change (same Enter flakiness).
- The network loop now splits a 'host:port' server string and uses the
  embedded port for both the relay resolve and the game's netcode transport,
  so typing '127.0.0.1:40001' works (previously it produced a broken
  'host:port:port' resolve).
This commit is contained in:
MiTHRAL 2026-08-28 13:19:45 -04:00
parent 2bc46451fc
commit 658d727aba
2 changed files with 38 additions and 14 deletions

View file

@ -733,11 +733,27 @@ void GhostNetworkLoop() {
} }
const bool debug = Debug(); const bool debug = Debug();
const uint16_t port = uint16_t(REXCVAR_GET(skate3_ghost_port));
std::string server = REXCVAR_GET(skate3_ghost_server); std::string server = REXCVAR_GET(skate3_ghost_server);
if (server.empty()) { if (server.empty()) {
server = kGhostDefaultServer; 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); const std::string room_str = REXCVAR_GET(skate3_ghost_room);
char room_buf[kGhostMaxRoomLen] = {}; char room_buf[kGhostMaxRoomLen] = {};
uint8_t room_len = 0; uint8_t room_len = 0;
@ -754,6 +770,13 @@ void GhostNetworkLoop() {
g_room_len = room_len; 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) { if (sock == GHOST_INVALID_SOCKET) {
sock = GhostSocketCreate(/*allow_broadcast=*/true); sock = GhostSocketCreate(/*allow_broadcast=*/true);
@ -768,7 +791,7 @@ void GhostNetworkLoop() {
if (server != last_server || port != last_port) { if (server != last_server || port != last_port) {
GhostAddr a; GhostAddr a;
g_peer_public.configured = 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) { if (g_peer_public.configured) {
g_peer_public.addr = a; g_peer_public.addr = a;
} else { } else {
@ -793,7 +816,7 @@ void GhostNetworkLoop() {
std::lock_guard<std::mutex> lock(g_mu); std::lock_guard<std::mutex> lock(g_mu);
std::memcpy(active_room, g_room, g_room_len); 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) { if (now - g_peer_public.last_join >= kJoinIntervalSec) {
SendJoin(sock, g_peer_public.addr, /*ping=*/false); SendJoin(sock, g_peer_public.addr, /*ping=*/false);
g_peer_public.last_join = now; g_peer_public.last_join = now;

View file

@ -97,11 +97,12 @@ void GhostDialog::OnDraw(ImGuiIO& io) {
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Room code"); 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_), 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_)); REXCVAR_SET(skate3_ghost_room, std::string(room_input_));
ImGui::SetKeyboardFocusHere(-1);
} }
ImGui::SameLine(); ImGui::SameLine();
// Copy the ACTIVE room (the one this player is actually in), which for an // 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::Separator();
ImGui::Text("Display name"); ImGui::Text("Display name");
if (ImGui::InputText("##ghost_name", name_input_, sizeof(name_input_), if (ImGui::InputText("##ghost_name", name_input_, sizeof(name_input_))) {
ImGuiInputTextFlags_EnterReturnsTrue)) {
REXCVAR_SET(skate3_ghost_name, std::string(name_input_)); REXCVAR_SET(skate3_ghost_name, std::string(name_input_));
ImGui::SetKeyboardFocusHere(-1);
} }
ImGui::TextDisabled("Shown on your nameplate above the ghost. Empty = " ImGui::TextDisabled("Shown on your nameplate above the ghost. Empty = "
"your OS user name."); "your OS user name.");
@ -174,14 +173,16 @@ void GhostDialog::OnDraw(ImGuiIO& io) {
ImGui::Text("Relay server (optional)"); ImGui::Text("Relay server (optional)");
ImGui::PushItemWidth(-1.0f); ImGui::PushItemWidth(-1.0f);
if (ImGui::InputText("##ghost_server", server_input_, sizeof(server_input_), // Commit on Enter OR when the field loses focus (clicking away), so a
ImGuiInputTextFlags_EnterReturnsTrue)) { // 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_)); REXCVAR_SET(skate3_ghost_server, std::string(server_input_));
ImGui::SetKeyboardFocusHere(-1);
} }
ImGui::PopItemWidth(); ImGui::PopItemWidth();
ImGui::TextDisabled("Empty = built-in default relay. No port forwarding " ImGui::TextDisabled("Empty = built-in default relay. Press Enter or click "
"needed on any client."); "away to apply. No port forwarding needed on any client.");
ImGui::Separator(); ImGui::Separator();