diff --git a/engine/MUNGA_L4/L4NET.CPP b/engine/MUNGA_L4/L4NET.CPP index 27dabc3..eefad11 100644 --- a/engine/MUNGA_L4/L4NET.CPP +++ b/engine/MUNGA_L4/L4NET.CPP @@ -1506,6 +1506,24 @@ void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* H // relaySelf and everything downstream (egg self-match, HELLO) proceeds as if // BT_SELF had been set. SEAT_FULL / timeout => False. // +// +// RelayWaitTick -- pump the window thread + paint the animated waiting screen +// (join/join_lan UX fix 2026-07-22: the seat-wait loop blocked the message +// pump for its whole 30-min patience window, so the main window showed "Not +// Responding" until the console started the game). +// +static void RelayWaitTick(const char *line1, const char *line2) +{ + MSG msg; + while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + extern void BTWaitScreenPaint(const char *, const char *); + BTWaitScreenPaint(line1, line2); +} + Logical L4NetworkManager::RelayRequestSeat() { // @@ -1541,7 +1559,17 @@ Logical L4NetworkManager::RelayRequestSeat() { BTRelayWaitStatus("\n"); } - Sleep(2000); + { + char detail[128]; + sprintf(detail, "session at %s:%d -- close this window to cancel", + inet_ntoa(relayGameAddress.sin_addr), + (int)ntohs(relayGameAddress.sin_port) - 1); + for (int slice = 0; slice < 20; ++slice) // 2s, pumped + animated + { + RelayWaitTick("WAITING FOR THE OPERATOR'S SESSION", detail); + Sleep(100); + } + } continue; } Logical got_seat = False; @@ -1564,6 +1592,8 @@ Logical L4NetworkManager::RelayRequestSeat() tv.tv_usec = 200000; if (select(0, &read_set, NULL, NULL, &tv) != 1) { + RelayWaitTick("WAITING FOR THE OPERATOR'S SESSION", + "requesting a seat..."); continue; } int received = recv(seat_socket, reply + have, diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index d7e77e9..914b0ce 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -8772,6 +8772,66 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte #endif } +// +// BTWaitScreenPaint -- the animated "waiting" screen (join/join_lan UX fix, +// 2026-07-22). Pure GDI over the main window: used while the relay seat-wait +// loop blocks startup (no D3D presenting yet -> flicker-free) and re-painted +// after each ExecuteIdle Present during WaitingForEgg (the black idle frame). +// A 12-segment spinner phases on the tick clock; text centered above it. +// +void BTWaitScreenPaint(const char *line1, const char *line2) +{ + extern void *BTResolveMainWindow(); + HWND wnd = (HWND)BTResolveMainWindow(); + if (wnd == NULL) + return; + HDC dc = GetDC(wnd); + if (dc == NULL) + return; + RECT rc; + GetClientRect(wnd, &rc); + int w = rc.right - rc.left, h = rc.bottom - rc.top; + FillRect(dc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH)); + + static HFONT s_font = NULL, s_fontDim = NULL; + if (s_font == NULL) + { + s_font = CreateFontA(30, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, + FIXED_PITCH | FF_MODERN, "Consolas"); + s_fontDim = CreateFontA(20, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, + FIXED_PITCH | FF_MODERN, "Consolas"); + } + SetBkMode(dc, TRANSPARENT); + HGDIOBJ old_font = SelectObject(dc, s_font); + SetTextColor(dc, RGB(64, 255, 64)); + RECT r1 = { 0, h / 2 - 70, w, h / 2 - 30 }; + DrawTextA(dc, line1, -1, &r1, DT_CENTER | DT_SINGLELINE | DT_VCENTER); + SelectObject(dc, s_fontDim); + SetTextColor(dc, RGB(24, 140, 24)); + RECT r2 = { 0, h / 2 - 24, w, h / 2 + 8 }; + DrawTextA(dc, line2, -1, &r2, DT_CENTER | DT_SINGLELINE | DT_VCENTER); + SelectObject(dc, old_font); + + // the spinner: 12 segments, three brightness tiers marching clockwise + int cx = w / 2, cy = h / 2 + 80, r_in = 18, r_out = 34; + int phase = (int)((GetTickCount() / 80UL) % 12UL); + for (int i = 0; i < 12; ++i) + { + int age = (i - phase + 12) % 12; + int lum = (age == 0) ? 255 : (age < 3) ? 150 : 60; + HPEN pen = CreatePen(PS_SOLID, 4, RGB(lum / 4, lum, lum / 4)); + HGDIOBJ old_pen = SelectObject(dc, pen); + double a = i * 3.14159265 / 6.0; + MoveToEx(dc, cx + (int)(r_in * cos(a)), cy + (int)(r_in * sin(a)), NULL); + LineTo(dc, cx + (int)(r_out * cos(a)), cy + (int)(r_out * sin(a))); + SelectObject(dc, old_pen); + DeleteObject(pen); + } + ReleaseDC(wnd, dc); +} + void DPLRenderer::ExecuteIdle() { HRESULT hr; @@ -8782,7 +8842,13 @@ void DPLRenderer::ExecuteIdle() hr = mDevice->EndScene(); - if (mDevice->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST) + // WaitingForEgg is a black frame -- overlay the waiting screen (GDI, + // repainted after every Present; join/join_lan UX fix 2026-07-22). + { + HRESULT present_hr = mDevice->Present(NULL, NULL, NULL, NULL); + BTWaitScreenPaint("WAITING FOR MISSION ASSIGNMENT", + "the operator starts the session from the console"); + if (present_hr == D3DERR_DEVICELOST) { int bbCount = mPresentParams.BackBufferCount; int bbWidth = mPresentParams.BackBufferWidth; @@ -8797,6 +8863,7 @@ void DPLRenderer::ExecuteIdle() mPresentParams.BackBufferWidth = bbWidth; mPresentParams.BackBufferHeight = bbHeight; } + } } // //#############################################################################