Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3a7c86f
feat(ngmp): setup branch and instruction context for generals online
fbraz3 Aug 3, 2026
2d20e87
feat(ngmp): implement core ngmp online services manager and ui hooks
fbraz3 Aug 3, 2026
cb58add
feat(ngmp): add dynamic server host and port resolution via environme…
fbraz3 Aug 3, 2026
186f8da
feat(ngmp): set exact default ports 9001 for HTTP/WS and 9000 for HTT…
fbraz3 Aug 3, 2026
a991c7f
feat(ngmp): hardcode server host and ssl configuration at build-time
fbraz3 Aug 4, 2026
b460fc5
feat(ngmp): async http requests, websocket chat, and lobby ui hook
fbraz3 Aug 4, 2026
1c106aa
fix(gui): bypass legacy gamespy timeout and hook ngmp login
fbraz3 Aug 4, 2026
f7a2380
fix(gui): guard legacy gamespy disconnect popups under SAGE_USE_NGMP
fbraz3 Aug 4, 2026
cff882e
Merge remote-tracking branch 'origin/main' into feat/generals-online-…
fbraz3 Aug 4, 2026
77262ef
fix(ngmp): pump event queue in WOLLoginMenuUpdate to process auth eve…
fbraz3 Aug 5, 2026
e46337e
feat(ngmp): implement UI binding for lobby list and chat
fbraz3 Aug 5, 2026
bae2778
fix(ngmp): load endpoint config from env vars at runtime
fbraz3 Aug 5, 2026
2aac27d
fix(ngmp): inject endpoint configuration via CMake compile definitions
fbraz3 Aug 5, 2026
801c8d6
fix(ngmp): bypass GameSpy patch check when NGMP is enabled
fbraz3 Aug 5, 2026
411d46b
fix: UB in obfuscate() crashing on ARM64
fbraz3 Aug 5, 2026
efd9493
feat(ngmp): browser gamecode login flow via SDL_OpenURL + CheckLogin …
fbraz3 Aug 6, 2026
1e42258
fix(ngmp): fix build errors - NGMP_CLIENT_ID in header, WOLLoginMenu …
fbraz3 Aug 6, 2026
606a0e0
feat(ngmp): implement player and global stats fetching
fbraz3 Aug 7, 2026
5d0955c
fix(ngmp): join global lobby network room on Custom Match start
fbraz3 Aug 7, 2026
034023d
fix(ngmp): prevent SIGSEGV on 'Create Game' via staging room init ord…
fbraz3 Aug 13, 2026
c48fb4c
fix(ngmp): separate UI events from GameEngine internal tick update
fbraz3 Aug 13, 2026
ebd949f
fix(ngmp): populate player slot identity and fix Setup Menu Back button
fbraz3 Aug 13, 2026
80e4336
fix(ngmp): support pascal case json keys for lobby list parsing
fbraz3 Aug 13, 2026
0c7527b
fix(ngmp): synchronize curl_ws_recv to prevent libcurl crash
fbraz3 Aug 14, 2026
364381a
fix(ngmp): populate player identity, hide local ping, and return to l…
fbraz3 Aug 14, 2026
b1b7f22
docs(worklog): update 2026-08-DIARY with NGMP libcurl and lobby UX fixes
fbraz3 Aug 14, 2026
33c7751
feat(ngmp): populate lobby roster and restore online welcome voice line
fbraz3 Aug 14, 2026
751583e
fix(build): resolve base game inclusion and offline flatpak json depe…
fbraz3 Aug 14, 2026
f1dc175
fix(ngmp): defer online login flow to user action and propagate CI se…
fbraz3 Aug 14, 2026
abb3505
fix(build): ensure Flatpak sandbox loads CI server secrets and dedupl…
fbraz3 Aug 15, 2026
9fe8170
chore(build): add cmake/ngmp_env.cmake to .gitignore
fbraz3 Aug 15, 2026
672319f
fix(build): remove unsupported env flag from flatpak-builder command
fbraz3 Aug 15, 2026
b8f5f6b
fix(ngmp): fix lobby list deserialization and websocket frame reassembly
fbraz3 Aug 16, 2026
3e3fac3
fix(ngmp): restore staging room synchronization and map selection
fbraz3 Aug 16, 2026
4ba745f
feat(online): implement ngmp architecture bridge and menu alignment
fbraz3 Aug 16, 2026
b074bc6
feat(online): address lobby and match room gaps with reference parity
fbraz3 Aug 16, 2026
9af8557
feat(ngmp): resolve lobby menu functional gaps
fbraz3 Aug 16, 2026
de201c5
fix(build): resolve Linux GCC template error from GameSpy min/max macros
fbraz3 Aug 16, 2026
c188899
feat(ngmp): reconcile multiplayer GUI menus with reference
fbraz3 Aug 16, 2026
3bc4418
feat(ngmp): fix staging room map selection, host controls, and websoc…
fbraz3 Aug 18, 2026
8062424
fix(ngmp): synchronize multiplayer staging room, map paths, and guest…
fbraz3 Aug 18, 2026
3e71605
fix(ngmp): populate lobby members cache and fix map listbox initializ…
fbraz3 Aug 18, 2026
905f56a
chore(sync): merge origin/main into feat/generals-online-ngmp
fbraz3 Aug 18, 2026
8c4de02
fix(network): isolate NGMPGame dependency from Core InGameChat
fbraz3 Aug 18, 2026
73ca7ee
fix(gui): resolve null pointer crash in map select menu initialization
fbraz3 Aug 19, 2026
ac1f367
fix(ngmp): fix staging room synchronization and segfaults
fbraz3 Aug 19, 2026
b679744
chore(merge): sync feat/generals-online-ngmp with origin/main
fbraz3 Aug 21, 2026
aafaf48
build(windows): enable curl dependency on Windows for NGMP support
fbraz3 Aug 21, 2026
55bd24d
fix(platform): add cross-platform NGMP OpenURL helper for Windows MSVC
fbraz3 Aug 21, 2026
8e97178
fix(build): protect nlohmann json from snprintf macro pollution on MSVC
fbraz3 Aug 21, 2026
db9eb5b
fix(build): fix C4189 unused variable and restore std::snprintf in fa…
fbraz3 Aug 21, 2026
46d1eb5
fix(engine): guard NGMP against headless mode and replace GameSpy thr…
fbraz3 Aug 22, 2026
a467d2d
Resolve merge conflicts in 1 file(s)
coderabbitai[bot] Aug 22, 2026
4d92264
fix(ci): restore executable permissions and invoke flatpak build scri…
fbraz3 Aug 22, 2026
ef691ae
chore(merge): sync feat/generals-online-ngmp with origin/main
fbraz3 Aug 22, 2026
e7f4cfa
fix(ngmp): initialize rank and ladder singletons and guard null acces…
fbraz3 Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/instructions/ngmp.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
applyTo: '**/GeneralsOnline/**,**/NextGenMP/**'
---

# NGMP Subsystem Implementation Instructions

These instructions govern the Next-Gen Multiplayer (NGMP) client protocol integration into **GeneralsX**. All changes in `GeneralsOnline` and `NextGenMP` components must adhere strictly to these guidelines.

---

## Golden Constraints

1. **Strict Prohibition of `<windows.h>` & Win32 APIs**:
- Never include `<windows.h>`, `<winsock2.h>`, `<ws2tcpip.h>`, `<wincred.h>`, or `<shellapi.h>` in NGMP sources.
- Use standard C++ (`<chrono>`, `<thread>`, `<mutex>`), POSIX networking (`<sys/socket.h>`, `<netinet/in.h>`, `<arpa/inet.h>`), or SDL3 primitives (`SDL_GetTicks()`, `SDL_Delay()`, `SDL_GetPrefPath()`).

2. **Platform Layer Isolation**:
- Low-level network socket and OS calls must reside exclusively in `Core/GameEngineDevice/`.
- Higher-level network logic under `GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/` must use pure abstract interfaces and cross-platform abstractions.

3. **Thread Safety & UI Main Thread Separation**:
- Network callbacks from HTTP/WebSocket/P2P threads **MUST NOT** directly access or mutate UI controls (`GameWindow`, `WOLLobbyMenu`, etc.).
- Network events must be posted to a thread-safe thread/FIFO queue, consumed strictly on the main render thread during `Shell::update()`.

4. **Credential & Token Storage**:
- Do NOT use Windows Credential Manager.
- Save session JWT tokens in an obfuscated local configuration file under the directory provided by `SDL_GetPrefPath("GeneralsX", "GeneralsOnline")`.

5. **Server Endpoint Environment**:
- Development backend server target: `ws://192.168.1.120:9001/ws` (WebSocket) and `http://192.168.1.120:9001/api` (REST).
- Server URLs must be configurable via INI/JSON config rather than hardcoded string literals.

6. **Cross-Platform Math & Endianness**:
- Ensure network packet data serialization handles network byte order (`htons`/`ntohs`, `htonl`/`ntohl`) explicitly to support cross-play between x86_64 Linux and ARM64 macOS.

7. **Reference repositories**:
- There are two reference repositories for NGMP, take a look on these to understand the protocol and implementation details:
1. `references/GameClient`: The client-side implementation.
2. `references/GameServer`: The server-side implementation.

8. **REST API & Routing Conventions (Lessons Learned)**:
- **Environment/Contract Prefix**: The C# Kestrel backend strictly requires environment and contract version routing. Do **NOT** use `GetServerRESTEndpoint()` directly to build URLs. Always use `NGMP::GetAPIEndpoint("EndpointName")` (e.g. `NGMP::GetAPIEndpoint("Lobbies")`), which appends the required `/env/dev/contract/1/` prefix automatically.
- **Authorization**: Almost all endpoints require authorization. You must inject the `Authorization: Bearer <token>` HTTP header in all requests (GET, POST, PUT, DELETE). Missing this will result in a `401 Unauthorized` response.
- **HTTP Methods & Payloads**: Backend endpoints have strict method bindings (`[HttpGet]`, `[HttpPut]`, `[HttpPost]`). For example, creating a lobby requires `PUT /Lobbies`, while joining a lobby requires `PUT /Lobby/{id}`. The JSON payloads must also contain all expected fields (even if default or empty), or the server will reject the request with `400 Bad Request`.

9. **WebSocket Protocol Conventions (Lessons Learned)**:
- **Message ID Routing**: The backend `WebSocketController` strictly uses integer `msg_id` values to route actions, NOT string action types (e.g., `msg_id: 1` for sending chat, `msg_id: 2` for receiving chat, `msg_id: 4` for member list updates). Always include the correct `msg_id` in sent JSON payloads and use it to parse incoming messages.
- **Network Rooms (Lobby Registration)**: The backend requires the client to explicitly register into a network room to receive lobby and player updates. When initializing the global lobby chat, you must send `{"msg_id": 3, "room": 0}` (NETWORK_ROOM_CHANGE_ROOM). If you fail to do this, the server considers you in room `-1`, which causes the HTTP `GET /Lobbies` endpoint to return `0 lobbies` (as it filters by your current room) and prevents you from receiving player list updates (`msg_id: 4`).
- **Heartbeats**: The WebSocket server automatically drops connections if a PING is not received within a timeout window (often 20s). The client must periodically send `{"msg_id": 8}` (ping) at least every 10 seconds to keep the socket alive.
7 changes: 6 additions & 1 deletion .github/workflows/build-linux-flatpak.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,15 @@ jobs:
flatpak-builder-${{ runner.os }}-${{ inputs.preset }}-

- name: Build Flatpak Bundle
env:
NGMP_DEFAULT_HOST: ${{ secrets.NGMP_DEFAULT_HOST }}
NGMP_SERVER_HOST: ${{ secrets.NGMP_DEFAULT_HOST }}
NGMP_SERVER_PORT: ${{ secrets.NGMP_SERVER_PORT }}
NGMP_USE_SSL: ${{ secrets.NGMP_USE_SSL }}
run: |
set -o pipefail
mkdir -p logs
./scripts/build/linux/build-linux-flatpak.sh "${{ inputs.preset }}" "${{ matrix.game.value }}" 2>&1 | tee "logs/build_flatpak_${{ matrix.game.log_suffix }}.log"
bash ./scripts/build/linux/build-linux-flatpak.sh "${{ inputs.preset }}" "${{ matrix.game.value }}" 2>&1 | tee "logs/build_flatpak_${{ matrix.game.log_suffix }}.log"

- name: Locate generated Flatpak bundle
id: bundle
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ jobs:
CXX: clang++
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
NGMP_DEFAULT_HOST: ${{ secrets.NGMP_DEFAULT_HOST }}
NGMP_SERVER_HOST: ${{ secrets.NGMP_DEFAULT_HOST }}
NGMP_SERVER_PORT: ${{ secrets.NGMP_SERVER_PORT }}
NGMP_USE_SSL: ${{ secrets.NGMP_USE_SSL }}
run: |
mkdir -p logs
echo "VULKAN_SDK=$VULKAN_SDK"
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,13 @@ jobs:
New-Item -ItemType Directory -Force ci-artifacts | Out-Null
Copy-Item build/win32-vcpkg/${{ matrix.game.folder }}/Release/${{ matrix.game.exe }} artifacts/
Copy-Item build/win32-vcpkg/${{ matrix.game.folder }}/Release/${{ matrix.game.pdb }} artifacts/
Copy-Item build/win32-vcpkg/vcpkg_installed/x86-windows/bin/zlib1.dll artifacts/
Copy-Item build/win32-vcpkg/${{ matrix.game.folder }}/Release/${{ matrix.game.exe }} ci-artifacts/
Copy-Item build/win32-vcpkg/vcpkg_installed/x86-windows/bin/zlib1.dll ci-artifacts/

Get-ChildItem -Path @("vcpkg_installed/x86-windows/bin/*.dll", "build/win32-vcpkg/vcpkg_installed/x86-windows/bin/*.dll") -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host "Collecting vcpkg DLL: $($_.FullName)"
Copy-Item $_.FullName artifacts/
Copy-Item $_.FullName ci-artifacts/
}

foreach ($name in @("binkw32.dll", "mss32.dll")) {
$stub = Get-ChildItem build/win32-vcpkg -Filter $name -Recurse |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,6 @@ flatpak/*.flatpak
#tmp and log files on root folder
logs_*.zip
*.log
/cmake/ngmp_env.cmake
references/GameClient/
references/GameServer/
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@
path = references/fbraz3-dxvk
url = https://github.com/fbraz3/dxvk.git
branch = generalsx-macos-v2.6
[submodule "references/GameClient"]
path = references/GameClient
url = https://github.com/GeneralsOnlineDevelopmentTeam/GameClient.git
[submodule "references/GameServer"]
path = references/GameServer
url = https://github.com/GeneralsOnlineDevelopmentTeam/Services.git
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,5 +289,6 @@ The `**` at applyTo means all files, you MUST load it everytime.
| [.github/instructions/platform-macos.instructions.md](.github/instructions/platform-macos.instructions.md) | `scripts/build/macos/**,references/fbraz3-dxvk/**` | macOS/DXVK build notes |
| [.github/instructions/docs.instructions.md](.github/instructions/docs.instructions.md) | `**/*.md` | Documentation structure and workflow |
| [.github/instructions/scripts.instructions.md](.github/instructions/scripts.instructions.md) | `scripts/**` | Script organization and naming |
| [.github/instructions/ngmp.instructions.md](.github/instructions/ngmp.instructions.md) | `**/GeneralsOnline/**,**/NextGenMP/**` | NGMP cross-platform multiplayer integration guidelines |

Update this table when instruction files are added, removed, or renamed.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ include(cmake/miniaudio.cmake)
# curl.cmake is self-guarded with if(SAGE_UPDATE_CHECK), so always safe to include.
include(cmake/curl.cmake)

# GeneralsX @feature GeneralsOnline NGMP protocol dependency setup
include(cmake/ngmp.cmake)

# GeneralsX @feature fbraz 03/05/2026 Phase 4: Deterministic math library integration
# gamemath.cmake integrates fdlibm-based GameMath for cross-platform replay validation.
# Upstream reference: Okladnoj, PR #2670
Expand Down
1 change: 0 additions & 1 deletion Core/GameEngine/Include/Common/GameDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/

#pragma once

#include "WWLib/WWDefines.h"

// Note: Retail compatibility must not be broken before this project officially does.
Expand Down
12 changes: 12 additions & 0 deletions Core/GameEngine/Include/GameNetwork/GameSpy/LobbyUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,15 @@ enum GameSortType CPP_11(: Int)

Bool HandleSortButton( NameKeyType sortButton );
void PopulateLobbyPlayerListbox();

enum LobbyGameModeFilter CPP_11(: Int)
{
LOBBY_FILTER_ALL = 0,
LOBBY_FILTER_1V1,
LOBBY_FILTER_TEAM,
LOBBY_FILTER_FFA,
LOBBY_FILTER_AOD,
};

extern LobbyGameModeFilter theLobbyFilter;
LobbyGameModeFilter detectGameMode(const std::string& name);
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@

#include "gamespy/gstats/gpersist.h"

#if defined(min)
#undef min
#endif
#if defined(max)
#undef max
#endif

#define MAX_BUDDY_CHAT_LEN 128

typedef std::map<Int, UnsignedInt> PerGeneralMap;
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Include/GameNetwork/GameSpyOverlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void ClearGSMessageBoxes(); ///< Tear down any GS message boxes (e.g. in case we
void GSMessageBoxOk(UnicodeString titleString,UnicodeString bodyString, GameWinMsgBoxFunc okFunc = nullptr); ///< Display a Message box with Ok button and track it
void GSMessageBoxOkCancel(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc okFunc, GameWinMsgBoxFunc cancelFunc); ///< Display a Message box with Ok/Cancel buttons and track it
void GSMessageBoxYesNo(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc yesFunc, GameWinMsgBoxFunc noFunc); ///< Display a Message box with Yes/No buttons and track it
void GSMessageBoxCancel(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc cancelFunc = nullptr); ///< Display a Message box with Cancel button and track it
void RaiseGSMessageBox(); ///< Bring GS message box to the foreground (if we transition screens while a message box is up)

enum GSOverlayType CPP_11(: Int)
Expand Down
28 changes: 25 additions & 3 deletions Core/GameEngine/Source/Common/UserPreferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@
#include "GameClient/ChallengeGenerals.h"
#include "GameNetwork/GameSpy/PeerDefs.h"

#if defined(SAGE_USE_NGMP) && __has_include("GameNetwork/GeneralsOnline/OnlineServices_Manager.h")
#include "GameNetwork/GeneralsOnline/OnlineServices_Manager.h"
#include "GameNetwork/GeneralsOnline/OnlineServices_Auth.h"
#include "GameNetwork/GeneralsOnline/NGMP_interfaces.h"
#define HAVE_NGMP_PREFS 1
#endif


//-----------------------------------------------------------------------------
// DEFINES ////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -242,7 +249,12 @@ void UserPreferences::setAsciiString(AsciiString key, AsciiString val)
QuickMatchPreferences::QuickMatchPreferences()
{
AsciiString userPrefFilename;
Int localProfile = TheGameSpyInfo->getLocalProfileID();
#if defined(HAVE_NGMP_PREFS)
NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_AuthInterface>();
Int localProfile = pAuthInterface != nullptr ? (Int)pAuthInterface->GetUserID() : 0;
#else
Int localProfile = TheGameSpyInfo ? TheGameSpyInfo->getLocalProfileID() : 0;
#endif
userPrefFilename.format("GeneralsOnline\\QMPref%d.ini", localProfile);
load(userPrefFilename);
}
Expand Down Expand Up @@ -427,7 +439,12 @@ Int QuickMatchPreferences::getSide()
CustomMatchPreferences::CustomMatchPreferences()
{
AsciiString userPrefFilename;
Int localProfile = TheGameSpyInfo->getLocalProfileID();
#if defined(HAVE_NGMP_PREFS)
NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_AuthInterface>();
Int localProfile = pAuthInterface != nullptr ? (Int)pAuthInterface->GetUserID() : 0;
#else
Int localProfile = TheGameSpyInfo ? TheGameSpyInfo->getLocalProfileID() : 0;
#endif
userPrefFilename.format("GeneralsOnline\\CustomPref%d.ini", localProfile);
load(userPrefFilename);
}
Expand Down Expand Up @@ -776,7 +793,12 @@ void CustomMatchPreferences::setUseStats( Bool useStats )
GameSpyMiscPreferences::GameSpyMiscPreferences()
{
AsciiString userPrefFilename;
Int localProfile = TheGameSpyInfo->getLocalProfileID();
#if defined(HAVE_NGMP_PREFS)
NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface<NGMP_OnlineServices_AuthInterface>();
Int localProfile = pAuthInterface != nullptr ? (Int)pAuthInterface->GetUserID() : 0;
#else
Int localProfile = TheGameSpyInfo ? TheGameSpyInfo->getLocalProfileID() : 0;
#endif
userPrefFilename.format("GeneralsOnline\\GSMiscPref%d.ini", localProfile);
load(userPrefFilename);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ void ToggleInGameChat( Bool immediate )
if (TheGameLogic->isInReplayGame())
return;

if (!TheGameInfo->isMultiPlayer() && TheGlobalData->m_netMinPlayers)
if (TheGameInfo == nullptr || (!TheGameInfo->isMultiPlayer() && TheGlobalData->m_netMinPlayers))
return;

if (chatWindow)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2478,6 +2478,9 @@ void GadgetListBoxRemoveMultiSelect( GameWindow *listbox )
//=============================================================================
void GadgetListBoxSetListLength( GameWindow *listbox, Int newLength )
{
if (!listbox)
return;

ListboxData *listboxData = (ListboxData *)listbox->winGetUserData();


Expand Down
35 changes: 35 additions & 0 deletions Core/GameEngine/Source/GameNetwork/GameSpy/LobbyUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,41 @@ static void showSortIcons()
}
}
}
LobbyGameModeFilter theLobbyFilter = LOBBY_FILTER_ALL;

LobbyGameModeFilter detectGameMode(const std::string& name)
{
std::string modeName = name;
std::transform(modeName.begin(), modeName.end(), modeName.begin(), ::tolower);

// remove spaces
modeName.erase(std::remove(modeName.begin(), modeName.end(), ' '), modeName.end());

// handle common variations
for (size_t i = 0; i < modeName.size(); ++i)
{
if (modeName.compare(i, 2, "vs") == 0)
{
modeName.erase(i + 1, 1);
continue;
}

if (modeName[i] == 'x')
modeName[i] = 'v';
}

if (modeName.find("aod") != std::string::npos)
return LOBBY_FILTER_AOD;
if (modeName.find("ffa") != std::string::npos || modeName.find("1v1v1") != std::string::npos)
return LOBBY_FILTER_FFA;
if (modeName.find("1v1") != std::string::npos)
return LOBBY_FILTER_1V1;
if (modeName.find("2v2") != std::string::npos || modeName.find("3v3") != std::string::npos || modeName.find("4v4") != std::string::npos)
return LOBBY_FILTER_TEAM;

return LOBBY_FILTER_ALL;
}

void setSortMode( GameSortType sortType ) { theGameSortType = sortType; showSortIcons(); RefreshGameListBoxes(); }
void sortByBuddies( Bool doSort ) { sortBuddies = doSort; showSortIcons(); RefreshGameListBoxes(); }

Expand Down
10 changes: 10 additions & 0 deletions Core/GameEngine/Source/GameNetwork/GameSpyOverlay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ void GSMessageBoxYesNo(UnicodeString title, UnicodeString message, GameWinMsgBox
cancelFunc = newNoFunc;
}

// GeneralsX @feature fbraz3 17/08/2026 Add GSMessageBoxCancel support for NGMP asynchronous lobby creation
void GSMessageBoxCancel(UnicodeString title, UnicodeString message, GameWinMsgBoxFunc newCancelFunc)
{
ClearGSMessageBoxes();
messageBoxWindow = MessageBoxCancel(title, message, messageBoxCancel);
cancelFunc = newCancelFunc;
}

/**
* If the screen transitions underneath the dialog box, we
* need to raise it to keep it visible.
Expand Down Expand Up @@ -194,6 +202,7 @@ void GameSpyOpenOverlay( GSOverlayType overlay )
{
if (overlay == GSOVERLAY_BUDDY)
{
#if !defined(SAGE_USE_NGMP)
if (!TheGameSpyBuddyMessageQueue->isConnected())
{
// not connected - is it because we were disconnected?
Expand All @@ -209,6 +218,7 @@ void GameSpyOpenOverlay( GSOverlayType overlay )
}
return;
}
#endif
AudioEventRTS buttonClick("GUICommunicatorOpen");

if( TheAudio )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,11 @@ WindowMsgHandledType MainMenuSystem( GameWindow *window, UnsignedInt msg,
dropDownWindows[DROPDOWN_MULTIPLAYER]->winHide(FALSE);
TheTransitionHandler->reverse("MainMenuMultiPlayerMenuTransitionToNext");

#if defined(SAGE_USE_NGMP)
TheShell->push( "Menus/GameSpyLoginProfile.wnd" );
#else
StartPatchCheck();
#endif
// localAnimateWindowManager->reverseAnimateWindow();
dropDown = DROPDOWN_NONE;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,9 +762,12 @@ void PopulatePlayerInfoWindows( AsciiString parentWindowName )
Int currentRank = 0;
Int rankPoints = CalculateRank(stats);
Int i = 0;
while( rankPoints >= TheRankPointValues->m_ranks[i + 1])
++i;
currentRank = i;
if (TheRankPointValues)
{
while (i + 1 < MAX_RANKS && rankPoints >= TheRankPointValues->m_ranks[i + 1])
++i;
currentRank = i;
}

PerGeneralMap::iterator it;
Int numWins = 0;
Expand Down Expand Up @@ -971,7 +974,15 @@ void PopulatePlayerInfoWindows( AsciiString parentWindowName )
}
else
{
GadgetProgressBarSetProgress(win, 100 * INT_TO_REAL(rankPoints - TheRankPointValues->m_ranks[currentRank])/( TheRankPointValues->m_ranks[currentRank + 1] - TheRankPointValues->m_ranks[currentRank]));
Int diff = TheRankPointValues->m_ranks[currentRank + 1] - TheRankPointValues->m_ranks[currentRank];
if (diff > 0)
{
GadgetProgressBarSetProgress(win, 100 * INT_TO_REAL(rankPoints - TheRankPointValues->m_ranks[currentRank]) / INT_TO_REAL(diff));
}
else
{
GadgetProgressBarSetProgress(win, 100);
}
}
}
win = findWindow(nullptr, parentWindowName, "WinRank");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,12 @@ static void playerTooltip(GameWindow *window,

Int rank = 0;
Int i = 0;
while( info->m_rankPoints >= TheRankPointValues->m_ranks[i + 1])
++i;
rank = i;
if (TheRankPointValues)
{
while (i + 1 < MAX_RANKS && info->m_rankPoints >= TheRankPointValues->m_ranks[i + 1])
++i;
rank = i;
}
AsciiString sideName = "GUI:RandomSide";
if (info->m_side > 0)
{
Expand Down Expand Up @@ -388,9 +391,12 @@ const Image* LookupSmallRankImage(Int side, Int rankPoints)

Int rank = 0;
Int i = 0;
while( rankPoints >= TheRankPointValues->m_ranks[i + 1])
++i;
rank = i;
if (TheRankPointValues)
{
while (i + 1 < MAX_RANKS && rankPoints >= TheRankPointValues->m_ranks[i + 1])
++i;
rank = i;
}

if (rank < 0 || rank >= 10)
return nullptr;
Expand Down
Loading
Loading