From 82092ab6da1bfce33526ba815df763157458014a Mon Sep 17 00:00:00 2001
From: tintinhamans <5984296+tintinhamans@users.noreply.github.com>
Date: Sat, 26 Sep 2026 16:23:39 +0200
Subject: [PATCH 1/2] chore: Normalize line endings in WOLQuickMatchMenu.cpp
---
.../GUICallbacks/Menus/WOLQuickMatchMenu.cpp | 5144 ++++++++---------
1 file changed, 2572 insertions(+), 2572 deletions(-)
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp
index 6ebd35767a8..26697a6fb5d 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp
@@ -1,1227 +1,1227 @@
-/*
-** Command & Conquer Generals Zero Hour(tm)
-** Copyright 2025 Electronic Arts Inc.
-**
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU General Public License as published by
-** the Free Software Foundation, either version 3 of the License, or
-** (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU General Public License for more details.
-**
-** You should have received a copy of the GNU General Public License
-** along with this program. If not, see .
-*/
-
-////////////////////////////////////////////////////////////////////////////////
-// //
-// (c) 2001-2003 Electronic Arts Inc. //
-// //
-////////////////////////////////////////////////////////////////////////////////
-
-///////////////////////////////////////////////////////////////////////////////////////
-// FILE: WOLQuickMatchMenu.cpp
-// Author: Chris Huybregts, November 2001
-// Description: Lan Lobby Menu
-///////////////////////////////////////////////////////////////////////////////////////
-
-// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
-#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
-
-#include "Common/GameEngine.h"
-#include "Common/QuickmatchPreferences.h"
-#include "Common/LadderPreferences.h"
-#include "Common/MultiplayerSettings.h"
-#include "Common/OptionPreferences.h"
-#include "Common/PlayerTemplate.h"
-#include "GameClient/AnimateWindowManager.h"
-#include "GameClient/WindowLayout.h"
-#include "GameClient/Gadget.h"
-#include "GameClient/GameText.h"
-#include "GameClient/InGameUI.h"
-#include "GameClient/Display.h"
-#include "GameClient/Shell.h"
-#include "GameClient/ShellHooks.h"
-#include "GameClient/KeyDefs.h"
-#include "GameClient/GameWindowManager.h"
-#include "GameClient/GadgetComboBox.h"
-#include "GameClient/GadgetPushButton.h"
-#include "GameClient/GadgetListBox.h"
-#include "GameClient/GadgetTextEntry.h"
-#include "GameClient/GadgetStaticText.h"
-#include "GameClient/MapUtil.h"
-#include "GameClient/GameWindowTransitions.h"
-#include "GameClient/ChallengeGenerals.h"
-
-#include "GameLogic/GameLogic.h"
-
-#include "GameNetwork/NAT.h"
-#include "GameNetwork/GameSpyOverlay.h"
-#include "GameNetwork/GameSpy/BuddyDefs.h"
-#include "GameNetwork/GameSpy/GSConfig.h"
-#include "GameNetwork/GameSpy/PeerDefs.h"
-#include "GameNetwork/GameSpy/PeerThread.h"
-#include "GameNetwork/GameSpy/PersistentStorageDefs.h"
-#include "GameNetwork/GameSpy/PersistentStorageThread.h"
-#include "GameNetwork/RankPointValue.h"
-#include "GameNetwork/GameSpy/LadderDefs.h"
-
-#ifdef DEBUG_LOGGING
-#include "Common/MiniLog.h"
-//#define PERF_TEST
-static LogClass s_perfLog("QMPerf.txt");
-static Bool s_inQM = FALSE;
-#define PERF_LOG(x) s_perfLog.log x
-#else // DEBUG_LOGGING
-#define PERF_LOG(x)
-#endif // DEBUG_LOGGING
-
-#if defined(GENERALS_ONLINE)
-#include "../OnlineServices_Init.h"
-#include "../OnlineServices_Auth.h"
-#include "../NGMPGame.h"
-extern NGMPGame* TheNGMPGame;
-#endif
-#include "../OnlineServices_MatchmakingInterface.h"
-#include "../OnlineServices_LobbyInterface.h"
-#include "../OnlineServices_SocialInterface.h"
-#include "../OnlineServices_StatsInterface.h"
-
-// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
-// window ids ------------------------------------------------------------------------------
-static NameKeyType parentWOLQuickMatchID = NAMEKEY_INVALID;
-static NameKeyType buttonBackID = NAMEKEY_INVALID;
-static NameKeyType buttonStartID = NAMEKEY_INVALID;
-static NameKeyType buttonStopID = NAMEKEY_INVALID;
-static NameKeyType buttonWidenID = NAMEKEY_INVALID;
-static NameKeyType buttonBuddiesID = NAMEKEY_INVALID;
-static NameKeyType listboxQuickMatchID = NAMEKEY_INVALID;
-static NameKeyType listboxMapSelectID = NAMEKEY_INVALID;
-static NameKeyType buttonSelectAllMapsID = NAMEKEY_INVALID;
-static NameKeyType buttonSelectNoMapsID = NAMEKEY_INVALID;
-//static NameKeyType textEntryMaxDisconnectsID = NAMEKEY_INVALID;
-//static NameKeyType textEntryMaxPointsID = NAMEKEY_INVALID;
-//static NameKeyType textEntryMinPointsID = NAMEKEY_INVALID;
-static NameKeyType textEntryWaitTimeID = NAMEKEY_INVALID;
-static NameKeyType comboBoxNumPlayersID = NAMEKEY_INVALID;
-static NameKeyType comboBoxMaxPingID = NAMEKEY_INVALID;
-static NameKeyType comboBoxLadderID = NAMEKEY_INVALID;
-static NameKeyType comboBoxMaxDisconnectsID = NAMEKEY_INVALID;
-static NameKeyType staticTextNumPlayersID = NAMEKEY_INVALID;
-static NameKeyType comboBoxSideID = NAMEKEY_INVALID;
-static NameKeyType comboBoxColorID = NAMEKEY_INVALID;
-
-
-// Window Pointers ------------------------------------------------------------------------
-static GameWindow *parentWOLQuickMatch = nullptr;
-static GameWindow *buttonBack = nullptr;
-static GameWindow *buttonStart = nullptr;
-static GameWindow *buttonStop = nullptr;
-static GameWindow *buttonWiden = nullptr;
-GameWindow *quickmatchTextWindow = nullptr;
-static GameWindow *listboxMapSelect = nullptr;
-//static GameWindow *textEntryMaxDisconnects = nullptr;
-//static GameWindow *textEntryMaxPoints = nullptr;
-//static GameWindow *textEntryMinPoints = nullptr;
-static GameWindow *textEntryWaitTime = nullptr;
-static GameWindow *comboBoxNumPlayers = nullptr;
-static GameWindow *comboBoxMaxPing = nullptr;
-static GameWindow *comboBoxLadder = nullptr;
-static GameWindow *comboBoxDisabledLadder = nullptr; // enable and disable this, but never use it. it is a stand-in for comboBoxLadder for when there are no ladders
-static GameWindow *comboBoxMaxDisconnects = nullptr;
-static GameWindow *staticTextNumPlayers = nullptr;
-static GameWindow *comboBoxSide = nullptr;
-static GameWindow *comboBoxColor = nullptr;
-
-static Bool isShuttingDown = false;
-static Bool buttonPushed = false;
-static const char *nextScreen = nullptr;
-static Bool raiseMessageBoxes = false;
-static Bool isInInit = FALSE;
-static const Image *selectedImage = nullptr;
-static const Image *unselectedImage = nullptr;
-static const Image *mapHoverPreview = nullptr;
-static GameWinDrawFunc mapListboxPreviewFunc = nullptr;
-
-static bool isPopulatingLadderBox = false;
-static Int maxPingEntries = 0;
-static Int maxPoints= 100;
-static Int minPoints = 0;
-
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2025 Electronic Arts Inc.
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+// //
+// (c) 2001-2003 Electronic Arts Inc. //
+// //
+////////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////////////
+// FILE: WOLQuickMatchMenu.cpp
+// Author: Chris Huybregts, November 2001
+// Description: Lan Lobby Menu
+///////////////////////////////////////////////////////////////////////////////////////
+
+// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
+#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
+
+#include "Common/GameEngine.h"
+#include "Common/QuickmatchPreferences.h"
+#include "Common/LadderPreferences.h"
+#include "Common/MultiplayerSettings.h"
+#include "Common/OptionPreferences.h"
+#include "Common/PlayerTemplate.h"
+#include "GameClient/AnimateWindowManager.h"
+#include "GameClient/WindowLayout.h"
+#include "GameClient/Gadget.h"
+#include "GameClient/GameText.h"
+#include "GameClient/InGameUI.h"
+#include "GameClient/Display.h"
+#include "GameClient/Shell.h"
+#include "GameClient/ShellHooks.h"
+#include "GameClient/KeyDefs.h"
+#include "GameClient/GameWindowManager.h"
+#include "GameClient/GadgetComboBox.h"
+#include "GameClient/GadgetPushButton.h"
+#include "GameClient/GadgetListBox.h"
+#include "GameClient/GadgetTextEntry.h"
+#include "GameClient/GadgetStaticText.h"
+#include "GameClient/MapUtil.h"
+#include "GameClient/GameWindowTransitions.h"
+#include "GameClient/ChallengeGenerals.h"
+
+#include "GameLogic/GameLogic.h"
+
+#include "GameNetwork/NAT.h"
+#include "GameNetwork/GameSpyOverlay.h"
+#include "GameNetwork/GameSpy/BuddyDefs.h"
+#include "GameNetwork/GameSpy/GSConfig.h"
+#include "GameNetwork/GameSpy/PeerDefs.h"
+#include "GameNetwork/GameSpy/PeerThread.h"
+#include "GameNetwork/GameSpy/PersistentStorageDefs.h"
+#include "GameNetwork/GameSpy/PersistentStorageThread.h"
+#include "GameNetwork/RankPointValue.h"
+#include "GameNetwork/GameSpy/LadderDefs.h"
+
+#ifdef DEBUG_LOGGING
+#include "Common/MiniLog.h"
+//#define PERF_TEST
+static LogClass s_perfLog("QMPerf.txt");
+static Bool s_inQM = FALSE;
+#define PERF_LOG(x) s_perfLog.log x
+#else // DEBUG_LOGGING
+#define PERF_LOG(x)
+#endif // DEBUG_LOGGING
+
+#if defined(GENERALS_ONLINE)
+#include "../OnlineServices_Init.h"
+#include "../OnlineServices_Auth.h"
+#include "../NGMPGame.h"
+extern NGMPGame* TheNGMPGame;
+#endif
+#include "../OnlineServices_MatchmakingInterface.h"
+#include "../OnlineServices_LobbyInterface.h"
+#include "../OnlineServices_SocialInterface.h"
+#include "../OnlineServices_StatsInterface.h"
+
+// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
+// window ids ------------------------------------------------------------------------------
+static NameKeyType parentWOLQuickMatchID = NAMEKEY_INVALID;
+static NameKeyType buttonBackID = NAMEKEY_INVALID;
+static NameKeyType buttonStartID = NAMEKEY_INVALID;
+static NameKeyType buttonStopID = NAMEKEY_INVALID;
+static NameKeyType buttonWidenID = NAMEKEY_INVALID;
+static NameKeyType buttonBuddiesID = NAMEKEY_INVALID;
+static NameKeyType listboxQuickMatchID = NAMEKEY_INVALID;
+static NameKeyType listboxMapSelectID = NAMEKEY_INVALID;
+static NameKeyType buttonSelectAllMapsID = NAMEKEY_INVALID;
+static NameKeyType buttonSelectNoMapsID = NAMEKEY_INVALID;
+//static NameKeyType textEntryMaxDisconnectsID = NAMEKEY_INVALID;
+//static NameKeyType textEntryMaxPointsID = NAMEKEY_INVALID;
+//static NameKeyType textEntryMinPointsID = NAMEKEY_INVALID;
+static NameKeyType textEntryWaitTimeID = NAMEKEY_INVALID;
+static NameKeyType comboBoxNumPlayersID = NAMEKEY_INVALID;
+static NameKeyType comboBoxMaxPingID = NAMEKEY_INVALID;
+static NameKeyType comboBoxLadderID = NAMEKEY_INVALID;
+static NameKeyType comboBoxMaxDisconnectsID = NAMEKEY_INVALID;
+static NameKeyType staticTextNumPlayersID = NAMEKEY_INVALID;
+static NameKeyType comboBoxSideID = NAMEKEY_INVALID;
+static NameKeyType comboBoxColorID = NAMEKEY_INVALID;
+
+
+// Window Pointers ------------------------------------------------------------------------
+static GameWindow *parentWOLQuickMatch = nullptr;
+static GameWindow *buttonBack = nullptr;
+static GameWindow *buttonStart = nullptr;
+static GameWindow *buttonStop = nullptr;
+static GameWindow *buttonWiden = nullptr;
+GameWindow *quickmatchTextWindow = nullptr;
+static GameWindow *listboxMapSelect = nullptr;
+//static GameWindow *textEntryMaxDisconnects = nullptr;
+//static GameWindow *textEntryMaxPoints = nullptr;
+//static GameWindow *textEntryMinPoints = nullptr;
+static GameWindow *textEntryWaitTime = nullptr;
+static GameWindow *comboBoxNumPlayers = nullptr;
+static GameWindow *comboBoxMaxPing = nullptr;
+static GameWindow *comboBoxLadder = nullptr;
+static GameWindow *comboBoxDisabledLadder = nullptr; // enable and disable this, but never use it. it is a stand-in for comboBoxLadder for when there are no ladders
+static GameWindow *comboBoxMaxDisconnects = nullptr;
+static GameWindow *staticTextNumPlayers = nullptr;
+static GameWindow *comboBoxSide = nullptr;
+static GameWindow *comboBoxColor = nullptr;
+
+static Bool isShuttingDown = false;
+static Bool buttonPushed = false;
+static const char *nextScreen = nullptr;
+static Bool raiseMessageBoxes = false;
+static Bool isInInit = FALSE;
+static const Image *selectedImage = nullptr;
+static const Image *unselectedImage = nullptr;
+static const Image *mapHoverPreview = nullptr;
+static GameWinDrawFunc mapListboxPreviewFunc = nullptr;
+
+static bool isPopulatingLadderBox = false;
+static Int maxPingEntries = 0;
+static Int maxPoints= 100;
+static Int minPoints = 0;
+
static Int matchFoundTimeoutStart = 0;
static const Int lobbyTimeoutMs = 10000;
static Int matchFoundTimeoutDurationMs = lobbyTimeoutMs;
static const Int defaultMatchStartCountdownMs = 5000;
static Int matchStartCountdownDurationMs = defaultMatchStartCountdownMs;
static Int matchStartCountdownLastSecond = 0;
-
-static const LadderInfo * getLadderInfo();
-
-static Bool isInfoShown()
-{
- static NameKeyType parentStatsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentStats");
- GameWindow *parentStats = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentStatsID );
- if (parentStats)
- return !parentStats->winIsHidden();
- return FALSE;
-}
-
-static void hideInfoGadgets(Bool doIt)
-{
- static NameKeyType parentStatsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentStats");
- GameWindow *parentStats = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentStatsID );
- if (parentStats)
- {
- parentStats->winHide(doIt);
- }
-}
-
-static void hideOptionsGadgets(Bool doIt)
-{
- static NameKeyType parentOptionsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentOptions");
- GameWindow *parentOptions = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentOptionsID );
- if (parentOptions)
- {
- parentOptions->winHide(doIt);
- if (comboBoxSide)
- comboBoxSide->winHide(doIt);
- if (comboBoxColor)
- comboBoxColor->winHide(doIt);
- if (comboBoxNumPlayers)
- comboBoxNumPlayers->winHide(doIt);
- if (comboBoxLadder)
- comboBoxLadder->winHide(doIt);
- if (comboBoxDisabledLadder)
- comboBoxDisabledLadder->winHide(doIt);
- if (comboBoxMaxPing)
- comboBoxMaxPing->winHide(doIt);
- if (comboBoxMaxDisconnects)
- comboBoxMaxDisconnects->winHide(doIt);
- }
-}
-
-static void enableOptionsGadgets(Bool doIt)
-{
-#ifdef PERF_TEST
- s_inQM = !doIt;
-#endif // PERF_TEST
- static NameKeyType parentOptionsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentOptions");
- GameWindow *parentOptions = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentOptionsID );
- const LadderInfo *li = getLadderInfo();
- if (parentOptions)
- {
- parentOptions->winEnable(doIt);
- if (comboBoxSide)
- comboBoxSide->winEnable(doIt && (!li || !li->randomFactions));
- if (comboBoxColor)
- comboBoxColor->winEnable(doIt);
- if (comboBoxNumPlayers)
- comboBoxNumPlayers->winEnable(doIt);
-
-#if !defined(GENERALS_ONLINE)
- if (comboBoxLadder)
- comboBoxLadder->winEnable(doIt);
- if (comboBoxDisabledLadder)
- comboBoxDisabledLadder->winEnable(FALSE);
- if (comboBoxMaxPing)
- comboBoxMaxPing->winEnable(doIt);
- if (comboBoxMaxDisconnects)
- comboBoxMaxDisconnects->winEnable(doIt);
-#endif
- }
-}
-
-enum
-{
- MAX_DISCONNECTS_ANY = 0,
- MAX_DISCONNECTS_5 = 5,
- MAX_DISCONNECTS_10 = 10,
- MAX_DISCONNECTS_25 = 25,
- MAX_DISCONNECTS_50 = 50,
-};
-enum{ MAX_DISCONNECTS_COUNT = 5 };
-
-static Int MAX_DISCONNECTS[MAX_DISCONNECTS_COUNT] = {MAX_DISCONNECTS_ANY, MAX_DISCONNECTS_5,
- MAX_DISCONNECTS_10, MAX_DISCONNECTS_25,
- MAX_DISCONNECTS_50};
-void updateMapHoverPreview(GameWindow* window, WinInstanceData* instData)
-{
- if (mapListboxPreviewFunc)
- mapListboxPreviewFunc(window, instData);
- if (listboxMapSelect == nullptr)
- return;
-
- const MouseIO* mouseStatus = TheMouse->getMouseStatus();
- Int mouseX = mouseStatus->pos.x;
- Int mouseY = mouseStatus->pos.y;
- Int listboxX, listboxY, listboxW, listboxH;
- listboxMapSelect->winGetScreenPosition(&listboxX, &listboxY);
- listboxMapSelect->winGetSize(&listboxW, &listboxH);
-
- // mouse is outside the listbox?
- if (mouseX < listboxX || mouseX > listboxX + listboxW || mouseY < listboxY || mouseY > listboxY + listboxH)
- return;
-
- Int hoveredRow, col;
- GadgetListBoxGetEntryBasedOnXY(listboxMapSelect, mouseX, mouseY, hoveredRow, col);
- const MapMetaData* mapData = (const MapMetaData*)GadgetListBoxGetItemData(listboxMapSelect, hoveredRow, 1);
- mapHoverPreview = mapData ? getMapPreviewImage(mapData->m_fileName) : nullptr;
-
- if (mapHoverPreview == nullptr)
- return;
-
- Real wScale = TheDisplay->getWidth() / (Real)DEFAULT_DISPLAY_WIDTH;
- Real hScale = TheDisplay->getHeight() / (Real)DEFAULT_DISPLAY_HEIGHT;
- Real scale = (wScale + hScale) * 0.5f;
- Int previewSize = (Int)(50 * scale);
- Int offset = (Int)(20 * scale);
- Int previewX = mouseX + offset;
- Int previewY = mouseY - (previewSize / 2);
- TheWindowManager->winDrawImage(mapHoverPreview, previewX, previewY, previewX + previewSize, previewY + previewSize);
-}
-
-void UpdateStartButton()
-{
- if (!comboBoxLadder || !buttonStart || !listboxMapSelect)
- return;
-
- Int index;
- Int selected;
- GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
- index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
- const LadderInfo *li = TheLadderList->findLadderByIndex( index );
- if (li)
- {
- buttonStart->winEnable(TRUE);
- return;
- }
-
- Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
- for ( Int i=0; iwinEnable(TRUE);
- return;
- }
- }
- buttonStart->winEnable(FALSE);
-}
-
-// -----------------------------------------------------------------------------
-
-static void populateQMColorComboBox(QuickMatchPreferences& pref)
-{
- Int numColors = TheMultiplayerSettings->getNumColors();
- UnicodeString colorName;
-
- GadgetComboBoxReset(comboBoxColor);
-
- MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_RANDOM);
- Int newIndex = GadgetComboBoxAddEntry(comboBoxColor, TheGameText->fetch("GUI:???"), def->getColor());
- GadgetComboBoxSetItemData(comboBoxColor, newIndex, (void *)-1);
-
- for (Int c=0; cgetColor(c);
- if (!def)
- continue;
-
- colorName = TheGameText->fetch(def->getTooltipName().str());
- newIndex = GadgetComboBoxAddEntry(comboBoxColor, colorName, def->getColor());
- GadgetComboBoxSetItemData(comboBoxColor, newIndex, (void *)c);
- }
- GadgetComboBoxSetSelectedPos(comboBoxColor, pref.getColor());
-}
-
-// -----------------------------------------------------------------------------
-
-static void populateQMSideComboBox(Int favSide, const LadderInfo *li = nullptr)
-{
- Int numPlayerTemplates = ThePlayerTemplateStore->getPlayerTemplateCount();
- UnicodeString playerTemplateName;
-
- GadgetComboBoxReset(comboBoxSide);
-
- MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_RANDOM);
- Int newIndex = GadgetComboBoxAddEntry(comboBoxSide, TheGameText->fetch("GUI:Random"), def->getColor());
- GadgetComboBoxSetItemData(comboBoxSide, newIndex, (void *)PLAYERTEMPLATE_RANDOM);
-
- std::set seenSides;
-
- Int entryToSelect = 0; // select Random by default
-
- for (Int c=0; cgetNthPlayerTemplate(c);
- if (!fac)
- continue;
-
- if (fac->getStartingBuilding().isEmpty())
- continue;
-
- AsciiString side;
- side.format("SIDE:%s", fac->getSide().str());
- if (seenSides.find(side) != seenSides.end())
- continue;
-
- if (li)
- {
- if (std::find(li->validFactions.begin(), li->validFactions.end(), fac->getSide()) == li->validFactions.end())
- continue; // ladder doesn't allow it.
- }
-
- // Remove disallowed generals from the choice list.
- // This is also enforced at GUI setup (GUIUtil.cpp and UserPreferences.cpp).
- // @todo: unlock these when something rad happens
- Bool disallowLockedGenerals = TRUE;
- const GeneralPersona *general = TheChallengeGenerals->getGeneralByTemplateName(fac->getName());
- Bool startsLocked = general ? !general->isStartingEnabled() : FALSE;
- if (disallowLockedGenerals && startsLocked)
- continue;
-
- seenSides.insert(side);
-
- newIndex = GadgetComboBoxAddEntry(comboBoxSide, TheGameText->fetch(side), def->getColor());
- GadgetComboBoxSetItemData(comboBoxSide, newIndex, (void *)c);
-
- if (c == favSide)
- entryToSelect = newIndex;
- }
- seenSides.clear();
-
- GadgetComboBoxSetSelectedPos(comboBoxSide, entryToSelect);
- if (li && li->randomFactions)
- comboBoxSide->winEnable(FALSE);
- else
- comboBoxSide->winEnable(TRUE);
-}
-
-void HandleQMLadderSelection(Int ladderID)
-{
- if (!parentWOLQuickMatch)
- return;
-
- QuickMatchPreferences pref;
-
- if (ladderID < 1)
- {
- pref.setLastLadder(AsciiString::TheEmptyString, 0);
- pref.write();
- return;
- }
-
- const LadderInfo *info = TheLadderList->findLadderByIndex(ladderID);
- if (!info)
- {
- pref.setLastLadder(AsciiString::TheEmptyString, 0);
- }
- else
- {
- pref.setLastLadder(info->address, info->port);
- }
-
- pref.write();
-}
-
-static inline Bool isValidLadder( const LadderInfo *lad )
-{
- if (lad && lad->index > 0 && lad->validQM)
- {
- PSPlayerStats stats = TheGameSpyPSMessageQueue->findPlayerStatsByID(TheGameSpyInfo->getLocalProfileID());
- Int numWins = 0;
- PerGeneralMap::iterator it;
- for (it = stats.wins.begin(); it != stats.wins.end(); ++it)
- {
- numWins += it->second;
- }
- if (!lad->maxWins || lad->maxWins >=numWins)
- {
- if (!lad->minWins || lad->minWins<=numWins)
- {
- return TRUE;
- }
- }
- }
- return FALSE;
-}
-
-void PopulateQMLadderListBox( GameWindow *win )
-{
- if (!parentWOLQuickMatch || !comboBoxLadder)
- return;
-
- isPopulatingLadderBox = true;
-
- QuickMatchPreferences pref;
- AsciiString userPrefFilename;
-
-#if !defined(GENERALS_ONLINE)
- Int localProfile = TheGameSpyInfo->getLocalProfileID();
-#endif
-
- Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
-#if !defined(GENERALS_ONLINE)
- Color specialColor = GameSpyColor[GSCOLOR_MAP_SELECTED];
- Color favoriteColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
-#endif
- Int index;
- GadgetListBoxReset( win );
-
- std::set usedLadders;
-
- // start with "No Ladder"
-#if defined(GENERALS_ONLINE)
- index = GadgetListBoxAddEntryText(win, UnicodeString(L"Automatic Ladder"), normalColor, -1);
- GadgetListBoxSetItemData(win, 0, index);
-#else
- index = GadgetListBoxAddEntryText( win, TheGameText->fetch("GUI:NoLadder"), normalColor, -1 );
- GadgetListBoxSetItemData( win, nullptr, index );
-
- // add the last ladder
- Int selectedPos = 0;
- AsciiString lastLadderAddr = pref.getLastLadderAddr();
- UnsignedShort lastLadderPort = pref.getLastLadderPort();
- const LadderInfo *info = TheLadderList->findLadder( lastLadderAddr, lastLadderPort );
- if (isValidLadder(info))
- {
- usedLadders.insert(info);
- index = GadgetListBoxAddEntryText( win, info->name, favoriteColor, -1 );
- GadgetListBoxSetItemData( win, (void *)(info->index), index );
- selectedPos = index;
- }
-
- // our recent ladders
- LadderPreferences ladPref;
- ladPref.loadProfile( localProfile );
- const LadderPrefMap recentLadders = ladPref.getRecentLadders();
- for (LadderPrefMap::const_iterator cit = recentLadders.begin(); cit != recentLadders.end(); ++cit)
- {
- AsciiString addr = cit->second.address;
- UnsignedShort port = cit->second.port;
- if (addr == lastLadderAddr && port == lastLadderPort)
- continue;
- const LadderInfo *info = TheLadderList->findLadder( addr, port );
- if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
- {
- usedLadders.insert(info);
- index = GadgetListBoxAddEntryText( win, info->name, favoriteColor, -1 );
- GadgetListBoxSetItemData( win, (void *)(info->index), index );
- }
- }
-
- // special ladders
- const LadderInfoList *lil = TheLadderList->getSpecialLadders();
- LadderInfoList::const_iterator lit;
- for (lit = lil->begin(); lit != lil->end(); ++lit)
- {
- const LadderInfo *info = *lit;
- if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
- {
- usedLadders.insert(info);
- index = GadgetListBoxAddEntryText( win, info->name, specialColor, -1 );
- GadgetListBoxSetItemData( win, (void *)(info->index), index );
- }
- }
-
- // standard ladders
- lil = TheLadderList->getStandardLadders();
- for (lit = lil->begin(); lit != lil->end(); ++lit)
- {
- const LadderInfo *info = *lit;
- if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
- {
- usedLadders.insert(info);
- index = GadgetListBoxAddEntryText( win, info->name, normalColor, -1 );
- GadgetListBoxSetItemData( win, (void *)(info->index), index );
- }
- }
-
- GadgetListBoxSetSelected( win, selectedPos );
-#endif
- isPopulatingLadderBox = false;
-}
-
-static const LadderInfo * getLadderInfo()
-{
- Int index;
- Int selected;
- GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
- index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
- const LadderInfo *li = TheLadderList->findLadderByIndex( index );
- return li;
-}
-
-void PopulateQMLadderComboBox()
-{
- if (!parentWOLQuickMatch || !comboBoxLadder)
- return;
-
- isPopulatingLadderBox = true;
-
- QuickMatchPreferences pref;
-
-#if defined(GENERALS_ONLINE)
- NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface();
-
- int64_t localProfile = pAuthInterface != nullptr ? pAuthInterface->GetUserID() : -1;
-#else
- Int localProfile = TheGameSpyInfo->getLocalProfileID();
-#endif
-
- Color specialColor = GameSpyColor[GSCOLOR_MAP_SELECTED];
- Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
- Int index;
- GadgetComboBoxReset( comboBoxLadder );
- index = GadgetComboBoxAddEntry( comboBoxLadder, TheGameText->fetch("GUI:NoLadder"), normalColor );
- GadgetComboBoxSetItemData( comboBoxLadder, index, nullptr );
-
- std::set usedLadders;
-
- Int selectedPos = 0;
- AsciiString lastLadderAddr = pref.getLastLadderAddr();
- UnsignedShort lastLadderPort = pref.getLastLadderPort();
- const LadderInfo *info = TheLadderList->findLadder( lastLadderAddr, lastLadderPort );
- if (isValidLadder(info))
- {
- usedLadders.insert(info);
- index = GadgetComboBoxAddEntry( comboBoxLadder, info->name, specialColor );
- GadgetComboBoxSetItemData( comboBoxLadder, index, (void *)(info->index) );
- selectedPos = index;
-
- // we selected a ladder? No game size choice for us...
- GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, info->playersPerTeam-1);
- comboBoxNumPlayers->winEnable( FALSE );
- }
- else
- {
- comboBoxNumPlayers->winEnable( TRUE );
- }
-
- LadderPreferences ladPref;
- ladPref.loadProfile( localProfile );
- const LadderPrefMap recentLadders = ladPref.getRecentLadders();
- for (LadderPrefMap::const_iterator cit = recentLadders.begin(); cit != recentLadders.end(); ++cit)
- {
- AsciiString addr = cit->second.address;
- UnsignedShort port = cit->second.port;
- if (addr == lastLadderAddr && port == lastLadderPort)
- continue;
- const LadderInfo *info = TheLadderList->findLadder( addr, port );
- if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
- {
- usedLadders.insert(info);
- index = GadgetComboBoxAddEntry( comboBoxLadder, info->name, normalColor );
- GadgetComboBoxSetItemData( comboBoxLadder, index, (void *)(info->index) );
- }
- }
-
- index = GadgetComboBoxAddEntry( comboBoxLadder, TheGameText->fetch("GUI:ChooseLadder"), normalColor );
- GadgetComboBoxSetItemData( comboBoxLadder, index, (void *)-1 );
-
- GadgetComboBoxSetSelectedPos( comboBoxLadder, selectedPos );
- isPopulatingLadderBox = false;
-
- populateQMSideComboBox(pref.getSide(), getLadderInfo()); // this will set side to random and disable if necessary
-}
-
-static void populateQuickMatchMapSelectListbox( QuickMatchPreferences& pref )
-{
- NetworkLog(ELogVerbosity::LOG_DEBUG, "Begin Map Dump");
- for (auto it = TheMapCache->begin(); it != TheMapCache->end(); ++it)
- {
- if (it->second.m_isOfficial && it->second.m_isMultiplayer)
- {
- NetworkLog(ELogVerbosity::LOG_DEBUG, "%ls - %s", it->second.m_displayName.str(), it->second.m_fileName.str());
- }
- }
- NetworkLog(ELogVerbosity::LOG_DEBUG, "End Map Dump");
-
- // TODO_QUICKMATCH
-#if defined(GENERALS_ONLINE)
- std::list maps;
- Int numPlayers = 0;
- Int playlistIndex = -1;
- GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &playlistIndex);
-
- if (playlistIndex != -1)
- {
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- PlaylistEntry plEntry = pMatchmakingInterface->GetCachedPlaylistFromIndex(playlistIndex);
- if (plEntry.PlaylistID != -1)
- {
- // take the min player count, we'll offer maps in that range, up to desired player count
- numPlayers = plEntry.MinPlayers;
-
- // maps
- for (PlaylistMapEntry& mapEntry : plEntry.Maps)
- {
- // format into game format (Maps\\name\\name.map) for official EA maps or full path for custom maps
- std::string correctedMapPath;
-
- // custom maps need the full path
- if (mapEntry.Custom)
- {
- // TODO_QUICKMATCH: How did the original gamespy service handle this?
- correctedMapPath = std::format("{}maps\\{}\\{}.map", TheGlobalData->getPath_UserData().str(), mapEntry.Path, mapEntry.Path);
- }
- else
- {
- correctedMapPath = std::format("maps\\{}\\{}.map", mapEntry.Path, mapEntry.Path);
- }
-
- AsciiString mapPath = correctedMapPath.c_str();
- mapPath.toLower();
- maps.push_back(mapPath);
- }
- }
- else
- {
- // TODO_QUICKMATCH: Error?
- }
- }
- }
- else
- {
- // TODO_QUICKMATCH: Error?
- }
-
-#else
- std::list maps = TheGameSpyConfig->getQMMaps();
-#endif
-
- // enable/disable box based on ladder status
- Int index;
- Int selected;
- GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
- index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
- const LadderInfo *li = TheLadderList->findLadderByIndex( index );
- //listboxMapSelect->winEnable( li == nullptr || li->randomMaps == FALSE );
-
- // GO does this differently (and does it above)
-#if !defined(GENERALS_ONLINE)
- Int numPlayers = 0;
- if (li)
- {
- numPlayers = li->playersPerTeam*2;
-
- maps = li->validMaps;
- }
- else
- {
- GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
- if (selected < 0)
- selected = 0;
- numPlayers = (selected+1)*2;
- }
-#endif
-
-
- GadgetListBoxReset(listboxMapSelect);
- for (std::list::const_iterator it = maps.begin(); it != maps.end(); ++it)
- {
- AsciiString theMap = *it;
- const MapMetaData *md = TheMapCache->findMap(theMap);
- if (md && md->m_numPlayers >= numPlayers)
- {
- UnicodeString displayName;
- displayName = md->m_displayName;
- Bool isSelected = pref.isMapSelected(theMap);
- if (li && li->randomMaps)
- isSelected = TRUE;
- Int width = 10;
- Int height = 10;
- const Image *img = (isSelected)?selectedImage:unselectedImage;
- if ( img )
- {
- width = min(GadgetListBoxGetColumnWidth(listboxMapSelect, 0), img->getImageWidth());
- height = width;
- }
- Int index = GadgetListBoxAddEntryImage(listboxMapSelect, img, -1, 0, height, width);
- GadgetListBoxAddEntryText(listboxMapSelect, displayName, GameSpyColor[(isSelected)?GSCOLOR_MAP_SELECTED:GSCOLOR_MAP_UNSELECTED], index, 1);
- GadgetListBoxSetItemData(listboxMapSelect, (void *)isSelected, index);
- GadgetListBoxSetItemData(listboxMapSelect, (void *)md, index, 1);
- }
- }
-}
-
-static void saveQuickMatchOptions()
-{
- if(isInInit)
- return;
- QuickMatchPreferences pref;
-
-#if defined(GENERALS_ONLINE)
- Int numPlayers = 0;
- std::list maps;
- Int selected = -1;
- GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
-
- if (selected != -1)
- {
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- PlaylistEntry plEntry = pMatchmakingInterface->GetCachedPlaylistFromIndex(selected);
- if (plEntry.PlaylistID != -1)
- {
- // take the min player count, we'll offer maps in that range, up to desired player count
- numPlayers = plEntry.MinPlayers;
-
- // maps
- for (PlaylistMapEntry& mapEntry : plEntry.Maps)
- {
- // format into game format (Maps\\name\\name.map)
- std::string correctedMapPath = std::format("maps\\{}\\{}.map", mapEntry.Path, mapEntry.Path);
- maps.push_back(AsciiString(correctedMapPath.c_str()));
- }
-
- // ladder
- pref.setLastLadder(AsciiString::TheEmptyString, 0);
-
- // map
- Int row = 0;
- Int entries = GadgetListBoxGetNumEntries(listboxMapSelect);
- while (row < entries)
- {
- const MapMetaData* md = (const MapMetaData*)GadgetListBoxGetItemData(listboxMapSelect, row, 1);
- if (md)
- pref.setMapSelected(md->m_fileName, (Bool)GadgetListBoxGetItemData(listboxMapSelect, row));
- row++;
- }
- }
- else
- {
- // TODO_QUICKMATCH: Error?
- }
- }
- }
- else
- {
- // TODO_QUICKMATCH: Error?
- }
-#else
- std::list maps = TheGameSpyConfig->getQMMaps();
-
-
- Int index;
- Int selected;
- GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
- index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
- const LadderInfo *li = TheLadderList->findLadderByIndex( index );
- Int numPlayers = 0;
-
- if (li)
- {
- pref.setLastLadder( li->address, li->port );
- numPlayers = li->playersPerTeam*2;
-
- pref.write();
- //return; // don't save our defaults based on the tournament's defaults
- }
- else
- {
- pref.setLastLadder( AsciiString::TheEmptyString, 0 );
- GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
- if (selected < 0)
- selected = 0;
- numPlayers = (selected+1)*2;
- }
-
-
- if (!li || !li->randomMaps) // don't save the map as selected if we couldn't choose
- {
- Int row = 0;
- Int entries = GadgetListBoxGetNumEntries(listboxMapSelect);
- while ( row < entries)
- {
- const MapMetaData *md = (const MapMetaData *)GadgetListBoxGetItemData(listboxMapSelect, row, 1);
- if(md)
- pref.setMapSelected(md->m_fileName, (Bool)GadgetListBoxGetItemData(listboxMapSelect, row));
- row++;
- }
- }
-#endif
-
- UnicodeString u;
- AsciiString a;
-// u = GadgetTextEntryGetText(textEntryMaxDisconnects);
-// a.translate(u);
-// pref.setMaxDisconnects(atoi(a.str()));
-// u = GadgetTextEntryGetText(textEntryMaxPoints);
-// a.translate(u);
-// pref.setMaxPoints(max(100, atoi(a.str())));
-// u = GadgetTextEntryGetText(textEntryMinPoints);
-// a.translate(u);
-// pref.setMinPoints(min(100, atoi(a.str())));
- //u = GadgetTextEntryGetText(textEntryWaitTime);
- //a.translate(u);
- //pref.setWaitTime(atoi(a.str()));
-
- GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
- pref.setNumPlayers(selected);
- GadgetComboBoxGetSelectedPos(comboBoxMaxPing, &selected);
- pref.setMaxPing(selected);
-
- Int item;
- GadgetComboBoxGetSelectedPos(comboBoxSide, &selected);
- item = (Int)GadgetComboBoxGetItemData(comboBoxSide, selected);
- pref.setSide(max(0, item));
- GadgetComboBoxGetSelectedPos(comboBoxColor, &selected);
- pref.setColor(max(0, selected));
-
- GadgetComboBoxGetSelectedPos(comboBoxMaxDisconnects, &selected);
- pref.setMaxDisconnects(selected);
-
-
- pref.write();
-}
-
-//-------------------------------------------------------------------------------------------------
-/** Initialize the WOL Quick Match Menu */
-//-------------------------------------------------------------------------------------------------
-void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
-{
- isInInit = TRUE;
- if (TheGameSpyGame && TheGameSpyGame->isGameInProgress())
- {
- TheGameSpyGame->setGameInProgress(FALSE);
-
- // check if we were disconnected
- Int disconReason;
- if (TheGameSpyInfo->isDisconnectedAfterGameStart(&disconReason))
- {
- AsciiString disconMunkee;
- disconMunkee.format("GUI:GSDisconReason%d", disconReason);
- UnicodeString title, body;
- title = TheGameText->fetch( "GUI:GSErrorTitle" );
- body = TheGameText->fetch( disconMunkee );
- GameSpyCloseAllOverlays();
- GSMessageBoxOk( title, body );
- TheGameSpyInfo->reset();
- DEBUG_LOG(("WOLQuickMatchMenuInit() - game was in progress, and we were disconnected, so pop immediate back to main menu"));
- TheShell->popImmediate();
- return;
- }
- }
-
- nextScreen = nullptr;
- buttonPushed = false;
- isShuttingDown = false;
- raiseMessageBoxes = true;
+
+static const LadderInfo * getLadderInfo();
+
+static Bool isInfoShown()
+{
+ static NameKeyType parentStatsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentStats");
+ GameWindow *parentStats = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentStatsID );
+ if (parentStats)
+ return !parentStats->winIsHidden();
+ return FALSE;
+}
+
+static void hideInfoGadgets(Bool doIt)
+{
+ static NameKeyType parentStatsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentStats");
+ GameWindow *parentStats = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentStatsID );
+ if (parentStats)
+ {
+ parentStats->winHide(doIt);
+ }
+}
+
+static void hideOptionsGadgets(Bool doIt)
+{
+ static NameKeyType parentOptionsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentOptions");
+ GameWindow *parentOptions = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentOptionsID );
+ if (parentOptions)
+ {
+ parentOptions->winHide(doIt);
+ if (comboBoxSide)
+ comboBoxSide->winHide(doIt);
+ if (comboBoxColor)
+ comboBoxColor->winHide(doIt);
+ if (comboBoxNumPlayers)
+ comboBoxNumPlayers->winHide(doIt);
+ if (comboBoxLadder)
+ comboBoxLadder->winHide(doIt);
+ if (comboBoxDisabledLadder)
+ comboBoxDisabledLadder->winHide(doIt);
+ if (comboBoxMaxPing)
+ comboBoxMaxPing->winHide(doIt);
+ if (comboBoxMaxDisconnects)
+ comboBoxMaxDisconnects->winHide(doIt);
+ }
+}
+
+static void enableOptionsGadgets(Bool doIt)
+{
+#ifdef PERF_TEST
+ s_inQM = !doIt;
+#endif // PERF_TEST
+ static NameKeyType parentOptionsID = NAMEKEY("WOLQuickMatchMenu.wnd:ParentOptions");
+ GameWindow *parentOptions = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, parentOptionsID );
+ const LadderInfo *li = getLadderInfo();
+ if (parentOptions)
+ {
+ parentOptions->winEnable(doIt);
+ if (comboBoxSide)
+ comboBoxSide->winEnable(doIt && (!li || !li->randomFactions));
+ if (comboBoxColor)
+ comboBoxColor->winEnable(doIt);
+ if (comboBoxNumPlayers)
+ comboBoxNumPlayers->winEnable(doIt);
+
+#if !defined(GENERALS_ONLINE)
+ if (comboBoxLadder)
+ comboBoxLadder->winEnable(doIt);
+ if (comboBoxDisabledLadder)
+ comboBoxDisabledLadder->winEnable(FALSE);
+ if (comboBoxMaxPing)
+ comboBoxMaxPing->winEnable(doIt);
+ if (comboBoxMaxDisconnects)
+ comboBoxMaxDisconnects->winEnable(doIt);
+#endif
+ }
+}
+
+enum
+{
+ MAX_DISCONNECTS_ANY = 0,
+ MAX_DISCONNECTS_5 = 5,
+ MAX_DISCONNECTS_10 = 10,
+ MAX_DISCONNECTS_25 = 25,
+ MAX_DISCONNECTS_50 = 50,
+};
+enum{ MAX_DISCONNECTS_COUNT = 5 };
+
+static Int MAX_DISCONNECTS[MAX_DISCONNECTS_COUNT] = {MAX_DISCONNECTS_ANY, MAX_DISCONNECTS_5,
+ MAX_DISCONNECTS_10, MAX_DISCONNECTS_25,
+ MAX_DISCONNECTS_50};
+void updateMapHoverPreview(GameWindow* window, WinInstanceData* instData)
+{
+ if (mapListboxPreviewFunc)
+ mapListboxPreviewFunc(window, instData);
+ if (listboxMapSelect == nullptr)
+ return;
+
+ const MouseIO* mouseStatus = TheMouse->getMouseStatus();
+ Int mouseX = mouseStatus->pos.x;
+ Int mouseY = mouseStatus->pos.y;
+ Int listboxX, listboxY, listboxW, listboxH;
+ listboxMapSelect->winGetScreenPosition(&listboxX, &listboxY);
+ listboxMapSelect->winGetSize(&listboxW, &listboxH);
+
+ // mouse is outside the listbox?
+ if (mouseX < listboxX || mouseX > listboxX + listboxW || mouseY < listboxY || mouseY > listboxY + listboxH)
+ return;
+
+ Int hoveredRow, col;
+ GadgetListBoxGetEntryBasedOnXY(listboxMapSelect, mouseX, mouseY, hoveredRow, col);
+ const MapMetaData* mapData = (const MapMetaData*)GadgetListBoxGetItemData(listboxMapSelect, hoveredRow, 1);
+ mapHoverPreview = mapData ? getMapPreviewImage(mapData->m_fileName) : nullptr;
+
+ if (mapHoverPreview == nullptr)
+ return;
+
+ Real wScale = TheDisplay->getWidth() / (Real)DEFAULT_DISPLAY_WIDTH;
+ Real hScale = TheDisplay->getHeight() / (Real)DEFAULT_DISPLAY_HEIGHT;
+ Real scale = (wScale + hScale) * 0.5f;
+ Int previewSize = (Int)(50 * scale);
+ Int offset = (Int)(20 * scale);
+ Int previewX = mouseX + offset;
+ Int previewY = mouseY - (previewSize / 2);
+ TheWindowManager->winDrawImage(mapHoverPreview, previewX, previewY, previewX + previewSize, previewY + previewSize);
+}
+
+void UpdateStartButton()
+{
+ if (!comboBoxLadder || !buttonStart || !listboxMapSelect)
+ return;
+
+ Int index;
+ Int selected;
+ GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
+ index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
+ const LadderInfo *li = TheLadderList->findLadderByIndex( index );
+ if (li)
+ {
+ buttonStart->winEnable(TRUE);
+ return;
+ }
+
+ Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
+ for ( Int i=0; iwinEnable(TRUE);
+ return;
+ }
+ }
+ buttonStart->winEnable(FALSE);
+}
+
+// -----------------------------------------------------------------------------
+
+static void populateQMColorComboBox(QuickMatchPreferences& pref)
+{
+ Int numColors = TheMultiplayerSettings->getNumColors();
+ UnicodeString colorName;
+
+ GadgetComboBoxReset(comboBoxColor);
+
+ MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_RANDOM);
+ Int newIndex = GadgetComboBoxAddEntry(comboBoxColor, TheGameText->fetch("GUI:???"), def->getColor());
+ GadgetComboBoxSetItemData(comboBoxColor, newIndex, (void *)-1);
+
+ for (Int c=0; cgetColor(c);
+ if (!def)
+ continue;
+
+ colorName = TheGameText->fetch(def->getTooltipName().str());
+ newIndex = GadgetComboBoxAddEntry(comboBoxColor, colorName, def->getColor());
+ GadgetComboBoxSetItemData(comboBoxColor, newIndex, (void *)c);
+ }
+ GadgetComboBoxSetSelectedPos(comboBoxColor, pref.getColor());
+}
+
+// -----------------------------------------------------------------------------
+
+static void populateQMSideComboBox(Int favSide, const LadderInfo *li = nullptr)
+{
+ Int numPlayerTemplates = ThePlayerTemplateStore->getPlayerTemplateCount();
+ UnicodeString playerTemplateName;
+
+ GadgetComboBoxReset(comboBoxSide);
+
+ MultiplayerColorDefinition *def = TheMultiplayerSettings->getColor(PLAYERTEMPLATE_RANDOM);
+ Int newIndex = GadgetComboBoxAddEntry(comboBoxSide, TheGameText->fetch("GUI:Random"), def->getColor());
+ GadgetComboBoxSetItemData(comboBoxSide, newIndex, (void *)PLAYERTEMPLATE_RANDOM);
+
+ std::set seenSides;
+
+ Int entryToSelect = 0; // select Random by default
+
+ for (Int c=0; cgetNthPlayerTemplate(c);
+ if (!fac)
+ continue;
+
+ if (fac->getStartingBuilding().isEmpty())
+ continue;
+
+ AsciiString side;
+ side.format("SIDE:%s", fac->getSide().str());
+ if (seenSides.find(side) != seenSides.end())
+ continue;
+
+ if (li)
+ {
+ if (std::find(li->validFactions.begin(), li->validFactions.end(), fac->getSide()) == li->validFactions.end())
+ continue; // ladder doesn't allow it.
+ }
+
+ // Remove disallowed generals from the choice list.
+ // This is also enforced at GUI setup (GUIUtil.cpp and UserPreferences.cpp).
+ // @todo: unlock these when something rad happens
+ Bool disallowLockedGenerals = TRUE;
+ const GeneralPersona *general = TheChallengeGenerals->getGeneralByTemplateName(fac->getName());
+ Bool startsLocked = general ? !general->isStartingEnabled() : FALSE;
+ if (disallowLockedGenerals && startsLocked)
+ continue;
+
+ seenSides.insert(side);
+
+ newIndex = GadgetComboBoxAddEntry(comboBoxSide, TheGameText->fetch(side), def->getColor());
+ GadgetComboBoxSetItemData(comboBoxSide, newIndex, (void *)c);
+
+ if (c == favSide)
+ entryToSelect = newIndex;
+ }
+ seenSides.clear();
+
+ GadgetComboBoxSetSelectedPos(comboBoxSide, entryToSelect);
+ if (li && li->randomFactions)
+ comboBoxSide->winEnable(FALSE);
+ else
+ comboBoxSide->winEnable(TRUE);
+}
+
+void HandleQMLadderSelection(Int ladderID)
+{
+ if (!parentWOLQuickMatch)
+ return;
+
+ QuickMatchPreferences pref;
+
+ if (ladderID < 1)
+ {
+ pref.setLastLadder(AsciiString::TheEmptyString, 0);
+ pref.write();
+ return;
+ }
+
+ const LadderInfo *info = TheLadderList->findLadderByIndex(ladderID);
+ if (!info)
+ {
+ pref.setLastLadder(AsciiString::TheEmptyString, 0);
+ }
+ else
+ {
+ pref.setLastLadder(info->address, info->port);
+ }
+
+ pref.write();
+}
+
+static inline Bool isValidLadder( const LadderInfo *lad )
+{
+ if (lad && lad->index > 0 && lad->validQM)
+ {
+ PSPlayerStats stats = TheGameSpyPSMessageQueue->findPlayerStatsByID(TheGameSpyInfo->getLocalProfileID());
+ Int numWins = 0;
+ PerGeneralMap::iterator it;
+ for (it = stats.wins.begin(); it != stats.wins.end(); ++it)
+ {
+ numWins += it->second;
+ }
+ if (!lad->maxWins || lad->maxWins >=numWins)
+ {
+ if (!lad->minWins || lad->minWins<=numWins)
+ {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+void PopulateQMLadderListBox( GameWindow *win )
+{
+ if (!parentWOLQuickMatch || !comboBoxLadder)
+ return;
+
+ isPopulatingLadderBox = true;
+
+ QuickMatchPreferences pref;
+ AsciiString userPrefFilename;
+
+#if !defined(GENERALS_ONLINE)
+ Int localProfile = TheGameSpyInfo->getLocalProfileID();
+#endif
+
+ Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
+#if !defined(GENERALS_ONLINE)
+ Color specialColor = GameSpyColor[GSCOLOR_MAP_SELECTED];
+ Color favoriteColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
+#endif
+ Int index;
+ GadgetListBoxReset( win );
+
+ std::set usedLadders;
+
+ // start with "No Ladder"
+#if defined(GENERALS_ONLINE)
+ index = GadgetListBoxAddEntryText(win, UnicodeString(L"Automatic Ladder"), normalColor, -1);
+ GadgetListBoxSetItemData(win, 0, index);
+#else
+ index = GadgetListBoxAddEntryText( win, TheGameText->fetch("GUI:NoLadder"), normalColor, -1 );
+ GadgetListBoxSetItemData( win, nullptr, index );
+
+ // add the last ladder
+ Int selectedPos = 0;
+ AsciiString lastLadderAddr = pref.getLastLadderAddr();
+ UnsignedShort lastLadderPort = pref.getLastLadderPort();
+ const LadderInfo *info = TheLadderList->findLadder( lastLadderAddr, lastLadderPort );
+ if (isValidLadder(info))
+ {
+ usedLadders.insert(info);
+ index = GadgetListBoxAddEntryText( win, info->name, favoriteColor, -1 );
+ GadgetListBoxSetItemData( win, (void *)(info->index), index );
+ selectedPos = index;
+ }
+
+ // our recent ladders
+ LadderPreferences ladPref;
+ ladPref.loadProfile( localProfile );
+ const LadderPrefMap recentLadders = ladPref.getRecentLadders();
+ for (LadderPrefMap::const_iterator cit = recentLadders.begin(); cit != recentLadders.end(); ++cit)
+ {
+ AsciiString addr = cit->second.address;
+ UnsignedShort port = cit->second.port;
+ if (addr == lastLadderAddr && port == lastLadderPort)
+ continue;
+ const LadderInfo *info = TheLadderList->findLadder( addr, port );
+ if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
+ {
+ usedLadders.insert(info);
+ index = GadgetListBoxAddEntryText( win, info->name, favoriteColor, -1 );
+ GadgetListBoxSetItemData( win, (void *)(info->index), index );
+ }
+ }
+
+ // special ladders
+ const LadderInfoList *lil = TheLadderList->getSpecialLadders();
+ LadderInfoList::const_iterator lit;
+ for (lit = lil->begin(); lit != lil->end(); ++lit)
+ {
+ const LadderInfo *info = *lit;
+ if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
+ {
+ usedLadders.insert(info);
+ index = GadgetListBoxAddEntryText( win, info->name, specialColor, -1 );
+ GadgetListBoxSetItemData( win, (void *)(info->index), index );
+ }
+ }
+
+ // standard ladders
+ lil = TheLadderList->getStandardLadders();
+ for (lit = lil->begin(); lit != lil->end(); ++lit)
+ {
+ const LadderInfo *info = *lit;
+ if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
+ {
+ usedLadders.insert(info);
+ index = GadgetListBoxAddEntryText( win, info->name, normalColor, -1 );
+ GadgetListBoxSetItemData( win, (void *)(info->index), index );
+ }
+ }
+
+ GadgetListBoxSetSelected( win, selectedPos );
+#endif
+ isPopulatingLadderBox = false;
+}
+
+static const LadderInfo * getLadderInfo()
+{
+ Int index;
+ Int selected;
+ GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
+ index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
+ const LadderInfo *li = TheLadderList->findLadderByIndex( index );
+ return li;
+}
+
+void PopulateQMLadderComboBox()
+{
+ if (!parentWOLQuickMatch || !comboBoxLadder)
+ return;
+
+ isPopulatingLadderBox = true;
+
+ QuickMatchPreferences pref;
+
+#if defined(GENERALS_ONLINE)
+ NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface();
+
+ int64_t localProfile = pAuthInterface != nullptr ? pAuthInterface->GetUserID() : -1;
+#else
+ Int localProfile = TheGameSpyInfo->getLocalProfileID();
+#endif
+
+ Color specialColor = GameSpyColor[GSCOLOR_MAP_SELECTED];
+ Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
+ Int index;
+ GadgetComboBoxReset( comboBoxLadder );
+ index = GadgetComboBoxAddEntry( comboBoxLadder, TheGameText->fetch("GUI:NoLadder"), normalColor );
+ GadgetComboBoxSetItemData( comboBoxLadder, index, nullptr );
+
+ std::set usedLadders;
+
+ Int selectedPos = 0;
+ AsciiString lastLadderAddr = pref.getLastLadderAddr();
+ UnsignedShort lastLadderPort = pref.getLastLadderPort();
+ const LadderInfo *info = TheLadderList->findLadder( lastLadderAddr, lastLadderPort );
+ if (isValidLadder(info))
+ {
+ usedLadders.insert(info);
+ index = GadgetComboBoxAddEntry( comboBoxLadder, info->name, specialColor );
+ GadgetComboBoxSetItemData( comboBoxLadder, index, (void *)(info->index) );
+ selectedPos = index;
+
+ // we selected a ladder? No game size choice for us...
+ GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, info->playersPerTeam-1);
+ comboBoxNumPlayers->winEnable( FALSE );
+ }
+ else
+ {
+ comboBoxNumPlayers->winEnable( TRUE );
+ }
+
+ LadderPreferences ladPref;
+ ladPref.loadProfile( localProfile );
+ const LadderPrefMap recentLadders = ladPref.getRecentLadders();
+ for (LadderPrefMap::const_iterator cit = recentLadders.begin(); cit != recentLadders.end(); ++cit)
+ {
+ AsciiString addr = cit->second.address;
+ UnsignedShort port = cit->second.port;
+ if (addr == lastLadderAddr && port == lastLadderPort)
+ continue;
+ const LadderInfo *info = TheLadderList->findLadder( addr, port );
+ if (isValidLadder(info) && usedLadders.find(info) == usedLadders.end())
+ {
+ usedLadders.insert(info);
+ index = GadgetComboBoxAddEntry( comboBoxLadder, info->name, normalColor );
+ GadgetComboBoxSetItemData( comboBoxLadder, index, (void *)(info->index) );
+ }
+ }
+
+ index = GadgetComboBoxAddEntry( comboBoxLadder, TheGameText->fetch("GUI:ChooseLadder"), normalColor );
+ GadgetComboBoxSetItemData( comboBoxLadder, index, (void *)-1 );
+
+ GadgetComboBoxSetSelectedPos( comboBoxLadder, selectedPos );
+ isPopulatingLadderBox = false;
+
+ populateQMSideComboBox(pref.getSide(), getLadderInfo()); // this will set side to random and disable if necessary
+}
+
+static void populateQuickMatchMapSelectListbox( QuickMatchPreferences& pref )
+{
+ NetworkLog(ELogVerbosity::LOG_DEBUG, "Begin Map Dump");
+ for (auto it = TheMapCache->begin(); it != TheMapCache->end(); ++it)
+ {
+ if (it->second.m_isOfficial && it->second.m_isMultiplayer)
+ {
+ NetworkLog(ELogVerbosity::LOG_DEBUG, "%ls - %s", it->second.m_displayName.str(), it->second.m_fileName.str());
+ }
+ }
+ NetworkLog(ELogVerbosity::LOG_DEBUG, "End Map Dump");
+
+ // TODO_QUICKMATCH
+#if defined(GENERALS_ONLINE)
+ std::list maps;
+ Int numPlayers = 0;
+ Int playlistIndex = -1;
+ GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &playlistIndex);
+
+ if (playlistIndex != -1)
+ {
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ PlaylistEntry plEntry = pMatchmakingInterface->GetCachedPlaylistFromIndex(playlistIndex);
+ if (plEntry.PlaylistID != -1)
+ {
+ // take the min player count, we'll offer maps in that range, up to desired player count
+ numPlayers = plEntry.MinPlayers;
+
+ // maps
+ for (PlaylistMapEntry& mapEntry : plEntry.Maps)
+ {
+ // format into game format (Maps\\name\\name.map) for official EA maps or full path for custom maps
+ std::string correctedMapPath;
+
+ // custom maps need the full path
+ if (mapEntry.Custom)
+ {
+ // TODO_QUICKMATCH: How did the original gamespy service handle this?
+ correctedMapPath = std::format("{}maps\\{}\\{}.map", TheGlobalData->getPath_UserData().str(), mapEntry.Path, mapEntry.Path);
+ }
+ else
+ {
+ correctedMapPath = std::format("maps\\{}\\{}.map", mapEntry.Path, mapEntry.Path);
+ }
+
+ AsciiString mapPath = correctedMapPath.c_str();
+ mapPath.toLower();
+ maps.push_back(mapPath);
+ }
+ }
+ else
+ {
+ // TODO_QUICKMATCH: Error?
+ }
+ }
+ }
+ else
+ {
+ // TODO_QUICKMATCH: Error?
+ }
+
+#else
+ std::list maps = TheGameSpyConfig->getQMMaps();
+#endif
+
+ // enable/disable box based on ladder status
+ Int index;
+ Int selected;
+ GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
+ index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
+ const LadderInfo *li = TheLadderList->findLadderByIndex( index );
+ //listboxMapSelect->winEnable( li == nullptr || li->randomMaps == FALSE );
+
+ // GO does this differently (and does it above)
+#if !defined(GENERALS_ONLINE)
+ Int numPlayers = 0;
+ if (li)
+ {
+ numPlayers = li->playersPerTeam*2;
+
+ maps = li->validMaps;
+ }
+ else
+ {
+ GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
+ if (selected < 0)
+ selected = 0;
+ numPlayers = (selected+1)*2;
+ }
+#endif
+
+
+ GadgetListBoxReset(listboxMapSelect);
+ for (std::list::const_iterator it = maps.begin(); it != maps.end(); ++it)
+ {
+ AsciiString theMap = *it;
+ const MapMetaData *md = TheMapCache->findMap(theMap);
+ if (md && md->m_numPlayers >= numPlayers)
+ {
+ UnicodeString displayName;
+ displayName = md->m_displayName;
+ Bool isSelected = pref.isMapSelected(theMap);
+ if (li && li->randomMaps)
+ isSelected = TRUE;
+ Int width = 10;
+ Int height = 10;
+ const Image *img = (isSelected)?selectedImage:unselectedImage;
+ if ( img )
+ {
+ width = min(GadgetListBoxGetColumnWidth(listboxMapSelect, 0), img->getImageWidth());
+ height = width;
+ }
+ Int index = GadgetListBoxAddEntryImage(listboxMapSelect, img, -1, 0, height, width);
+ GadgetListBoxAddEntryText(listboxMapSelect, displayName, GameSpyColor[(isSelected)?GSCOLOR_MAP_SELECTED:GSCOLOR_MAP_UNSELECTED], index, 1);
+ GadgetListBoxSetItemData(listboxMapSelect, (void *)isSelected, index);
+ GadgetListBoxSetItemData(listboxMapSelect, (void *)md, index, 1);
+ }
+ }
+}
+
+static void saveQuickMatchOptions()
+{
+ if(isInInit)
+ return;
+ QuickMatchPreferences pref;
+
+#if defined(GENERALS_ONLINE)
+ Int numPlayers = 0;
+ std::list maps;
+ Int selected = -1;
+ GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
+
+ if (selected != -1)
+ {
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ PlaylistEntry plEntry = pMatchmakingInterface->GetCachedPlaylistFromIndex(selected);
+ if (plEntry.PlaylistID != -1)
+ {
+ // take the min player count, we'll offer maps in that range, up to desired player count
+ numPlayers = plEntry.MinPlayers;
+
+ // maps
+ for (PlaylistMapEntry& mapEntry : plEntry.Maps)
+ {
+ // format into game format (Maps\\name\\name.map)
+ std::string correctedMapPath = std::format("maps\\{}\\{}.map", mapEntry.Path, mapEntry.Path);
+ maps.push_back(AsciiString(correctedMapPath.c_str()));
+ }
+
+ // ladder
+ pref.setLastLadder(AsciiString::TheEmptyString, 0);
+
+ // map
+ Int row = 0;
+ Int entries = GadgetListBoxGetNumEntries(listboxMapSelect);
+ while (row < entries)
+ {
+ const MapMetaData* md = (const MapMetaData*)GadgetListBoxGetItemData(listboxMapSelect, row, 1);
+ if (md)
+ pref.setMapSelected(md->m_fileName, (Bool)GadgetListBoxGetItemData(listboxMapSelect, row));
+ row++;
+ }
+ }
+ else
+ {
+ // TODO_QUICKMATCH: Error?
+ }
+ }
+ }
+ else
+ {
+ // TODO_QUICKMATCH: Error?
+ }
+#else
+ std::list maps = TheGameSpyConfig->getQMMaps();
+
+
+ Int index;
+ Int selected;
+ GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
+ index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
+ const LadderInfo *li = TheLadderList->findLadderByIndex( index );
+ Int numPlayers = 0;
+
+ if (li)
+ {
+ pref.setLastLadder( li->address, li->port );
+ numPlayers = li->playersPerTeam*2;
+
+ pref.write();
+ //return; // don't save our defaults based on the tournament's defaults
+ }
+ else
+ {
+ pref.setLastLadder( AsciiString::TheEmptyString, 0 );
+ GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
+ if (selected < 0)
+ selected = 0;
+ numPlayers = (selected+1)*2;
+ }
+
+
+ if (!li || !li->randomMaps) // don't save the map as selected if we couldn't choose
+ {
+ Int row = 0;
+ Int entries = GadgetListBoxGetNumEntries(listboxMapSelect);
+ while ( row < entries)
+ {
+ const MapMetaData *md = (const MapMetaData *)GadgetListBoxGetItemData(listboxMapSelect, row, 1);
+ if(md)
+ pref.setMapSelected(md->m_fileName, (Bool)GadgetListBoxGetItemData(listboxMapSelect, row));
+ row++;
+ }
+ }
+#endif
+
+ UnicodeString u;
+ AsciiString a;
+// u = GadgetTextEntryGetText(textEntryMaxDisconnects);
+// a.translate(u);
+// pref.setMaxDisconnects(atoi(a.str()));
+// u = GadgetTextEntryGetText(textEntryMaxPoints);
+// a.translate(u);
+// pref.setMaxPoints(max(100, atoi(a.str())));
+// u = GadgetTextEntryGetText(textEntryMinPoints);
+// a.translate(u);
+// pref.setMinPoints(min(100, atoi(a.str())));
+ //u = GadgetTextEntryGetText(textEntryWaitTime);
+ //a.translate(u);
+ //pref.setWaitTime(atoi(a.str()));
+
+ GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &selected);
+ pref.setNumPlayers(selected);
+ GadgetComboBoxGetSelectedPos(comboBoxMaxPing, &selected);
+ pref.setMaxPing(selected);
+
+ Int item;
+ GadgetComboBoxGetSelectedPos(comboBoxSide, &selected);
+ item = (Int)GadgetComboBoxGetItemData(comboBoxSide, selected);
+ pref.setSide(max(0, item));
+ GadgetComboBoxGetSelectedPos(comboBoxColor, &selected);
+ pref.setColor(max(0, selected));
+
+ GadgetComboBoxGetSelectedPos(comboBoxMaxDisconnects, &selected);
+ pref.setMaxDisconnects(selected);
+
+
+ pref.write();
+}
+
+//-------------------------------------------------------------------------------------------------
+/** Initialize the WOL Quick Match Menu */
+//-------------------------------------------------------------------------------------------------
+void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
+{
+ isInInit = TRUE;
+ if (TheGameSpyGame && TheGameSpyGame->isGameInProgress())
+ {
+ TheGameSpyGame->setGameInProgress(FALSE);
+
+ // check if we were disconnected
+ Int disconReason;
+ if (TheGameSpyInfo->isDisconnectedAfterGameStart(&disconReason))
+ {
+ AsciiString disconMunkee;
+ disconMunkee.format("GUI:GSDisconReason%d", disconReason);
+ UnicodeString title, body;
+ title = TheGameText->fetch( "GUI:GSErrorTitle" );
+ body = TheGameText->fetch( disconMunkee );
+ GameSpyCloseAllOverlays();
+ GSMessageBoxOk( title, body );
+ TheGameSpyInfo->reset();
+ DEBUG_LOG(("WOLQuickMatchMenuInit() - game was in progress, and we were disconnected, so pop immediate back to main menu"));
+ TheShell->popImmediate();
+ return;
+ }
+ }
+
+ nextScreen = nullptr;
+ buttonPushed = false;
+ isShuttingDown = false;
+ raiseMessageBoxes = true;
matchFoundTimeoutStart = 0;
matchFoundTimeoutDurationMs = lobbyTimeoutMs;
matchStartCountdownLastSecond = 0;
-
- delete TheNAT;
- TheNAT = nullptr;
-
- parentWOLQuickMatchID = NAMEKEY( "WOLQuickMatchMenu.wnd:WOLQuickMatchMenuParent" );
- buttonBackID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonBack" );
- buttonBuddiesID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonBuddies" );
- buttonStartID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonStart" );
- buttonStopID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonStop" );
- buttonWidenID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonWiden" );
- listboxQuickMatchID = NAMEKEY( "WOLQuickMatchMenu.wnd:ListboxQuickMatch" );
- listboxMapSelectID = NAMEKEY( "WOLQuickMatchMenu.wnd:ListBoxMapSelect" );
- buttonSelectAllMapsID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonSelectAllMaps" );
- buttonSelectNoMapsID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonSelectNoMaps" );
- //textEntryMaxDisconnectsID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryMaxDisconnects" );
- //textEntryMaxPointsID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryMaxPointPercent" );
- //textEntryMinPointsID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryMinPointPercent" );
- textEntryWaitTimeID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryWaitTime" );
- comboBoxMaxPingID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxMaxPing" );
- comboBoxNumPlayersID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxNumPlayers" );
- comboBoxLadderID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxLadder" );
- comboBoxMaxDisconnectsID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxMaxDisconnects" );
- staticTextNumPlayersID = NAMEKEY( "WOLQuickMatchMenu.wnd:StaticTextNumPlayers" );
- comboBoxSideID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxSide" );
- comboBoxColorID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxColor" );
-
- parentWOLQuickMatch = TheWindowManager->winGetWindowFromId( nullptr, parentWOLQuickMatchID );
- buttonBack = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonBackID);
- buttonStart = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonStartID);
- buttonStop = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonStopID);
- buttonWiden = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonWidenID);
- quickmatchTextWindow = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, listboxQuickMatchID);
- listboxMapSelect = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, listboxMapSelectID);
- if (listboxMapSelect)
- {
- mapListboxPreviewFunc = listboxMapSelect->winGetDrawFunc();
- listboxMapSelect->winSetDrawFunc(updateMapHoverPreview);
- }
- SetListBoxRowAnimMode(quickmatchTextWindow, LIST_ROW_ANIM_SLOT);
- SetListBoxRowAnimMode(listboxMapSelect, LIST_ROW_ANIM_SLOT);
- //textEntryMaxDisconnects = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryMaxDisconnectsID );
- //textEntryMaxPoints = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryMaxPointsID );
- //textEntryMinPoints = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryMinPointsID );
- textEntryWaitTime = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryWaitTimeID );
- comboBoxMaxPing = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxMaxPingID );
- comboBoxNumPlayers = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxNumPlayersID );
- comboBoxLadder = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxLadderID );
- comboBoxMaxDisconnects = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxMaxDisconnectsID );
-
-#if !defined(GENERALS_ONLINE)
- TheGameSpyInfo->registerTextWindow(quickmatchTextWindow);
-#endif
- staticTextNumPlayers = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, staticTextNumPlayersID );
- comboBoxSide = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxSideID );
- comboBoxColor = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxColorID );
-
- if (TheLadderList->getStandardLadders()->empty()
- && TheLadderList->getSpecialLadders()->empty()
- && TheLadderList->getLocalLadders()->empty())
- {
- // no ladders, so just disable them
- comboBoxDisabledLadder = comboBoxLadder;
- comboBoxLadder = nullptr;
-
- isPopulatingLadderBox = TRUE;
-
- Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
- Int index;
- GadgetComboBoxReset( comboBoxDisabledLadder );
-
-#if defined(GENERALS_ONLINE)
- index = GadgetComboBoxAddEntry(comboBoxDisabledLadder, UnicodeString(L"Automatic Ladder"), normalColor);
-#else
- index = GadgetComboBoxAddEntry( comboBoxDisabledLadder, TheGameText->fetch("GUI:NoLadder"), normalColor );
-#endif
- GadgetComboBoxSetItemData( comboBoxDisabledLadder, index, 0 );
- GadgetComboBoxSetSelectedPos( comboBoxDisabledLadder, index );
-
- isPopulatingLadderBox = FALSE;
-
- /** This code would actually *hide* the combo box, but it doesn't look as good. Left here since someone will want to
- ** see it at some point. :P
- if (comboBoxLadder)
- {
- comboBoxLadder->winHide(TRUE);
- comboBoxLadder->winEnable(FALSE);
- }
- comboBoxLadder = nullptr;
- GameWindow *staticTextLadder = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch,
- NAMEKEY("WOLQuickMatchMenu.wnd:StaticTextLadder") );
- if (staticTextLadder)
- staticTextLadder->winHide(TRUE);
- */
- }
-
- GameWindow *buttonBuddies = TheWindowManager->winGetWindowFromId(nullptr, buttonBuddiesID);
- if (buttonBuddies)
- buttonBuddies->winEnable(TRUE);
-
- GameWindow *staticTextTitle = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch,
- NAMEKEY("WOLQuickMatchMenu.wnd:StaticTextTitle") );
- if (staticTextTitle)
- {
- UnicodeString tmp;
-#if !defined(GENERALS_ONLINE)
- tmp.format(TheGameText->fetch("GUI:QuickMatchTitle"), TheGameSpyInfo->getLocalName().str());
-#else
- NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pAuthInterface != nullptr)
- {
- tmp.format(TheGameText->fetch("GUI:QuickMatchTitle"), pAuthInterface->GetDisplayName().c_str());
- }
-#endif
- GadgetStaticTextSetText(staticTextTitle, tmp);
- }
-
- // QM is not going yet, so disable the Widen Search button
- if (buttonWiden) buttonWiden->winEnable( FALSE );
- if (buttonStop) { buttonStop->winHide( TRUE ); buttonStop->winEnable(TRUE); }
- if (buttonStart) buttonStart->winHide( FALSE );
- if (quickmatchTextWindow) GadgetListBoxReset(quickmatchTextWindow);
- enableOptionsGadgets(TRUE);
-
- // Show Menu
- layout->hide( FALSE );
-
- // Set Keyboard to Main Parent
- TheWindowManager->winSetFocus( parentWOLQuickMatch );
-
- // fill in preferences
- selectedImage = TheMappedImageCollection->findImageByName("CustomMatch_selected");
- unselectedImage = TheMappedImageCollection->findImageByName("CustomMatch_deselected");
- QuickMatchPreferences pref;
-
-
- UnicodeString s;
-// s.format(L"%d", pref.getMaxDisconnects());
-// GadgetTextEntrySetText(textEntryMaxDisconnects, s);
-// s.format(L"%d", pref.getMaxPoints());
-// GadgetTextEntrySetText(textEntryMaxPoints, s);
-// s.format(L"%d", pref.getMinPoints());
-// GadgetTextEntrySetText(textEntryMinPoints, s);
- //s.format(L"%d", pref.getWaitTime());
- //GadgetTextEntrySetText(textEntryWaitTime, s);
- maxPoints= pref.getMaxPoints();
- minPoints = pref.getMinPoints();
-
- // NOTE: On GO, this comes from the service
- Int i;
- Color c = GameSpyColor[GSCOLOR_DEFAULT];
-#if !defined(GENERALS_ONLINE)
- GadgetComboBoxReset( comboBoxNumPlayers );
- for (i=1; i<5; ++i)
- {
- s.format(TheGameText->fetch("GUI:PlayersVersusPlayers"), i, i);
- GadgetComboBoxAddEntry( comboBoxNumPlayers, s, c );
- }
- GadgetComboBoxSetSelectedPos( comboBoxNumPlayers, max(0, pref.getNumPlayers()) );
-#endif
-
- GadgetComboBoxReset(comboBoxMaxDisconnects);
- GadgetComboBoxAddEntry( comboBoxMaxDisconnects, TheGameText->fetch("GUI:Any"), c);
- for( i = 1; i < MAX_DISCONNECTS_COUNT; ++i )
- {
- s.format(L"%d", MAX_DISCONNECTS[i]);
- GadgetComboBoxAddEntry( comboBoxMaxDisconnects, s, c );
- }
- Int maxDisconIndex = max(0, pref.getMaxDisconnects());
- GadgetComboBoxSetSelectedPos(comboBoxMaxDisconnects, maxDisconIndex);
-
- GadgetComboBoxReset( comboBoxMaxPing );
-#if defined(GENERALS_ONLINE)
- // not supported in GO
- maxPingEntries = 0;
-#else
- maxPingEntries = (TheGameSpyConfig->getPingTimeoutInMs() - 1) / 100;
-#endif
-
- maxPingEntries++; // need to add the entry for the actual timeout
- for (i=1; i fetch("GUI:TimeInMilliseconds"), i*100);
- GadgetComboBoxAddEntry( comboBoxMaxPing, s, c );
- }
- GadgetComboBoxAddEntry( comboBoxMaxPing, TheGameText->fetch("GUI:ANY"), c );
- i = pref.getMaxPing();
- if( i < 0 )
- i = 0;
- if( i >= maxPingEntries )
- i = maxPingEntries - 1;
- GadgetComboBoxSetSelectedPos( comboBoxMaxPing, i );
-
- populateQMColorComboBox(pref);
- populateQMSideComboBox(pref.getSide(), getLadderInfo());
-
- PopulateQMLadderComboBox();
- TheShell->showShellMap(TRUE);
-
-#if defined(GENERALS_ONLINE)
- if (TheNGMPGame != nullptr)
- TheNGMPGame->reset();
-
- GadgetListBoxReset(listboxMapSelect);
- populateQuickMatchMapSelectListbox(pref);
-
- UpdateLocalPlayerStats();
- UpdateStartButton();
- TheTransitionHandler->setGroup("WOLQuickMatchMenuFade");
- isInInit= FALSE;
-#endif
-
-#if defined(GENERALS_ONLINE)
- buttonStart->winEnable(TRUE);
- buttonStart->winSetText(UnicodeString(L"START MATCHMAKING"));
- buttonStop->winSetText(UnicodeString(L"CANCEL MATCHMAKING"));
- buttonBack->winEnable(TRUE);
-
- // not supported in GO
- if (comboBoxLadder)
- comboBoxLadder->winEnable(FALSE);
- if (comboBoxDisabledLadder)
- comboBoxDisabledLadder->winEnable(FALSE);
- if (comboBoxMaxPing)
- comboBoxMaxPing->winEnable(FALSE);
- if (comboBoxMaxDisconnects)
- comboBoxMaxDisconnects->winEnable(FALSE);
- if (comboBoxSide)
- comboBoxSide->winEnable(FALSE);
- if (comboBoxColor)
- comboBoxColor->winEnable(FALSE);
-
- // welcome msg + instructions
- GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Welcome to QuickMatch. Choose Setup to select playlists and maps."), GameMakeColor(255, 194, 25, 255), -1, -1);
- GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Special thanks to map makers Tanso, ReLaX, cncHD, Specovik, Mp3, Jundiyy & Bamovich for making quickmatch possible."), GameMakeColor(255, 194, 25, 255), -1, -1);
-
-
- // refresh out stats, needed for ELO data
- NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface();
- NGMP_OnlineServices_StatsInterface* pStatsInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pAuthInterface != nullptr && pStatsInterface != nullptr)
- {
- pStatsInterface->findPlayerStatsByID(pAuthInterface->GetUserID(), [=](bool bSuccess, PSPlayerStats stats)
- {
- if (bSuccess)
- {
- UnicodeString eloStr;
- eloStr.format(L"Your current Elo rating is %d (World Series: %d) after %d match(es)", stats.elo_rating, stats.monthly_elo_rating, stats.elo_num_matches);
- GadgetListBoxAddEntryText(quickmatchTextWindow, eloStr, GameMakeColor(255, 194, 25, 255), -1, -1);
- }
- }, EStatsRequestPolicy::BYPASS_CACHE_FORCE_REQUEST);
- }
-
-#endif
-
-#if defined(GENERALS_ONLINE)
- NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
-
- // cannot connect to the lobby we joined
+
+ delete TheNAT;
+ TheNAT = nullptr;
+
+ parentWOLQuickMatchID = NAMEKEY( "WOLQuickMatchMenu.wnd:WOLQuickMatchMenuParent" );
+ buttonBackID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonBack" );
+ buttonBuddiesID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonBuddies" );
+ buttonStartID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonStart" );
+ buttonStopID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonStop" );
+ buttonWidenID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonWiden" );
+ listboxQuickMatchID = NAMEKEY( "WOLQuickMatchMenu.wnd:ListboxQuickMatch" );
+ listboxMapSelectID = NAMEKEY( "WOLQuickMatchMenu.wnd:ListBoxMapSelect" );
+ buttonSelectAllMapsID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonSelectAllMaps" );
+ buttonSelectNoMapsID = NAMEKEY( "WOLQuickMatchMenu.wnd:ButtonSelectNoMaps" );
+ //textEntryMaxDisconnectsID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryMaxDisconnects" );
+ //textEntryMaxPointsID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryMaxPointPercent" );
+ //textEntryMinPointsID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryMinPointPercent" );
+ textEntryWaitTimeID = NAMEKEY( "WOLQuickMatchMenu.wnd:TextEntryWaitTime" );
+ comboBoxMaxPingID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxMaxPing" );
+ comboBoxNumPlayersID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxNumPlayers" );
+ comboBoxLadderID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxLadder" );
+ comboBoxMaxDisconnectsID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxMaxDisconnects" );
+ staticTextNumPlayersID = NAMEKEY( "WOLQuickMatchMenu.wnd:StaticTextNumPlayers" );
+ comboBoxSideID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxSide" );
+ comboBoxColorID = NAMEKEY( "WOLQuickMatchMenu.wnd:ComboBoxColor" );
+
+ parentWOLQuickMatch = TheWindowManager->winGetWindowFromId( nullptr, parentWOLQuickMatchID );
+ buttonBack = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonBackID);
+ buttonStart = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonStartID);
+ buttonStop = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonStopID);
+ buttonWiden = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, buttonWidenID);
+ quickmatchTextWindow = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, listboxQuickMatchID);
+ listboxMapSelect = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, listboxMapSelectID);
+ if (listboxMapSelect)
+ {
+ mapListboxPreviewFunc = listboxMapSelect->winGetDrawFunc();
+ listboxMapSelect->winSetDrawFunc(updateMapHoverPreview);
+ }
+ SetListBoxRowAnimMode(quickmatchTextWindow, LIST_ROW_ANIM_SLOT);
+ SetListBoxRowAnimMode(listboxMapSelect, LIST_ROW_ANIM_SLOT);
+ //textEntryMaxDisconnects = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryMaxDisconnectsID );
+ //textEntryMaxPoints = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryMaxPointsID );
+ //textEntryMinPoints = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryMinPointsID );
+ textEntryWaitTime = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, textEntryWaitTimeID );
+ comboBoxMaxPing = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxMaxPingID );
+ comboBoxNumPlayers = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxNumPlayersID );
+ comboBoxLadder = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxLadderID );
+ comboBoxMaxDisconnects = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxMaxDisconnectsID );
+
+#if !defined(GENERALS_ONLINE)
+ TheGameSpyInfo->registerTextWindow(quickmatchTextWindow);
+#endif
+ staticTextNumPlayers = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, staticTextNumPlayersID );
+ comboBoxSide = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxSideID );
+ comboBoxColor = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxColorID );
+
+ if (TheLadderList->getStandardLadders()->empty()
+ && TheLadderList->getSpecialLadders()->empty()
+ && TheLadderList->getLocalLadders()->empty())
+ {
+ // no ladders, so just disable them
+ comboBoxDisabledLadder = comboBoxLadder;
+ comboBoxLadder = nullptr;
+
+ isPopulatingLadderBox = TRUE;
+
+ Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
+ Int index;
+ GadgetComboBoxReset( comboBoxDisabledLadder );
+
+#if defined(GENERALS_ONLINE)
+ index = GadgetComboBoxAddEntry(comboBoxDisabledLadder, UnicodeString(L"Automatic Ladder"), normalColor);
+#else
+ index = GadgetComboBoxAddEntry( comboBoxDisabledLadder, TheGameText->fetch("GUI:NoLadder"), normalColor );
+#endif
+ GadgetComboBoxSetItemData( comboBoxDisabledLadder, index, 0 );
+ GadgetComboBoxSetSelectedPos( comboBoxDisabledLadder, index );
+
+ isPopulatingLadderBox = FALSE;
+
+ /** This code would actually *hide* the combo box, but it doesn't look as good. Left here since someone will want to
+ ** see it at some point. :P
+ if (comboBoxLadder)
+ {
+ comboBoxLadder->winHide(TRUE);
+ comboBoxLadder->winEnable(FALSE);
+ }
+ comboBoxLadder = nullptr;
+ GameWindow *staticTextLadder = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch,
+ NAMEKEY("WOLQuickMatchMenu.wnd:StaticTextLadder") );
+ if (staticTextLadder)
+ staticTextLadder->winHide(TRUE);
+ */
+ }
+
+ GameWindow *buttonBuddies = TheWindowManager->winGetWindowFromId(nullptr, buttonBuddiesID);
+ if (buttonBuddies)
+ buttonBuddies->winEnable(TRUE);
+
+ GameWindow *staticTextTitle = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch,
+ NAMEKEY("WOLQuickMatchMenu.wnd:StaticTextTitle") );
+ if (staticTextTitle)
+ {
+ UnicodeString tmp;
+#if !defined(GENERALS_ONLINE)
+ tmp.format(TheGameText->fetch("GUI:QuickMatchTitle"), TheGameSpyInfo->getLocalName().str());
+#else
+ NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pAuthInterface != nullptr)
+ {
+ tmp.format(TheGameText->fetch("GUI:QuickMatchTitle"), pAuthInterface->GetDisplayName().c_str());
+ }
+#endif
+ GadgetStaticTextSetText(staticTextTitle, tmp);
+ }
+
+ // QM is not going yet, so disable the Widen Search button
+ if (buttonWiden) buttonWiden->winEnable( FALSE );
+ if (buttonStop) { buttonStop->winHide( TRUE ); buttonStop->winEnable(TRUE); }
+ if (buttonStart) buttonStart->winHide( FALSE );
+ if (quickmatchTextWindow) GadgetListBoxReset(quickmatchTextWindow);
+ enableOptionsGadgets(TRUE);
+
+ // Show Menu
+ layout->hide( FALSE );
+
+ // Set Keyboard to Main Parent
+ TheWindowManager->winSetFocus( parentWOLQuickMatch );
+
+ // fill in preferences
+ selectedImage = TheMappedImageCollection->findImageByName("CustomMatch_selected");
+ unselectedImage = TheMappedImageCollection->findImageByName("CustomMatch_deselected");
+ QuickMatchPreferences pref;
+
+
+ UnicodeString s;
+// s.format(L"%d", pref.getMaxDisconnects());
+// GadgetTextEntrySetText(textEntryMaxDisconnects, s);
+// s.format(L"%d", pref.getMaxPoints());
+// GadgetTextEntrySetText(textEntryMaxPoints, s);
+// s.format(L"%d", pref.getMinPoints());
+// GadgetTextEntrySetText(textEntryMinPoints, s);
+ //s.format(L"%d", pref.getWaitTime());
+ //GadgetTextEntrySetText(textEntryWaitTime, s);
+ maxPoints= pref.getMaxPoints();
+ minPoints = pref.getMinPoints();
+
+ // NOTE: On GO, this comes from the service
+ Int i;
+ Color c = GameSpyColor[GSCOLOR_DEFAULT];
+#if !defined(GENERALS_ONLINE)
+ GadgetComboBoxReset( comboBoxNumPlayers );
+ for (i=1; i<5; ++i)
+ {
+ s.format(TheGameText->fetch("GUI:PlayersVersusPlayers"), i, i);
+ GadgetComboBoxAddEntry( comboBoxNumPlayers, s, c );
+ }
+ GadgetComboBoxSetSelectedPos( comboBoxNumPlayers, max(0, pref.getNumPlayers()) );
+#endif
+
+ GadgetComboBoxReset(comboBoxMaxDisconnects);
+ GadgetComboBoxAddEntry( comboBoxMaxDisconnects, TheGameText->fetch("GUI:Any"), c);
+ for( i = 1; i < MAX_DISCONNECTS_COUNT; ++i )
+ {
+ s.format(L"%d", MAX_DISCONNECTS[i]);
+ GadgetComboBoxAddEntry( comboBoxMaxDisconnects, s, c );
+ }
+ Int maxDisconIndex = max(0, pref.getMaxDisconnects());
+ GadgetComboBoxSetSelectedPos(comboBoxMaxDisconnects, maxDisconIndex);
+
+ GadgetComboBoxReset( comboBoxMaxPing );
+#if defined(GENERALS_ONLINE)
+ // not supported in GO
+ maxPingEntries = 0;
+#else
+ maxPingEntries = (TheGameSpyConfig->getPingTimeoutInMs() - 1) / 100;
+#endif
+
+ maxPingEntries++; // need to add the entry for the actual timeout
+ for (i=1; i fetch("GUI:TimeInMilliseconds"), i*100);
+ GadgetComboBoxAddEntry( comboBoxMaxPing, s, c );
+ }
+ GadgetComboBoxAddEntry( comboBoxMaxPing, TheGameText->fetch("GUI:ANY"), c );
+ i = pref.getMaxPing();
+ if( i < 0 )
+ i = 0;
+ if( i >= maxPingEntries )
+ i = maxPingEntries - 1;
+ GadgetComboBoxSetSelectedPos( comboBoxMaxPing, i );
+
+ populateQMColorComboBox(pref);
+ populateQMSideComboBox(pref.getSide(), getLadderInfo());
+
+ PopulateQMLadderComboBox();
+ TheShell->showShellMap(TRUE);
+
+#if defined(GENERALS_ONLINE)
+ if (TheNGMPGame != nullptr)
+ TheNGMPGame->reset();
+
+ GadgetListBoxReset(listboxMapSelect);
+ populateQuickMatchMapSelectListbox(pref);
+
+ UpdateLocalPlayerStats();
+ UpdateStartButton();
+ TheTransitionHandler->setGroup("WOLQuickMatchMenuFade");
+ isInInit= FALSE;
+#endif
+
+#if defined(GENERALS_ONLINE)
+ buttonStart->winEnable(TRUE);
+ buttonStart->winSetText(UnicodeString(L"START MATCHMAKING"));
+ buttonStop->winSetText(UnicodeString(L"CANCEL MATCHMAKING"));
+ buttonBack->winEnable(TRUE);
+
+ // not supported in GO
+ if (comboBoxLadder)
+ comboBoxLadder->winEnable(FALSE);
+ if (comboBoxDisabledLadder)
+ comboBoxDisabledLadder->winEnable(FALSE);
+ if (comboBoxMaxPing)
+ comboBoxMaxPing->winEnable(FALSE);
+ if (comboBoxMaxDisconnects)
+ comboBoxMaxDisconnects->winEnable(FALSE);
+ if (comboBoxSide)
+ comboBoxSide->winEnable(FALSE);
+ if (comboBoxColor)
+ comboBoxColor->winEnable(FALSE);
+
+ // welcome msg + instructions
+ GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Welcome to QuickMatch. Choose Setup to select playlists and maps."), GameMakeColor(255, 194, 25, 255), -1, -1);
+ GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Special thanks to map makers Tanso, ReLaX, cncHD, Specovik, Mp3, Jundiyy & Bamovich for making quickmatch possible."), GameMakeColor(255, 194, 25, 255), -1, -1);
+
+
+ // refresh out stats, needed for ELO data
+ NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface();
+ NGMP_OnlineServices_StatsInterface* pStatsInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pAuthInterface != nullptr && pStatsInterface != nullptr)
+ {
+ pStatsInterface->findPlayerStatsByID(pAuthInterface->GetUserID(), [=](bool bSuccess, PSPlayerStats stats)
+ {
+ if (bSuccess)
+ {
+ UnicodeString eloStr;
+ eloStr.format(L"Your current Elo rating is %d (World Series: %d) after %d match(es)", stats.elo_rating, stats.monthly_elo_rating, stats.elo_num_matches);
+ GadgetListBoxAddEntryText(quickmatchTextWindow, eloStr, GameMakeColor(255, 194, 25, 255), -1, -1);
+ }
+ }, EStatsRequestPolicy::BYPASS_CACHE_FORCE_REQUEST);
+ }
+
+#endif
+
+#if defined(GENERALS_ONLINE)
+ NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
+
+ // cannot connect to the lobby we joined
if (pLobbyInterface != nullptr)
{
- pLobbyInterface->RegisterForCannotConnectToLobbyCallback([](void)
- {
- // TODO_QUICKMATCH: Show error message + stop matchmaking + enable buttons again
- });
+ pLobbyInterface->RegisterForCannotConnectToLobbyCallback([](void)
+ {
+ // TODO_QUICKMATCH: Show error message + stop matchmaking + enable buttons again
+ });
}
-
- // get playlist list
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- pMatchmakingInterface->RetrievePlaylists([](std::vector vecPlaylists)
- {
- // add playlists
- UnicodeString s;
-
- // clear ui element
- GadgetComboBoxReset(comboBoxNumPlayers);
-
-
- for (PlaylistEntry& playlist : vecPlaylists)
- {
- s.format(L"%hs", playlist.Name.c_str());
- GadgetComboBoxAddEntry(comboBoxNumPlayers, s, GameSpyColor[GSCOLOR_DEFAULT]);
- }
-
- GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, 0);
- });
- }
-#endif
-
-
- if (pLobbyInterface != nullptr)
- {
- // TODO_QUICKMATCH: Deregister when leaving QM
- pLobbyInterface->RegisterForMatchmakingMessageCallback([](std::string strMsg)
- {
- UnicodeString uMsg;
- uMsg.format(L"%hs", strMsg.c_str());
-
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, uMsg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
- });
-
+
+ // get playlist list
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ pMatchmakingInterface->RetrievePlaylists([](std::vector vecPlaylists)
+ {
+ // add playlists
+ UnicodeString s;
+
+ // clear ui element
+ GadgetComboBoxReset(comboBoxNumPlayers);
+
+
+ for (PlaylistEntry& playlist : vecPlaylists)
+ {
+ s.format(L"%hs", playlist.Name.c_str());
+ GadgetComboBoxAddEntry(comboBoxNumPlayers, s, GameSpyColor[GSCOLOR_DEFAULT]);
+ }
+
+ GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, 0);
+ });
+ }
+#endif
+
+
+ if (pLobbyInterface != nullptr)
+ {
+ // TODO_QUICKMATCH: Deregister when leaving QM
+ pLobbyInterface->RegisterForMatchmakingMessageCallback([](std::string strMsg)
+ {
+ UnicodeString uMsg;
+ uMsg.format(L"%hs", strMsg.c_str());
+
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, uMsg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+ });
+
pLobbyInterface->RegisterForMatchmakingMatchFoundCallback([]()
{
buttonBack->winEnable(FALSE);
@@ -1229,11 +1229,11 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
matchFoundTimeoutDurationMs = lobbyTimeoutMs;
matchFoundTimeoutStart = timeGetTime();
matchStartCountdownLastSecond = 0;
- if (TheAudio)
- {
- AudioEventRTS evt("GUICommunicatorOpen");
- TheAudio->addAudioEvent(&evt);
- }
+ if (TheAudio)
+ {
+ AudioEventRTS evt("GUICommunicatorOpen");
+ TheAudio->addAudioEvent(&evt);
+ }
});
pLobbyInterface->RegisterForMatchmakingRequeueCallback([]()
@@ -1246,378 +1246,378 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
buttonWiden->winEnable(TRUE);
});
- pLobbyInterface->RegisterForMatchmakingSetupProgressCallback([](int timeoutMs, int countdownMs)
- {
- matchFoundTimeoutDurationMs = timeoutMs;
- matchFoundTimeoutStart = timeGetTime();
+ pLobbyInterface->RegisterForMatchmakingSetupProgressCallback([](int timeoutMs, int countdownMs)
+ {
+ matchFoundTimeoutDurationMs = timeoutMs;
+ matchFoundTimeoutStart = timeGetTime();
+
+ // Mirror the service-owned countdown for UI feedback. Older services don't send its length, so a short
+ // timeout is taken to mean the countdown.
+ if (countdownMs < 0)
+ {
+ countdownMs = timeoutMs < lobbyTimeoutMs ? defaultMatchStartCountdownMs : 0;
+ }
+
+ matchStartCountdownDurationMs = countdownMs;
+ matchStartCountdownLastSecond = countdownMs > 0 ? (countdownMs + 999) / 1000 : 0;
+ });
+
+ pLobbyInterface->RegisterForMatchmakingStartGameCallback([]()
+ {
+ matchFoundTimeoutStart = 0;
+ matchStartCountdownLastSecond = 0;
+ NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] GOT START GAME EVENT");
+
+ // Check if TheNGMPGame is initialized before dereferencing it
+ if (!TheNGMPGame)
+ {
+ NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] NO NGMP GAME INSTANCE");
+ return;
+ }
+
+ // mark everyone as having the map, we dont allow user provided custom maps or map transfers in QM
+ // TODO_QUICKMATCH: Do this automatically for game type quickmatch, or better yet, do it on the service
+ for (int i = 0; i < MAX_SLOTS; i++)
+ {
+ GameSlot* slot = TheNGMPGame->getSlot(i);
+ if (slot != nullptr)
+ {
+ slot->setMapAvailability(TRUE);
+ }
+ }
+
+ // start
+ NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
+ NGMPGame* myGame = pLobbyInterface == nullptr ? nullptr : pLobbyInterface->GetCurrentGame();
+
+ if (pLobbyInterface == nullptr || !myGame || !myGame->isInGame())
+ {
+ NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] Checks failed, %d, %d, %d", pLobbyInterface == nullptr, !myGame, !myGame->isInGame());
+ return;
+ }
+
+
+ // TODO_NGMP
+ //SendStatsToOtherPlayers(TheNGMPGame);
+
+ GameWindow* buttonBuddy = TheWindowManager->winGetWindowFromId(NULL, NAMEKEY("GameSpyGameOptionsMenu.wnd:ButtonCommunicator"));
+ if (buttonBuddy)
+ buttonBuddy->winEnable(FALSE);
+ GameSpyCloseOverlay(GSOVERLAY_BUDDY);
+ GameSpyCloseOverlay(GSOVERLAY_PLAYERINFO);
+
+ *TheNGMPGame = *myGame;
+ TheNGMPGame->startGame(0);
+ });
+
+ pLobbyInterface->RegisterForJoinLobbyCallback([](EJoinLobbyResult result)
+ {
+ NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
+
+ if (!pLobbyInterface->IsInLobby())
+ {
+ return;
+ }
+
+ if (TheNGMPGame == nullptr)
+ {
+ TheNGMPGame = new NGMPGame();
+ TheNGMPGame->markGameAsQM();
+ }
+ pLobbyInterface->UpdateRoomDataCache([](bool bSuccess)
+ {
+
+ });
- // Mirror the service-owned countdown for UI feedback. Older services don't send its length, so a short
- // timeout is taken to mean the countdown.
- if (countdownMs < 0)
+ // connection events (for debug really)
+ NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh();
+ if (pMesh != nullptr)
{
- countdownMs = timeoutMs < lobbyTimeoutMs ? defaultMatchStartCountdownMs : 0;
+ pMesh->RegisterForConnectionEvents([](int64_t userID, std::wstring strDisplayName, PlayerConnection* connection)
+ {
+#if _DEBUG // not enabled in quickmatch because then people see their opponent during matchmaking
+ std::string strState = "Unknown";
+
+ EConnectionState connState = connection->GetState();
+
+ switch (connState)
+ {
+ case EConnectionState::NOT_CONNECTED:
+ strState = "Not Connected";
+ break;
+
+ case EConnectionState::CONNECTING_DIRECT:
+ strState = "Connecting";
+ break;
+ case EConnectionState::FINDING_ROUTE:
+ strState = "Connecting (Finding Route)";
+ break;
+
+ case EConnectionState::CONNECTED_DIRECT:
+ strState = "Connected";
+ break;
+
+ case EConnectionState::CONNECTION_FAILED:
+ strState = "Connection Failed";
+ break;
+
+ case EConnectionState::CONNECTION_DISCONNECTED:
+ strState = "Disconnected (Was Connected Previously)";
+ break;
+
+ default:
+ strState = "Unknown";
+ break;
+ }
+
+ UnicodeString strConnectionMessage;
+ if (connState == EConnectionState::CONNECTING_DIRECT || connState == EConnectionState::FINDING_ROUTE)
+ {
+ strConnectionMessage.format(L"Connecting to %s", strDisplayName.c_str());
+
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+ }
+ else if (connState == EConnectionState::CONNECTED_DIRECT)
+ {
+ strConnectionMessage.format(L"Connected to %s", strDisplayName.c_str());
+
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+ }
+ else
+ {
+ if (connState == EConnectionState::CONNECTION_FAILED || connState == EConnectionState::CONNECTION_DISCONNECTED)
+ {
+ strConnectionMessage.format(L"Connection failed to %s", strDisplayName.c_str());
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+ }
+ }
+#endif
+ });
}
- matchStartCountdownDurationMs = countdownMs;
- matchStartCountdownLastSecond = countdownMs > 0 ? (countdownMs + 999) / 1000 : 0;
});
+ }
- pLobbyInterface->RegisterForMatchmakingStartGameCallback([]()
- {
- matchFoundTimeoutStart = 0;
- matchStartCountdownLastSecond = 0;
- NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] GOT START GAME EVENT");
-
- // Check if TheNGMPGame is initialized before dereferencing it
- if (!TheNGMPGame)
- {
- NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] NO NGMP GAME INSTANCE");
- return;
- }
-
- // mark everyone as having the map, we dont allow user provided custom maps or map transfers in QM
- // TODO_QUICKMATCH: Do this automatically for game type quickmatch, or better yet, do it on the service
- for (int i = 0; i < MAX_SLOTS; i++)
- {
- GameSlot* slot = TheNGMPGame->getSlot(i);
- if (slot != nullptr)
- {
- slot->setMapAvailability(TRUE);
- }
- }
-
- // start
- NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
- NGMPGame* myGame = pLobbyInterface == nullptr ? nullptr : pLobbyInterface->GetCurrentGame();
-
- if (pLobbyInterface == nullptr || !myGame || !myGame->isInGame())
- {
- NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] Checks failed, %d, %d, %d", pLobbyInterface == nullptr, !myGame, !myGame->isInGame());
- return;
- }
-
-
- // TODO_NGMP
- //SendStatsToOtherPlayers(TheNGMPGame);
-
- GameWindow* buttonBuddy = TheWindowManager->winGetWindowFromId(NULL, NAMEKEY("GameSpyGameOptionsMenu.wnd:ButtonCommunicator"));
- if (buttonBuddy)
- buttonBuddy->winEnable(FALSE);
- GameSpyCloseOverlay(GSOVERLAY_BUDDY);
- GameSpyCloseOverlay(GSOVERLAY_PLAYERINFO);
-
- *TheNGMPGame = *myGame;
- TheNGMPGame->startGame(0);
- });
-
- pLobbyInterface->RegisterForJoinLobbyCallback([](EJoinLobbyResult result)
- {
- NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
-
- if (!pLobbyInterface->IsInLobby())
- {
- return;
- }
-
- if (TheNGMPGame == nullptr)
- {
- TheNGMPGame = new NGMPGame();
- TheNGMPGame->markGameAsQM();
- }
- pLobbyInterface->UpdateRoomDataCache([](bool bSuccess)
- {
-
- });
-
- // connection events (for debug really)
- NetworkMesh* pMesh = NGMP_OnlineServicesManager::GetNetworkMesh();
- if (pMesh != nullptr)
- {
- pMesh->RegisterForConnectionEvents([](int64_t userID, std::wstring strDisplayName, PlayerConnection* connection)
- {
-#if _DEBUG // not enabled in quickmatch because then people see their opponent during matchmaking
- std::string strState = "Unknown";
-
- EConnectionState connState = connection->GetState();
-
- switch (connState)
- {
- case EConnectionState::NOT_CONNECTED:
- strState = "Not Connected";
- break;
-
- case EConnectionState::CONNECTING_DIRECT:
- strState = "Connecting";
- break;
- case EConnectionState::FINDING_ROUTE:
- strState = "Connecting (Finding Route)";
- break;
-
- case EConnectionState::CONNECTED_DIRECT:
- strState = "Connected";
- break;
-
- case EConnectionState::CONNECTION_FAILED:
- strState = "Connection Failed";
- break;
-
- case EConnectionState::CONNECTION_DISCONNECTED:
- strState = "Disconnected (Was Connected Previously)";
- break;
-
- default:
- strState = "Unknown";
- break;
- }
-
- UnicodeString strConnectionMessage;
- if (connState == EConnectionState::CONNECTING_DIRECT || connState == EConnectionState::FINDING_ROUTE)
- {
- strConnectionMessage.format(L"Connecting to %s", strDisplayName.c_str());
-
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
- }
- else if (connState == EConnectionState::CONNECTED_DIRECT)
- {
- strConnectionMessage.format(L"Connected to %s", strDisplayName.c_str());
-
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
- }
- else
- {
- if (connState == EConnectionState::CONNECTION_FAILED || connState == EConnectionState::CONNECTION_DISCONNECTED)
- {
- strConnectionMessage.format(L"Connection failed to %s", strDisplayName.c_str());
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, strConnectionMessage, GameMakeColor(255, 194, 15, 255), -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
- }
- }
-#endif
- });
- }
-
- });
- }
-
-#if defined(GENERALS_ONLINE)
- // Update the communicator button anytime we get notifications
- NGMP_OnlineServices_SocialInterface* pSocialInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pSocialInterface != nullptr)
- {
- // notifiactions callback
- pSocialInterface->RegisterForCallback_OnNumberGlobalNotificationsChanged([=](int numNotifications)
- {
- // update communicator button
- if (buttonBuddies != nullptr)
- {
- UnicodeString buttonText;
- if (numNotifications > 0)
- {
- buttonText.format(L"%s [%d]", TheGameText->fetch("GUI:Buddies").str(), numNotifications);
- }
- else
- {
- buttonText.format(L"%s", TheGameText->fetch("GUI:Buddies").str());
- }
- buttonBuddies->winSetText(buttonText);
-
-
- }
- });
- }
-
- // And also initialize it
+#if defined(GENERALS_ONLINE)
+ // Update the communicator button anytime we get notifications
+ NGMP_OnlineServices_SocialInterface* pSocialInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pSocialInterface != nullptr)
+ {
+ // notifiactions callback
+ pSocialInterface->RegisterForCallback_OnNumberGlobalNotificationsChanged([=](int numNotifications)
+ {
+ // update communicator button
+ if (buttonBuddies != nullptr)
+ {
+ UnicodeString buttonText;
+ if (numNotifications > 0)
+ {
+ buttonText.format(L"%s [%d]", TheGameText->fetch("GUI:Buddies").str(), numNotifications);
+ }
+ else
+ {
+ buttonText.format(L"%s", TheGameText->fetch("GUI:Buddies").str());
+ }
+ buttonBuddies->winSetText(buttonText);
+
+
+ }
+ });
+ }
+
+ // And also initialize it
if (buttonBuddies != nullptr && pSocialInterface != nullptr && pSocialInterface->GetNumTotalNotifications() > 0)
- {
- UnicodeString buttonText;
- buttonText.format(L"%s [%d]", TheGameText->fetch("GUI:Buddies").str(), pSocialInterface->GetNumTotalNotifications());
- buttonBuddies->winSetText(buttonText);
- }
-#endif
-}
-
-//-------------------------------------------------------------------------------------------------
-/** This is called when a shutdown is complete for this menu */
-//-------------------------------------------------------------------------------------------------
-static void shutdownComplete( WindowLayout *layout )
-{
-
- isShuttingDown = false;
-
- // hide the layout
- layout->hide( TRUE );
-
- // our shutdown is complete
- TheShell->shutdownComplete( layout, (nextScreen != nullptr) );
-
- if (nextScreen != nullptr)
- {
- TheShell->push(nextScreen);
- }
-
- nextScreen = nullptr;
-
-}
-
-//-------------------------------------------------------------------------------------------------
-/** WOL Quick Match Menu shutdown method */
-//-------------------------------------------------------------------------------------------------
-void WOLQuickMatchMenuShutdown( WindowLayout *layout, void *userData )
-{
-#if !defined(GENERALS_ONLINE)
- TheGameSpyInfo->unregisterTextWindow(quickmatchTextWindow);
-#endif
-
-#if defined(GENERALS_ONLINE)
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- // Shutdown is invokved for UI exit but also for going to game... so don't tear down lobby on service in the latter case
- if (TheNGMPGame == nullptr || !TheNGMPGame->isGameInProgress())
- {
- pMatchmakingInterface->CancelMatchmaking();
- }
- }
-
- NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pLobbyInterface != nullptr)
- {
+ {
+ UnicodeString buttonText;
+ buttonText.format(L"%s [%d]", TheGameText->fetch("GUI:Buddies").str(), pSocialInterface->GetNumTotalNotifications());
+ buttonBuddies->winSetText(buttonText);
+ }
+#endif
+}
+
+//-------------------------------------------------------------------------------------------------
+/** This is called when a shutdown is complete for this menu */
+//-------------------------------------------------------------------------------------------------
+static void shutdownComplete( WindowLayout *layout )
+{
+
+ isShuttingDown = false;
+
+ // hide the layout
+ layout->hide( TRUE );
+
+ // our shutdown is complete
+ TheShell->shutdownComplete( layout, (nextScreen != nullptr) );
+
+ if (nextScreen != nullptr)
+ {
+ TheShell->push(nextScreen);
+ }
+
+ nextScreen = nullptr;
+
+}
+
+//-------------------------------------------------------------------------------------------------
+/** WOL Quick Match Menu shutdown method */
+//-------------------------------------------------------------------------------------------------
+void WOLQuickMatchMenuShutdown( WindowLayout *layout, void *userData )
+{
+#if !defined(GENERALS_ONLINE)
+ TheGameSpyInfo->unregisterTextWindow(quickmatchTextWindow);
+#endif
+
+#if defined(GENERALS_ONLINE)
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ // Shutdown is invokved for UI exit but also for going to game... so don't tear down lobby on service in the latter case
+ if (TheNGMPGame == nullptr || !TheNGMPGame->isGameInProgress())
+ {
+ pMatchmakingInterface->CancelMatchmaking();
+ }
+ }
+
+ NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pLobbyInterface != nullptr)
+ {
pLobbyInterface->DeregisterForMatchmakingMessageCallback();
pLobbyInterface->DeRegisterForMatchmakingMatchFoundCallback();
pLobbyInterface->DeregisterForMatchmakingRequeueCallback();
pLobbyInterface->DeregisterForMatchmakingSetupProgressCallback();
pLobbyInterface->DeregisterForMatchmakingStartGameCallback();
-
- pLobbyInterface->DeregisterForJoinLobbyCallback();
- pLobbyInterface->DeregisterForCannotConnectToLobbyCallback();
- }
-#endif
-
- if (!TheGameEngine->getQuitting())
- saveQuickMatchOptions();
-
+
+ pLobbyInterface->DeregisterForJoinLobbyCallback();
+ pLobbyInterface->DeregisterForCannotConnectToLobbyCallback();
+ }
+#endif
+
+ if (!TheGameEngine->getQuitting())
+ saveQuickMatchOptions();
+
if (listboxMapSelect && mapListboxPreviewFunc)
listboxMapSelect->winSetDrawFunc(mapListboxPreviewFunc);
mapListboxPreviewFunc = nullptr;
mapHoverPreview = nullptr;
- listboxMapSelect = nullptr;
-
- parentWOLQuickMatch = nullptr;
- buttonBack = nullptr;
- quickmatchTextWindow = nullptr;
- selectedImage = unselectedImage = nullptr;
+ listboxMapSelect = nullptr;
+
+ parentWOLQuickMatch = nullptr;
+ buttonBack = nullptr;
+ quickmatchTextWindow = nullptr;
+ selectedImage = unselectedImage = nullptr;
matchFoundTimeoutStart = 0;
matchStartCountdownLastSecond = 0;
-
- isShuttingDown = true;
-
- // if we are shutting down for an immediate pop, skip the animations
- Bool popImmediate = *(Bool *)userData;
- if( popImmediate )
- {
-
- shutdownComplete( layout );
- return;
-
- }
-
- TheShell->reverseAnimatewindow();
- TheTransitionHandler->reverse("WOLQuickMatchMenuFade");
-
- RaiseGSMessageBox();
-}
-
-
-#ifdef PERF_TEST
-static const char* getMessageString(Int t)
-{
- switch(t)
- {
- case PeerResponse::PEERRESPONSE_LOGIN:
- return "login";
- case PeerResponse::PEERRESPONSE_DISCONNECT:
- return "disconnect";
- case PeerResponse::PEERRESPONSE_MESSAGE:
- return "message";
- case PeerResponse::PEERRESPONSE_GROUPROOM:
- return "group room";
- case PeerResponse::PEERRESPONSE_STAGINGROOM:
- return "staging room";
- case PeerResponse::PEERRESPONSE_STAGINGROOMPLAYERINFO:
- return "staging room player info";
- case PeerResponse::PEERRESPONSE_JOINGROUPROOM:
- return "group room join";
- case PeerResponse::PEERRESPONSE_CREATESTAGINGROOM:
- return "staging room create";
- case PeerResponse::PEERRESPONSE_JOINSTAGINGROOM:
- return "staging room join";
- case PeerResponse::PEERRESPONSE_PLAYERJOIN:
- return "player join";
- case PeerResponse::PEERRESPONSE_PLAYERLEFT:
- return "player part";
- case PeerResponse::PEERRESPONSE_PLAYERCHANGEDNICK:
- return "player nick";
- case PeerResponse::PEERRESPONSE_PLAYERINFO:
- return "player info";
- case PeerResponse::PEERRESPONSE_PLAYERCHANGEDFLAGS:
- return "player flags";
- case PeerResponse::PEERRESPONSE_ROOMUTM:
- return "room UTM";
- case PeerResponse::PEERRESPONSE_PLAYERUTM:
- return "player UTM";
- case PeerResponse::PEERRESPONSE_QUICKMATCHSTATUS:
- return "QM status";
- case PeerResponse::PEERRESPONSE_GAMESTART:
- return "game start";
- case PeerResponse::PEERRESPONSE_FAILEDTOHOST:
- return "host failure";
- }
- return "unknown";
-}
-#endif // PERF_TEST
-
-//-------------------------------------------------------------------------------------------------
-/** WOL Quick Match Menu update method */
-//-------------------------------------------------------------------------------------------------
-void WOLQuickMatchMenuUpdate( WindowLayout * layout, void *userData)
-{
- // need to exit?
- if (NGMP_OnlineServicesManager::GetInstance() != nullptr && NGMP_OnlineServicesManager::GetInstance()->IsPendingFullTeardown())
- {
- // Only if not in game and not in anim
- if ((TheNGMPGame == nullptr || !TheNGMPGame->isGameInProgress()) && TheShell->isAnimFinished() && TheTransitionHandler->isFinished())
- {
- bool bForceShutdown = true;
- WOLQuickMatchMenuShutdown(layout, (void*)&bForceShutdown); // userdata is 'force shutdown'
- TearDownGeneralsOnline();
-
- TheShell->pop();
- return;
- }
- }
-
- if (TheGameLogic->isInShellGame() && TheGameLogic->getFrame() == 1)
- {
- SignalUIInteraction(SHELL_SCRIPT_HOOK_GENERALS_ONLINE_ENTERED_FROM_GAME);
- }
-
- // We'll only be successful if we've requested to
- if(isShuttingDown && TheShell->isAnimFinished()&& TheTransitionHandler->isFinished())
- shutdownComplete(layout);
-
- if (raiseMessageBoxes)
- {
- RaiseGSMessageBox();
- raiseMessageBoxes = false;
- }
-
-#if defined(GENERALS_ONLINE) // GO needs to tick this, so notifications disappear etc
- HandleBuddyResponses();
-#endif
-
+
+ isShuttingDown = true;
+
+ // if we are shutting down for an immediate pop, skip the animations
+ Bool popImmediate = *(Bool *)userData;
+ if( popImmediate )
+ {
+
+ shutdownComplete( layout );
+ return;
+
+ }
+
+ TheShell->reverseAnimatewindow();
+ TheTransitionHandler->reverse("WOLQuickMatchMenuFade");
+
+ RaiseGSMessageBox();
+}
+
+
+#ifdef PERF_TEST
+static const char* getMessageString(Int t)
+{
+ switch(t)
+ {
+ case PeerResponse::PEERRESPONSE_LOGIN:
+ return "login";
+ case PeerResponse::PEERRESPONSE_DISCONNECT:
+ return "disconnect";
+ case PeerResponse::PEERRESPONSE_MESSAGE:
+ return "message";
+ case PeerResponse::PEERRESPONSE_GROUPROOM:
+ return "group room";
+ case PeerResponse::PEERRESPONSE_STAGINGROOM:
+ return "staging room";
+ case PeerResponse::PEERRESPONSE_STAGINGROOMPLAYERINFO:
+ return "staging room player info";
+ case PeerResponse::PEERRESPONSE_JOINGROUPROOM:
+ return "group room join";
+ case PeerResponse::PEERRESPONSE_CREATESTAGINGROOM:
+ return "staging room create";
+ case PeerResponse::PEERRESPONSE_JOINSTAGINGROOM:
+ return "staging room join";
+ case PeerResponse::PEERRESPONSE_PLAYERJOIN:
+ return "player join";
+ case PeerResponse::PEERRESPONSE_PLAYERLEFT:
+ return "player part";
+ case PeerResponse::PEERRESPONSE_PLAYERCHANGEDNICK:
+ return "player nick";
+ case PeerResponse::PEERRESPONSE_PLAYERINFO:
+ return "player info";
+ case PeerResponse::PEERRESPONSE_PLAYERCHANGEDFLAGS:
+ return "player flags";
+ case PeerResponse::PEERRESPONSE_ROOMUTM:
+ return "room UTM";
+ case PeerResponse::PEERRESPONSE_PLAYERUTM:
+ return "player UTM";
+ case PeerResponse::PEERRESPONSE_QUICKMATCHSTATUS:
+ return "QM status";
+ case PeerResponse::PEERRESPONSE_GAMESTART:
+ return "game start";
+ case PeerResponse::PEERRESPONSE_FAILEDTOHOST:
+ return "host failure";
+ }
+ return "unknown";
+}
+#endif // PERF_TEST
+
+//-------------------------------------------------------------------------------------------------
+/** WOL Quick Match Menu update method */
+//-------------------------------------------------------------------------------------------------
+void WOLQuickMatchMenuUpdate( WindowLayout * layout, void *userData)
+{
+ // need to exit?
+ if (NGMP_OnlineServicesManager::GetInstance() != nullptr && NGMP_OnlineServicesManager::GetInstance()->IsPendingFullTeardown())
+ {
+ // Only if not in game and not in anim
+ if ((TheNGMPGame == nullptr || !TheNGMPGame->isGameInProgress()) && TheShell->isAnimFinished() && TheTransitionHandler->isFinished())
+ {
+ bool bForceShutdown = true;
+ WOLQuickMatchMenuShutdown(layout, (void*)&bForceShutdown); // userdata is 'force shutdown'
+ TearDownGeneralsOnline();
+
+ TheShell->pop();
+ return;
+ }
+ }
+
+ if (TheGameLogic->isInShellGame() && TheGameLogic->getFrame() == 1)
+ {
+ SignalUIInteraction(SHELL_SCRIPT_HOOK_GENERALS_ONLINE_ENTERED_FROM_GAME);
+ }
+
+ // We'll only be successful if we've requested to
+ if(isShuttingDown && TheShell->isAnimFinished()&& TheTransitionHandler->isFinished())
+ shutdownComplete(layout);
+
+ if (raiseMessageBoxes)
+ {
+ RaiseGSMessageBox();
+ raiseMessageBoxes = false;
+ }
+
+#if defined(GENERALS_ONLINE) // GO needs to tick this, so notifications disappear etc
+ HandleBuddyResponses();
+#endif
+
if (matchStartCountdownLastSecond > 0)
{
Int elapsedMs = timeGetTime() - matchFoundTimeoutStart;
@@ -1645,1007 +1645,1007 @@ void WOLQuickMatchMenuUpdate( WindowLayout * layout, void *userData)
// Leave time for the server's START_GAME event to arrive.
Int effectiveTimeoutMs = matchFoundTimeoutDurationMs < lobbyTimeoutMs ? lobbyTimeoutMs : matchFoundTimeoutDurationMs;
if (matchFoundTimeoutStart != 0 && timeGetTime() - matchFoundTimeoutStart >= effectiveTimeoutMs)
- {
- matchFoundTimeoutStart = 0;
- buttonBack->winEnable(TRUE);
- buttonStop->winEnable(TRUE);
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Match setup timed out. You may cancel or continue waiting."), GameMakeColor(255, 194, 25, 255), -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
- }
-
- /// @todo: MDC handle disconnects in-game the same way as Custom Match!
-
- if (TheShell->isAnimFinished() && !buttonPushed && TheGameSpyPeerMessageQueue && TheGameSpyInfo)
- {
- HandleBuddyResponses();
- HandlePersistentStorageResponses();
-
- if (TheGameSpyGame && TheGameSpyGame->isGameInProgress())
- {
- if (TheGameSpyInfo->isDisconnectedAfterGameStart(nullptr))
- {
- return; // already been disconnected, so don't worry.
- }
-
- Int allowedMessages = TheGameSpyInfo->getMaxMessagesPerUpdate();
- Bool sawImportantMessage = FALSE;
- PeerResponse resp;
- while (allowedMessages-- && !sawImportantMessage && TheGameSpyPeerMessageQueue->getResponse( resp ))
- {
- switch (resp.peerResponseType)
- {
- case PeerResponse::PEERRESPONSE_DISCONNECT:
- {
- sawImportantMessage = TRUE;
- AsciiString disconMunkee;
- disconMunkee.format("GUI:GSDisconReason%d", resp.discon.reason);
-
- // check for scorescreen
- NameKeyType listboxChatWindowScoreScreenID = NAMEKEY("ScoreScreen.wnd:ListboxChatWindowScoreScreen");
- GameWindow *listboxChatWindowScoreScreen = TheWindowManager->winGetWindowFromId( nullptr, listboxChatWindowScoreScreenID );
- if (listboxChatWindowScoreScreen)
- {
- GadgetListBoxAddEntryText(listboxChatWindowScoreScreen, TheGameText->fetch(disconMunkee),
- GameSpyColor[GSCOLOR_DEFAULT], -1);
- }
- else
- {
- // still ingame
- TheInGameUI->message(disconMunkee);
- }
- TheGameSpyInfo->markAsDisconnectedAfterGameStart(resp.discon.reason);
- }
- }
- }
-
- return; // if we're in game, all we care about is if we've been disconnected from the chat server
- }
-
- if (TheNAT != nullptr) {
- NATStateType NATState = TheNAT->update();
- if (NATState == NATSTATE_DONE)
- {
- TheGameSpyGame->launchGame();
- if (TheGameSpyInfo) // this can be blown away by a disconnect on the map transfer screen
- TheGameSpyInfo->leaveStagingRoom();
- return; // don't do any more processing this frame, in case the screen goes away
- }
- else if (NATState == NATSTATE_FAILED)
- {
- // delete TheNAT, its no good for us anymore.
- delete TheNAT;
- TheNAT = nullptr;
-
- // Just back out. This cleans up some slot list problems
- buttonPushed = true;
- GSMessageBoxOk(TheGameText->fetch("GUI:Error"), TheGameText->fetch("GUI:NATNegotiationFailed"));
- nextScreen = "Menus/WOLWelcomeMenu.wnd";
- TheShell->pop();
- return; // don't do any more processing this frame, in case the screen goes away
- }
- }
-
-#ifdef PERF_TEST
- UnsignedInt start = timeGetTime();
- UnsignedInt end = timeGetTime();
- std::list responses;
- Int numMessages = 0;
-#endif // PERF_TEST
-
- Int allowedMessages = TheGameSpyInfo->getMaxMessagesPerUpdate();
- Bool sawImportantMessage = FALSE;
- PeerResponse resp;
- while (allowedMessages-- && !sawImportantMessage && TheGameSpyPeerMessageQueue->getResponse( resp ))
- {
-#ifdef PERF_TEST
- ++numMessages;
- responses.push_back(resp.peerResponseType);
-#endif // PERF_TEST
- switch (resp.peerResponseType)
- {
- case PeerResponse::PEERRESPONSE_PLAYERUTM:
- {
- if (stricmp(resp.command.c_str(), "STATS") == 0)
- {
- DEBUG_LOG(("Saw STATS from %s, data was '%s'", resp.nick.c_str(), resp.commandOptions.c_str()));
- AsciiString data = resp.commandOptions.c_str();
- AsciiString idStr;
- data.nextToken(&idStr, " ");
- Int id = atoi(idStr.str());
- DEBUG_LOG(("data: %d(%s) - '%s'", id, idStr.str(), data.str()));
-
- PSPlayerStats stats = TheGameSpyPSMessageQueue->parsePlayerKVPairs(data.str());
- PSPlayerStats oldStats = TheGameSpyPSMessageQueue->findPlayerStatsByID(id);
- stats.id = id;
- DEBUG_LOG(("Parsed ID is %d, old ID is %d", stats.id, oldStats.id));
- if (stats.id && (oldStats.id == 0))
- TheGameSpyPSMessageQueue->trackPlayerStats(stats);
-
- // now fill in the profileID in the game slot
- AsciiString nick = resp.nick.c_str();
- for (Int i=0; igetGameSpySlot(i);
- if (slot && slot->isHuman() && (slot->getLoginName().compareNoCase(nick) == 0))
- {
- slot->setProfileID(id);
- break;
- }
- }
- }
- Int slotNum = TheGameSpyGame->getSlotNum(resp.nick.c_str());
- if ((slotNum >= 0) && (slotNum < MAX_SLOTS) && (stricmp(resp.command.c_str(), "NAT") == 0)) {
- // this is a command for NAT negotiations, pass if off to TheNAT
- sawImportantMessage = TRUE;
- if (TheNAT != nullptr) {
- TheNAT->processGlobalMessage(slotNum, resp.commandOptions.c_str());
- }
- }
- /*
- else if (key == "NAT")
- {
- if ((val >= FirewallHelperClass::FIREWALL_TYPE_SIMPLE) &&
- (val <= FirewallHelperClass::FIREWALL_TYPE_DESTINATION_PORT_DELTA))
- {
- slot->setNATBehavior((FirewallHelperClass::FirewallBehaviorType)val);
- DEBUG_LOG(("Setting NAT behavior to %d for player %d", val, slotNum));
- change = true;
- }
- else
- {
- DEBUG_LOG(("Rejecting invalid NAT behavior %d from player %d", val, slotNum));
- }
- }
- */
- }
- break;
-
- case PeerResponse::PEERRESPONSE_DISCONNECT:
- {
- sawImportantMessage = TRUE;
- UnicodeString title, body;
- AsciiString disconMunkee;
- disconMunkee.format("GUI:GSDisconReason%d", resp.discon.reason);
- title = TheGameText->fetch( "GUI:GSErrorTitle" );
- body = TheGameText->fetch( disconMunkee );
- GameSpyCloseAllOverlays();
- GSMessageBoxOk( title, body );
- TheGameSpyInfo->reset();
- TheShell->pop();
- }
- break; // LORENZEN ADDED. SORRY IF THIS "BREAKS" IT...
-
-
- case PeerResponse::PEERRESPONSE_JOINGROUPROOM:
- /*
- if (resp.joinGroupRoom.ok)
- {
- TheGameSpyInfo->addText(L"Joined group room", GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- else
- {
- TheGameSpyInfo->addText(L"Didn't join group room", GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- */
- break;
- case PeerResponse::PEERRESPONSE_PLAYERJOIN:
- {
- //UnicodeString str;
- //str.format(L"Player %hs joined the room", resp.nick.c_str());
- //TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- break;
- case PeerResponse::PEERRESPONSE_PLAYERLEFT:
- {
- //UnicodeString str;
- //str.format(L"Player %hs left the room", resp.nick.c_str());
- //TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- break;
- case PeerResponse::PEERRESPONSE_MESSAGE:
- {
- //UnicodeString m;
- //m.format(L"[%hs]: %ls", resp.nick.c_str(), resp.text.c_str());
- //TheGameSpyInfo->addText(m, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- break;
-
-
-// LORENZEN EXPRESSES DOUBT ABOUT THIS ONE, AS IT MAY HAVE SUFFERED MERGE MANGLING... SORRY
- // I THINK THIS IS THE OBSOLETE VERSION... SEE THE NEWER LOOKING ONE ABOVE
-/*
- case PeerResponse::PEERRESPONSE_DISCONNECT:
- {
- UnicodeString title, body;
- AsciiString disconMunkee;
- disconMunkee.format("GUI:GSDisconReason%d", resp.discon.reason);
- title = TheGameText->fetch( "GUI:GSErrorTitle" );
- body = TheGameText->fetch( disconMunkee );
- GameSpyCloseAllOverlays();
- GSMessageBoxOk( title, body );
- TheGameSpyInfo->reset();
- TheShell->pop();
- }
-*/
-
-
-
- case PeerResponse::PEERRESPONSE_CREATESTAGINGROOM:
- {
- if (resp.createStagingRoom.result == PEERJoinSuccess)
- {
- // Woohoo! On to our next screen!
- UnicodeString str;
- str.format(L"Created staging room");
- TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- else
- {
- UnicodeString s;
- s.format(L"createStagingRoom result: %d", resp.createStagingRoom.result);
- TheGameSpyInfo->addText( s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow );
- }
- }
- break;
- case PeerResponse::PEERRESPONSE_JOINSTAGINGROOM:
- {
- if (resp.joinStagingRoom.ok == PEERTrue)
- {
- // Woohoo! On to our next screen!
- UnicodeString s;
- s.format(L"joinStagingRoom result: %d", resp.joinStagingRoom.ok);
- TheGameSpyInfo->addText( s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow );
- }
- else
- {
- UnicodeString s;
- s.format(L"joinStagingRoom result: %d", resp.joinStagingRoom.ok);
- TheGameSpyInfo->addText( s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow );
- }
- }
- break;
- case PeerResponse::PEERRESPONSE_STAGINGROOM:
- {
- UnicodeString str;
- str.format(L"Staging room list callback", resp.nick.c_str());
- TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- break;
- case PeerResponse::PEERRESPONSE_QUICKMATCHSTATUS:
- {
- sawImportantMessage = TRUE;
- switch( resp.qmStatus.status )
- {
- case QM_IDLE:
- //TheGameSpyInfo->addText(L"Status: QM_IDLE", GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- break;
- case QM_JOININGQMCHANNEL:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:JOININGQMCHANNEL"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- break;
- case QM_LOOKINGFORBOT:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:LOOKINGFORBOT"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- break;
- case QM_SENTINFO:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:SENTINFO"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- break;
- case QM_WORKING:
- {
- UnicodeString s;
- s.format(TheGameText->fetch("QM:WORKING"), resp.qmStatus.poolSize);
- TheGameSpyInfo->addText(s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- buttonWiden->winEnable( TRUE );
- break;
- case QM_POOLSIZE:
- {
- UnicodeString s;
- s.format(TheGameText->fetch("QM:POOLSIZE"), resp.qmStatus.poolSize);
- TheGameSpyInfo->addText(s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- }
- break;
- case QM_WIDENINGSEARCH:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:WIDENINGSEARCH"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- buttonWiden->winEnable( FALSE );
- break;
- case QM_MATCHED:
- {
- TheGameSpyInfo->addText(TheGameText->fetch("QM:MATCHED"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- buttonWiden->winEnable( FALSE );
-
- TheGameSpyGame->enterGame();
- TheGameSpyGame->setSeed(resp.qmStatus.seed);
-
- TheGameSpyGame->markGameAsQM();
-
- const LadderInfo *info = getLadderInfo();
- if (!info)
- {
- TheGameSpyGame->setLadderIP("localhost");
- TheGameSpyGame->setLadderPort(0);
- }
- else
- {
- TheGameSpyGame->setLadderIP(info->address);
- TheGameSpyGame->setLadderPort(info->port);
- }
-
- Int i;
- Int numPlayers = 0;
- for (i=0; i maps = TheGameSpyConfig->getQMMaps();
- for (std::list::const_iterator it = maps.begin(); it != maps.end(); ++it)
- {
- AsciiString theMap = *it;
- theMap.toLower();
- const MapMetaData *md = TheMapCache->findMap(theMap);
- if (md && md->m_numPlayers >= numPlayers)
- {
- TheGameSpyGame->setMap(*it);
- if (resp.qmStatus.mapIdx-- == 0)
- break;
- }
- }
-
- Int numPlayersPerTeam = numPlayers/2;
- DEBUG_ASSERTCRASH(numPlayersPerTeam, ("0 players per team???"));
- if (!numPlayersPerTeam)
- numPlayersPerTeam = 1;
-
- for (i=0; igetGameSpySlot(i);
- if (resp.stagingRoomPlayerNames[i].empty())
- {
- slot->setState(SLOT_CLOSED);
- }
- else
- {
- AsciiString aName = resp.stagingRoomPlayerNames[i].c_str();
- UnicodeString uName;
- uName.translate(aName);
- slot->setState(SLOT_PLAYER, uName, resp.qmStatus.IP[i]);
- slot->setColor(resp.qmStatus.color[i]);
- slot->setPlayerTemplate(resp.qmStatus.side[i]);
- //slot->setProfileID(0);
- slot->setNATBehavior((FirewallHelperClass::FirewallBehaviorType)resp.qmStatus.nat[i]);
- slot->setLocale("");
- slot->setTeamNumber( i/numPlayersPerTeam );
- if (i==0)
- TheGameSpyGame->setGameName(uName);
- }
- }
-
- DEBUG_LOG(("Starting a QM game: options=[%s]", GameInfoToAsciiString(TheGameSpyGame).str()));
- SendStatsToOtherPlayers(TheGameSpyGame);
- TheGameSpyGame->startGame(0);
- GameWindow *buttonBuddies = TheWindowManager->winGetWindowFromId(nullptr, buttonBuddiesID);
- if (buttonBuddies)
- buttonBuddies->winEnable(FALSE);
- GameSpyCloseOverlay(GSOVERLAY_BUDDY);
- }
- break;
- case QM_INCHANNEL:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:INCHANNEL"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- break;
- case QM_NEGOTIATINGFIREWALLS:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:NEGOTIATINGFIREWALLS"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- break;
- case QM_STARTINGGAME:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:STARTINGGAME"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- break;
- case QM_COULDNOTFINDBOT:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:COULDNOTFINDBOT"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- buttonWiden->winEnable( FALSE );
- buttonStart->winHide( FALSE );
- buttonStop->winHide( TRUE );
- enableOptionsGadgets(TRUE);
- break;
- case QM_COULDNOTFINDCHANNEL:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:COULDNOTFINDCHANNEL"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- buttonWiden->winEnable( FALSE );
- buttonStart->winHide( FALSE );
- buttonStop->winHide( TRUE );
- enableOptionsGadgets(TRUE);
- break;
- case QM_COULDNOTNEGOTIATEFIREWALLS:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:COULDNOTNEGOTIATEFIREWALLS"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- buttonWiden->winEnable( FALSE );
- buttonStart->winHide( FALSE );
- buttonStop->winHide( TRUE );
- enableOptionsGadgets(TRUE);
- break;
- case QM_STOPPED:
- TheGameSpyInfo->addText(TheGameText->fetch("QM:STOPPED"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- buttonWiden->winEnable( FALSE );
- buttonStart->winHide( FALSE );
- buttonStop->winHide( TRUE );
- enableOptionsGadgets(TRUE);
- break;
- }
- }
- break;
- }
- }
-#ifdef PERF_TEST
- // check performance
- end = timeGetTime();
- UnsignedInt frameTime = end-start;
- if (frameTime > 100 || responses.size() > 20)
- {
- UnicodeString munkee;
- munkee.format(L"inQM:%d %d ms, %d messages", s_inQM, frameTime, responses.size());
- TheGameSpyInfo->addText(munkee, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
- PERF_LOG(("%ls", munkee.str()));
-
- std::list::const_iterator it;
- for (it = responses.begin(); it != responses.end(); ++it)
- {
- PERF_LOG((" %s", getMessageString(*it)));
- }
- }
-#endif // PERF_TEST
- }
-}
-
-//-------------------------------------------------------------------------------------------------
-/** WOL Quick Match Menu input callback */
-//-------------------------------------------------------------------------------------------------
-WindowMsgHandledType WOLQuickMatchMenuInput( GameWindow *window, UnsignedInt msg,
- WindowMsgData mData1, WindowMsgData mData2 )
-{
- switch( msg )
- {
-
- // --------------------------------------------------------------------------------------------
- case GWM_CHAR:
- {
- UnsignedByte key = mData1;
- UnsignedByte state = mData2;
- if (buttonPushed)
- break;
-
- switch( key )
- {
-
- // ----------------------------------------------------------------------------------------
- case KEY_ESC:
- {
-
- //
- // send a simulated selected event to the parent window of the
- // back/exit button
- //
- if( BitIsSet( state, KEY_STATE_UP ) )
- {
- // ESC must not do what the disabled back button can't, e.g. leave during match setup
- if(!buttonBack->winIsHidden() && buttonBack->winGetEnabled())
- TheWindowManager->winSendSystemMsg( window, GBM_SELECTED,
- (WindowMsgData)buttonBack, buttonBackID );
-
- }
-
- // don't let key fall through anywhere else
- return MSG_HANDLED;
-
- }
-
- }
-
- }
-
- }
-
- return MSG_IGNORED;
-}
-
-//-------------------------------------------------------------------------------------------------
-/** WOL Quick Match Menu window system callback */
-//-------------------------------------------------------------------------------------------------
-WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt msg,
- WindowMsgData mData1, WindowMsgData mData2 )
-{
- UnicodeString txtInput;
-
- switch( msg )
- {
-
-
- case GWM_CREATE:
- {
-
- break;
- }
-
- case GWM_DESTROY:
- {
- break;
- }
-
- case GWM_INPUT_FOCUS:
- {
- // if we're givin the opportunity to take the keyboard focus we must say we want it
- if( mData1 == TRUE )
- *(Bool *)mData2 = TRUE;
-
- return MSG_HANDLED;
- }
-
- case GCM_SELECTED:
- {
- if (buttonPushed)
- break;
- GameWindow *control = (GameWindow *)mData1;
- Int controlID = control->winGetWindowId();
- Int pos = -1;
- GadgetComboBoxGetSelectedPos(control, &pos);
-
- saveQuickMatchOptions();
- if (controlID == comboBoxLadderID && !isPopulatingLadderBox)
- {
- if (pos >= 0)
- {
- QuickMatchPreferences pref;
- Int ladderID = (Int)GadgetComboBoxGetItemData(control, pos);
- if (ladderID == 0)
- {
- // no ladder selected - enable buttons
- GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, max(0, pref.getNumPlayers()/2-1));
- comboBoxNumPlayers->winEnable( TRUE );
- populateQMSideComboBox(pref.getSide()); // this will set side to random and disable if necessary
- }
- else if (ladderID > 0)
- {
- // ladder selected - disable buttons
- const LadderInfo *li = TheLadderList->findLadderByIndex(ladderID);
- if (li)
- GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, li->playersPerTeam-1);
- else
- GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, 0);
- comboBoxNumPlayers->winEnable( FALSE );
-
- populateQMSideComboBox(pref.getSide(), li); // this will set side to random and disable if necessary
- }
- else
- {
- // "Choose a ladder" selected - open overlay
- PopulateQMLadderComboBox(); // this restores the non-"Choose a ladder" selection
- GameSpyOpenOverlay( GSOVERLAY_LADDERSELECT );
- }
- }
- }
- if (!isInInit)
- {
- QuickMatchPreferences pref;
- populateQuickMatchMapSelectListbox(pref);
- UpdateStartButton();
- }
- break;
- }
-
- case GBM_SELECTED:
- {
- if (buttonPushed)
- break;
- GameWindow *control = (GameWindow *)mData1;
- Int controlID = control->winGetWindowId();
- static NameKeyType buttonOptionsID = NAMEKEY("WOLQuickMatchMenu.wnd:ButtonOptions");
-
- if ( controlID == buttonStopID )
- {
-#if defined(GENERALS_ONLINE)
- buttonWiden->winEnable(FALSE);
- buttonStart->winEnable(TRUE);
- buttonStart->winHide(FALSE);
- buttonStop->winHide(TRUE);
- enableOptionsGadgets(TRUE);
-
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, TheGameText->fetch("GUI:QMAborted"), GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
-
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- pMatchmakingInterface->CancelMatchmaking();
- }
-#else
- PeerRequest req;
- req.peerRequestType = PeerRequest::PEERREQUEST_STOPQUICKMATCH;
- TheGameSpyPeerMessageQueue->addRequest(req);
- buttonWiden->winEnable( FALSE );
- buttonStart->winHide( FALSE );
- buttonStop->winHide( TRUE );
- enableOptionsGadgets(TRUE);
- TheGameSpyInfo->addText(TheGameText->fetch("GUI:QMAborted"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
-#endif
- }
- else if ( controlID == buttonOptionsID )
- {
- GameWindow *win =TheWindowManager->winGetWindowFromId(parentWOLQuickMatch,buttonOptionsID);
- if (isInfoShown())
- {
- hideInfoGadgets(TRUE);
- hideOptionsGadgets(FALSE);
- GadgetButtonSetText(win, TheGameText->fetch("GUI:PlayerInfo"));
- }
- else
- {
- hideInfoGadgets(FALSE);
- hideOptionsGadgets(TRUE);
- GadgetButtonSetText(win, TheGameText->fetch("GUI:Setup"));
- }
- }
- else if ( controlID == buttonWidenID )
- {
-#if defined(GENERALS_ONLINE)
- GadgetListBoxAddEntryText(quickmatchTextWindow, TheGameText->fetch("QM:WIDENINGSEARCH"), GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
-
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- pMatchmakingInterface->WidenSearch();
-
- // disable widen button
- buttonWiden->winEnable(FALSE);
- }
-#else
- PeerRequest req;
- req.peerRequestType = PeerRequest::PEERREQUEST_WIDENQUICKMATCHSEARCH;
- TheGameSpyPeerMessageQueue->addRequest(req);
- buttonWiden->winEnable( FALSE );
-#endif
- }
- else if ( controlID == buttonStartID )
- {
-#if defined(GENERALS_ONLINE)
-
- std::vector vecSelectedMapIndexes;
- uint16_t playlistID = 0;
- int minSelectedMaps = 0;
-
- // get maps and playlist ID
- std::list maps;
- Int playlistIndex = -1;
- GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &playlistIndex);
-
- if (playlistIndex != -1)
- {
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- PlaylistEntry plEntry = pMatchmakingInterface->GetCachedPlaylistFromIndex(playlistIndex);
- if (plEntry.PlaylistID != -1)
- {
- playlistID = plEntry.PlaylistID;
- minSelectedMaps = plEntry.MinSelectedMaps;
-
- // maps
- Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
- for (Int i = 0; i < numMaps; ++i)
- {
- bool bMapSelected = GadgetListBoxGetItemData(listboxMapSelect, i, 0);
- if (bMapSelected)
- {
- vecSelectedMapIndexes.push_back(i);
- }
- }
- }
- else
- {
- // TODO_QUICKMATCH: Error?
- }
- }
- }
- else
- {
- // TODO_QUICKMATCH: Error?
- }
-
- if (static_cast(vecSelectedMapIndexes.size()) < minSelectedMaps)
- {
- UnicodeString msg;
- msg.format(L"You must select at least %d maps.", minSelectedMaps);
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, msg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
-
- // buttons
- buttonWiden->winEnable(FALSE);
- buttonStart->winHide(FALSE);
- buttonStart->winEnable(TRUE);
- buttonStop->winHide(TRUE);
-
- break;
- }
-
- // buttons
- buttonWiden->winEnable(FALSE);
- buttonStart->winEnable(FALSE);
- buttonStop->winHide(TRUE);
-
- NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
- if (pMatchmakingInterface != nullptr)
- {
- pMatchmakingInterface->StartMatchmaking(playlistID, vecSelectedMapIndexes, [](bool bSuccess)
- {
- // TODO_QUICKMATCH: Chat has a sound effect in TheGameSpyInfo, re-eanble it
- if (bSuccess)
- {
-
- // buttons
- buttonWiden->winEnable(TRUE);
- buttonStart->winHide(TRUE);
- buttonStop->winHide(FALSE);
- }
- else
- {
- Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Failed to start matchmaking."), GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
- GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
-
- // buttons
- buttonWiden->winEnable(FALSE);
- buttonStart->winHide(FALSE);
- buttonStart->winEnable(TRUE);
- buttonStop->winHide(TRUE);
- }
- });
- }
-#else
- PeerRequest req;
- req.peerRequestType = PeerRequest::PEERREQUEST_STARTQUICKMATCH;
- req.qmMaps.clear();
- Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
- for ( Int i=0; i= maxPingEntries - 1)
- {
- req.QM.maxPing = TheGameSpyConfig->getPingTimeoutInMs();
- }
- else
- req.QM.maxPing = (val+1)*100;
-
- PSPlayerStats stats = TheGameSpyPSMessageQueue->findPlayerStatsByID(TheGameSpyInfo->getLocalProfileID());
- req.QM.points = CalculateRank(stats);
-
- Int ladderIndex, index, selected;
- GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
- ladderIndex = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
- const LadderInfo *ladderInfo = nullptr;
- if (ladderIndex < 0)
- {
- ladderIndex = 0;
- }
- if (ladderIndex)
- {
- ladderInfo = TheLadderList->findLadderByIndex( ladderIndex );
- if (!ladderInfo)
- {
- ladderIndex = 0; // sanity
- }
- }
- req.QM.ladderID = ladderIndex;
-
- req.QM.ladderPassCRC = 0;
-
- index = -1;
- GadgetComboBoxGetSelectedPos( comboBoxSide, &selected );
- if (selected >= 0)
- index = (Int)GadgetComboBoxGetItemData( comboBoxSide, selected );
- req.QM.side = index;
- if (ladderInfo && ladderInfo->randomFactions)
- {
- Int sideNum = GameClientRandomValue(0, ladderInfo->validFactions.size()-1);
- DEBUG_LOG(("Looking for %d out of %d random sides", sideNum, ladderInfo->validFactions.size()));
- AsciiStringListConstIterator cit = ladderInfo->validFactions.begin();
- while (sideNum)
- {
- ++cit;
- --sideNum;
- }
- if (cit != ladderInfo->validFactions.end())
- {
- Int numPlayerTemplates = ThePlayerTemplateStore->getPlayerTemplateCount();
- AsciiString sideStr = *cit;
- DEBUG_LOG(("Chose %s as our side... finding", sideStr.str()));
- for (Int c=0; cgetNthPlayerTemplate(c);
- if (fac && fac->getSide() == sideStr)
- {
- DEBUG_LOG(("Found %s in index %d", sideStr.str(), c));
- req.QM.side = c;
- break;
- }
- }
- }
- }
- else if( index == PLAYERTEMPLATE_RANDOM )
- {
- // If not a forced random ladder, then we need to resolve our pick of random right now anyway, or else
- // we will get the same pick every darn time.
- Int randomTries = 0;// Rare to hit Random 10 times in a row, but if it does then random will be converted to a set side by the very bug this tries to fix, so no harm done.
-
- while( randomTries < 10 && index == PLAYERTEMPLATE_RANDOM )
- {
- Int numberComboBoxEntries = GadgetComboBoxGetLength(comboBoxSide);
- Int randomPick = GameClientRandomValue(0, numberComboBoxEntries - 1);
- index = (Int)GadgetComboBoxGetItemData( comboBoxSide, randomPick );
- req.QM.side = index;
-
- randomTries++;
- }
- }
-
- index = -1;
- GadgetComboBoxGetSelectedPos( comboBoxColor, &selected );
- if (selected >= 0)
- index = (Int)GadgetComboBoxGetItemData( comboBoxColor, selected );
- req.QM.color = index;
-
- OptionPreferences natPref;
- req.QM.NAT = natPref.getFirewallBehavior();
-
- if (ladderIndex)
- {
- req.QM.numPlayers = (ladderInfo)?ladderInfo->playersPerTeam*2 : 2;
- }
- else
- {
- GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &val);
- if (val < 0)
- val = 0;
- req.QM.numPlayers = (val+1)*2;
- }
-
- Int numDiscons = 0;
- PerGeneralMap::iterator it;
- for(it =stats.discons.begin(); it != stats.discons.end(); ++it)
- {
- numDiscons += it->second;
- }
- for(it =stats.desyncs.begin(); it != stats.desyncs.end(); ++it)
- {
- numDiscons += it->second;
- }
- req.QM.discons = numDiscons;
-
-
- strlcpy(req.QM.pings, TheGameSpyInfo->getPingString().str(), sizeof(req.QM.pings));
-
- req.QM.botID = TheGameSpyConfig->getQMBotID();
- req.QM.roomID = TheGameSpyConfig->getQMChannel();
-
- req.QM.exeCRC = TheGlobalData->m_exeCRC;
- req.QM.iniCRC = TheGlobalData->m_iniCRC;
-
- TheGameSpyPeerMessageQueue->addRequest(req);
- buttonWiden->winEnable( FALSE );
- buttonStart->winHide( TRUE );
- buttonStop->winHide( FALSE );
- enableOptionsGadgets(FALSE);
-
- if (ladderIndex > 0)
- {
- // save the ladder as being played upon even if we cancel out of matching early...
- LadderPreferences ladPref;
- ladPref.loadProfile( TheGameSpyInfo->getLocalProfileID() );
- LadderPref p;
- p.lastPlayDate = time(nullptr);
- p.address = ladderInfo->address;
- p.port = ladderInfo->port;
- p.name = ladderInfo->name;
- ladPref.addRecentLadder( p );
- ladPref.write();
- }
-#endif
- }
- else if ( controlID == buttonBuddiesID )
- {
- GameSpyToggleOverlay( GSOVERLAY_BUDDY );
- }
- else if ( controlID == buttonBackID )
- {
-#if !defined(GENERALS_ONLINE)
- TheGameSpyInfo->leaveGroupRoom();
-#endif
- buttonPushed = true;
- nextScreen = "Menus/WOLWelcomeMenu.wnd";
- TheShell->pop();
- }
- else if ( controlID == buttonSelectAllMapsID )
- {
- Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
- for ( Int i=0; iwinGetWindowId();
- Int selected = (Int)mData2;
-
- if ( controlID == listboxMapSelectID )
- {
- const LadderInfo *li = getLadderInfo();
- if (selected >= 0 && (!li || !li->randomMaps))
- {
- Bool wasSelected = (Bool)GadgetListBoxGetItemData(control, selected, 0);
- GadgetListBoxSetItemData(control, (void *)(!wasSelected), selected, 0);
- Int width = 10;
- Int height = 10;
- const Image *img = (!wasSelected)?selectedImage:unselectedImage;
- if ( img )
- {
- width = min(GadgetListBoxGetColumnWidth(control, 0), img->getImageWidth());
- height = width;
- }
- GadgetListBoxAddEntryImage(control, img, selected, 0, height, width);
- GadgetListBoxAddEntryText(control, GadgetListBoxGetText(control, selected, 1), GameSpyColor[(wasSelected)?GSCOLOR_MAP_UNSELECTED:GSCOLOR_MAP_SELECTED], selected, 1);
- }
- if (selected >= 0)
- GadgetListBoxSetSelected(control, -1);
- }
- UpdateStartButton();
- break;
- }
-
- case GEM_EDIT_DONE:
- {
- break;
- }
- default:
- return MSG_IGNORED;
-
- }
-
- return MSG_HANDLED;
-}
-
-
+ {
+ matchFoundTimeoutStart = 0;
+ buttonBack->winEnable(TRUE);
+ buttonStop->winEnable(TRUE);
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Match setup timed out. You may cancel or continue waiting."), GameMakeColor(255, 194, 25, 255), -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+ }
+
+ /// @todo: MDC handle disconnects in-game the same way as Custom Match!
+
+ if (TheShell->isAnimFinished() && !buttonPushed && TheGameSpyPeerMessageQueue && TheGameSpyInfo)
+ {
+ HandleBuddyResponses();
+ HandlePersistentStorageResponses();
+
+ if (TheGameSpyGame && TheGameSpyGame->isGameInProgress())
+ {
+ if (TheGameSpyInfo->isDisconnectedAfterGameStart(nullptr))
+ {
+ return; // already been disconnected, so don't worry.
+ }
+
+ Int allowedMessages = TheGameSpyInfo->getMaxMessagesPerUpdate();
+ Bool sawImportantMessage = FALSE;
+ PeerResponse resp;
+ while (allowedMessages-- && !sawImportantMessage && TheGameSpyPeerMessageQueue->getResponse( resp ))
+ {
+ switch (resp.peerResponseType)
+ {
+ case PeerResponse::PEERRESPONSE_DISCONNECT:
+ {
+ sawImportantMessage = TRUE;
+ AsciiString disconMunkee;
+ disconMunkee.format("GUI:GSDisconReason%d", resp.discon.reason);
+
+ // check for scorescreen
+ NameKeyType listboxChatWindowScoreScreenID = NAMEKEY("ScoreScreen.wnd:ListboxChatWindowScoreScreen");
+ GameWindow *listboxChatWindowScoreScreen = TheWindowManager->winGetWindowFromId( nullptr, listboxChatWindowScoreScreenID );
+ if (listboxChatWindowScoreScreen)
+ {
+ GadgetListBoxAddEntryText(listboxChatWindowScoreScreen, TheGameText->fetch(disconMunkee),
+ GameSpyColor[GSCOLOR_DEFAULT], -1);
+ }
+ else
+ {
+ // still ingame
+ TheInGameUI->message(disconMunkee);
+ }
+ TheGameSpyInfo->markAsDisconnectedAfterGameStart(resp.discon.reason);
+ }
+ }
+ }
+
+ return; // if we're in game, all we care about is if we've been disconnected from the chat server
+ }
+
+ if (TheNAT != nullptr) {
+ NATStateType NATState = TheNAT->update();
+ if (NATState == NATSTATE_DONE)
+ {
+ TheGameSpyGame->launchGame();
+ if (TheGameSpyInfo) // this can be blown away by a disconnect on the map transfer screen
+ TheGameSpyInfo->leaveStagingRoom();
+ return; // don't do any more processing this frame, in case the screen goes away
+ }
+ else if (NATState == NATSTATE_FAILED)
+ {
+ // delete TheNAT, its no good for us anymore.
+ delete TheNAT;
+ TheNAT = nullptr;
+
+ // Just back out. This cleans up some slot list problems
+ buttonPushed = true;
+ GSMessageBoxOk(TheGameText->fetch("GUI:Error"), TheGameText->fetch("GUI:NATNegotiationFailed"));
+ nextScreen = "Menus/WOLWelcomeMenu.wnd";
+ TheShell->pop();
+ return; // don't do any more processing this frame, in case the screen goes away
+ }
+ }
+
+#ifdef PERF_TEST
+ UnsignedInt start = timeGetTime();
+ UnsignedInt end = timeGetTime();
+ std::list responses;
+ Int numMessages = 0;
+#endif // PERF_TEST
+
+ Int allowedMessages = TheGameSpyInfo->getMaxMessagesPerUpdate();
+ Bool sawImportantMessage = FALSE;
+ PeerResponse resp;
+ while (allowedMessages-- && !sawImportantMessage && TheGameSpyPeerMessageQueue->getResponse( resp ))
+ {
+#ifdef PERF_TEST
+ ++numMessages;
+ responses.push_back(resp.peerResponseType);
+#endif // PERF_TEST
+ switch (resp.peerResponseType)
+ {
+ case PeerResponse::PEERRESPONSE_PLAYERUTM:
+ {
+ if (stricmp(resp.command.c_str(), "STATS") == 0)
+ {
+ DEBUG_LOG(("Saw STATS from %s, data was '%s'", resp.nick.c_str(), resp.commandOptions.c_str()));
+ AsciiString data = resp.commandOptions.c_str();
+ AsciiString idStr;
+ data.nextToken(&idStr, " ");
+ Int id = atoi(idStr.str());
+ DEBUG_LOG(("data: %d(%s) - '%s'", id, idStr.str(), data.str()));
+
+ PSPlayerStats stats = TheGameSpyPSMessageQueue->parsePlayerKVPairs(data.str());
+ PSPlayerStats oldStats = TheGameSpyPSMessageQueue->findPlayerStatsByID(id);
+ stats.id = id;
+ DEBUG_LOG(("Parsed ID is %d, old ID is %d", stats.id, oldStats.id));
+ if (stats.id && (oldStats.id == 0))
+ TheGameSpyPSMessageQueue->trackPlayerStats(stats);
+
+ // now fill in the profileID in the game slot
+ AsciiString nick = resp.nick.c_str();
+ for (Int i=0; igetGameSpySlot(i);
+ if (slot && slot->isHuman() && (slot->getLoginName().compareNoCase(nick) == 0))
+ {
+ slot->setProfileID(id);
+ break;
+ }
+ }
+ }
+ Int slotNum = TheGameSpyGame->getSlotNum(resp.nick.c_str());
+ if ((slotNum >= 0) && (slotNum < MAX_SLOTS) && (stricmp(resp.command.c_str(), "NAT") == 0)) {
+ // this is a command for NAT negotiations, pass if off to TheNAT
+ sawImportantMessage = TRUE;
+ if (TheNAT != nullptr) {
+ TheNAT->processGlobalMessage(slotNum, resp.commandOptions.c_str());
+ }
+ }
+ /*
+ else if (key == "NAT")
+ {
+ if ((val >= FirewallHelperClass::FIREWALL_TYPE_SIMPLE) &&
+ (val <= FirewallHelperClass::FIREWALL_TYPE_DESTINATION_PORT_DELTA))
+ {
+ slot->setNATBehavior((FirewallHelperClass::FirewallBehaviorType)val);
+ DEBUG_LOG(("Setting NAT behavior to %d for player %d", val, slotNum));
+ change = true;
+ }
+ else
+ {
+ DEBUG_LOG(("Rejecting invalid NAT behavior %d from player %d", val, slotNum));
+ }
+ }
+ */
+ }
+ break;
+
+ case PeerResponse::PEERRESPONSE_DISCONNECT:
+ {
+ sawImportantMessage = TRUE;
+ UnicodeString title, body;
+ AsciiString disconMunkee;
+ disconMunkee.format("GUI:GSDisconReason%d", resp.discon.reason);
+ title = TheGameText->fetch( "GUI:GSErrorTitle" );
+ body = TheGameText->fetch( disconMunkee );
+ GameSpyCloseAllOverlays();
+ GSMessageBoxOk( title, body );
+ TheGameSpyInfo->reset();
+ TheShell->pop();
+ }
+ break; // LORENZEN ADDED. SORRY IF THIS "BREAKS" IT...
+
+
+ case PeerResponse::PEERRESPONSE_JOINGROUPROOM:
+ /*
+ if (resp.joinGroupRoom.ok)
+ {
+ TheGameSpyInfo->addText(L"Joined group room", GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ else
+ {
+ TheGameSpyInfo->addText(L"Didn't join group room", GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ */
+ break;
+ case PeerResponse::PEERRESPONSE_PLAYERJOIN:
+ {
+ //UnicodeString str;
+ //str.format(L"Player %hs joined the room", resp.nick.c_str());
+ //TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ break;
+ case PeerResponse::PEERRESPONSE_PLAYERLEFT:
+ {
+ //UnicodeString str;
+ //str.format(L"Player %hs left the room", resp.nick.c_str());
+ //TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ break;
+ case PeerResponse::PEERRESPONSE_MESSAGE:
+ {
+ //UnicodeString m;
+ //m.format(L"[%hs]: %ls", resp.nick.c_str(), resp.text.c_str());
+ //TheGameSpyInfo->addText(m, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ break;
+
+
+// LORENZEN EXPRESSES DOUBT ABOUT THIS ONE, AS IT MAY HAVE SUFFERED MERGE MANGLING... SORRY
+ // I THINK THIS IS THE OBSOLETE VERSION... SEE THE NEWER LOOKING ONE ABOVE
+/*
+ case PeerResponse::PEERRESPONSE_DISCONNECT:
+ {
+ UnicodeString title, body;
+ AsciiString disconMunkee;
+ disconMunkee.format("GUI:GSDisconReason%d", resp.discon.reason);
+ title = TheGameText->fetch( "GUI:GSErrorTitle" );
+ body = TheGameText->fetch( disconMunkee );
+ GameSpyCloseAllOverlays();
+ GSMessageBoxOk( title, body );
+ TheGameSpyInfo->reset();
+ TheShell->pop();
+ }
+*/
+
+
+
+ case PeerResponse::PEERRESPONSE_CREATESTAGINGROOM:
+ {
+ if (resp.createStagingRoom.result == PEERJoinSuccess)
+ {
+ // Woohoo! On to our next screen!
+ UnicodeString str;
+ str.format(L"Created staging room");
+ TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ else
+ {
+ UnicodeString s;
+ s.format(L"createStagingRoom result: %d", resp.createStagingRoom.result);
+ TheGameSpyInfo->addText( s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow );
+ }
+ }
+ break;
+ case PeerResponse::PEERRESPONSE_JOINSTAGINGROOM:
+ {
+ if (resp.joinStagingRoom.ok == PEERTrue)
+ {
+ // Woohoo! On to our next screen!
+ UnicodeString s;
+ s.format(L"joinStagingRoom result: %d", resp.joinStagingRoom.ok);
+ TheGameSpyInfo->addText( s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow );
+ }
+ else
+ {
+ UnicodeString s;
+ s.format(L"joinStagingRoom result: %d", resp.joinStagingRoom.ok);
+ TheGameSpyInfo->addText( s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow );
+ }
+ }
+ break;
+ case PeerResponse::PEERRESPONSE_STAGINGROOM:
+ {
+ UnicodeString str;
+ str.format(L"Staging room list callback", resp.nick.c_str());
+ TheGameSpyInfo->addText(str, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ break;
+ case PeerResponse::PEERRESPONSE_QUICKMATCHSTATUS:
+ {
+ sawImportantMessage = TRUE;
+ switch( resp.qmStatus.status )
+ {
+ case QM_IDLE:
+ //TheGameSpyInfo->addText(L"Status: QM_IDLE", GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ break;
+ case QM_JOININGQMCHANNEL:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:JOININGQMCHANNEL"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ break;
+ case QM_LOOKINGFORBOT:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:LOOKINGFORBOT"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ break;
+ case QM_SENTINFO:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:SENTINFO"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ break;
+ case QM_WORKING:
+ {
+ UnicodeString s;
+ s.format(TheGameText->fetch("QM:WORKING"), resp.qmStatus.poolSize);
+ TheGameSpyInfo->addText(s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ buttonWiden->winEnable( TRUE );
+ break;
+ case QM_POOLSIZE:
+ {
+ UnicodeString s;
+ s.format(TheGameText->fetch("QM:POOLSIZE"), resp.qmStatus.poolSize);
+ TheGameSpyInfo->addText(s, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ }
+ break;
+ case QM_WIDENINGSEARCH:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:WIDENINGSEARCH"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ buttonWiden->winEnable( FALSE );
+ break;
+ case QM_MATCHED:
+ {
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:MATCHED"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ buttonWiden->winEnable( FALSE );
+
+ TheGameSpyGame->enterGame();
+ TheGameSpyGame->setSeed(resp.qmStatus.seed);
+
+ TheGameSpyGame->markGameAsQM();
+
+ const LadderInfo *info = getLadderInfo();
+ if (!info)
+ {
+ TheGameSpyGame->setLadderIP("localhost");
+ TheGameSpyGame->setLadderPort(0);
+ }
+ else
+ {
+ TheGameSpyGame->setLadderIP(info->address);
+ TheGameSpyGame->setLadderPort(info->port);
+ }
+
+ Int i;
+ Int numPlayers = 0;
+ for (i=0; i maps = TheGameSpyConfig->getQMMaps();
+ for (std::list::const_iterator it = maps.begin(); it != maps.end(); ++it)
+ {
+ AsciiString theMap = *it;
+ theMap.toLower();
+ const MapMetaData *md = TheMapCache->findMap(theMap);
+ if (md && md->m_numPlayers >= numPlayers)
+ {
+ TheGameSpyGame->setMap(*it);
+ if (resp.qmStatus.mapIdx-- == 0)
+ break;
+ }
+ }
+
+ Int numPlayersPerTeam = numPlayers/2;
+ DEBUG_ASSERTCRASH(numPlayersPerTeam, ("0 players per team???"));
+ if (!numPlayersPerTeam)
+ numPlayersPerTeam = 1;
+
+ for (i=0; igetGameSpySlot(i);
+ if (resp.stagingRoomPlayerNames[i].empty())
+ {
+ slot->setState(SLOT_CLOSED);
+ }
+ else
+ {
+ AsciiString aName = resp.stagingRoomPlayerNames[i].c_str();
+ UnicodeString uName;
+ uName.translate(aName);
+ slot->setState(SLOT_PLAYER, uName, resp.qmStatus.IP[i]);
+ slot->setColor(resp.qmStatus.color[i]);
+ slot->setPlayerTemplate(resp.qmStatus.side[i]);
+ //slot->setProfileID(0);
+ slot->setNATBehavior((FirewallHelperClass::FirewallBehaviorType)resp.qmStatus.nat[i]);
+ slot->setLocale("");
+ slot->setTeamNumber( i/numPlayersPerTeam );
+ if (i==0)
+ TheGameSpyGame->setGameName(uName);
+ }
+ }
+
+ DEBUG_LOG(("Starting a QM game: options=[%s]", GameInfoToAsciiString(TheGameSpyGame).str()));
+ SendStatsToOtherPlayers(TheGameSpyGame);
+ TheGameSpyGame->startGame(0);
+ GameWindow *buttonBuddies = TheWindowManager->winGetWindowFromId(nullptr, buttonBuddiesID);
+ if (buttonBuddies)
+ buttonBuddies->winEnable(FALSE);
+ GameSpyCloseOverlay(GSOVERLAY_BUDDY);
+ }
+ break;
+ case QM_INCHANNEL:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:INCHANNEL"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ break;
+ case QM_NEGOTIATINGFIREWALLS:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:NEGOTIATINGFIREWALLS"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ break;
+ case QM_STARTINGGAME:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:STARTINGGAME"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ break;
+ case QM_COULDNOTFINDBOT:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:COULDNOTFINDBOT"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ buttonWiden->winEnable( FALSE );
+ buttonStart->winHide( FALSE );
+ buttonStop->winHide( TRUE );
+ enableOptionsGadgets(TRUE);
+ break;
+ case QM_COULDNOTFINDCHANNEL:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:COULDNOTFINDCHANNEL"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ buttonWiden->winEnable( FALSE );
+ buttonStart->winHide( FALSE );
+ buttonStop->winHide( TRUE );
+ enableOptionsGadgets(TRUE);
+ break;
+ case QM_COULDNOTNEGOTIATEFIREWALLS:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:COULDNOTNEGOTIATEFIREWALLS"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ buttonWiden->winEnable( FALSE );
+ buttonStart->winHide( FALSE );
+ buttonStop->winHide( TRUE );
+ enableOptionsGadgets(TRUE);
+ break;
+ case QM_STOPPED:
+ TheGameSpyInfo->addText(TheGameText->fetch("QM:STOPPED"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ buttonWiden->winEnable( FALSE );
+ buttonStart->winHide( FALSE );
+ buttonStop->winHide( TRUE );
+ enableOptionsGadgets(TRUE);
+ break;
+ }
+ }
+ break;
+ }
+ }
+#ifdef PERF_TEST
+ // check performance
+ end = timeGetTime();
+ UnsignedInt frameTime = end-start;
+ if (frameTime > 100 || responses.size() > 20)
+ {
+ UnicodeString munkee;
+ munkee.format(L"inQM:%d %d ms, %d messages", s_inQM, frameTime, responses.size());
+ TheGameSpyInfo->addText(munkee, GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+ PERF_LOG(("%ls", munkee.str()));
+
+ std::list::const_iterator it;
+ for (it = responses.begin(); it != responses.end(); ++it)
+ {
+ PERF_LOG((" %s", getMessageString(*it)));
+ }
+ }
+#endif // PERF_TEST
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+/** WOL Quick Match Menu input callback */
+//-------------------------------------------------------------------------------------------------
+WindowMsgHandledType WOLQuickMatchMenuInput( GameWindow *window, UnsignedInt msg,
+ WindowMsgData mData1, WindowMsgData mData2 )
+{
+ switch( msg )
+ {
+
+ // --------------------------------------------------------------------------------------------
+ case GWM_CHAR:
+ {
+ UnsignedByte key = mData1;
+ UnsignedByte state = mData2;
+ if (buttonPushed)
+ break;
+
+ switch( key )
+ {
+
+ // ----------------------------------------------------------------------------------------
+ case KEY_ESC:
+ {
+
+ //
+ // send a simulated selected event to the parent window of the
+ // back/exit button
+ //
+ if( BitIsSet( state, KEY_STATE_UP ) )
+ {
+ // ESC must not do what the disabled back button can't, e.g. leave during match setup
+ if(!buttonBack->winIsHidden() && buttonBack->winGetEnabled())
+ TheWindowManager->winSendSystemMsg( window, GBM_SELECTED,
+ (WindowMsgData)buttonBack, buttonBackID );
+
+ }
+
+ // don't let key fall through anywhere else
+ return MSG_HANDLED;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return MSG_IGNORED;
+}
+
+//-------------------------------------------------------------------------------------------------
+/** WOL Quick Match Menu window system callback */
+//-------------------------------------------------------------------------------------------------
+WindowMsgHandledType WOLQuickMatchMenuSystem( GameWindow *window, UnsignedInt msg,
+ WindowMsgData mData1, WindowMsgData mData2 )
+{
+ UnicodeString txtInput;
+
+ switch( msg )
+ {
+
+
+ case GWM_CREATE:
+ {
+
+ break;
+ }
+
+ case GWM_DESTROY:
+ {
+ break;
+ }
+
+ case GWM_INPUT_FOCUS:
+ {
+ // if we're givin the opportunity to take the keyboard focus we must say we want it
+ if( mData1 == TRUE )
+ *(Bool *)mData2 = TRUE;
+
+ return MSG_HANDLED;
+ }
+
+ case GCM_SELECTED:
+ {
+ if (buttonPushed)
+ break;
+ GameWindow *control = (GameWindow *)mData1;
+ Int controlID = control->winGetWindowId();
+ Int pos = -1;
+ GadgetComboBoxGetSelectedPos(control, &pos);
+
+ saveQuickMatchOptions();
+ if (controlID == comboBoxLadderID && !isPopulatingLadderBox)
+ {
+ if (pos >= 0)
+ {
+ QuickMatchPreferences pref;
+ Int ladderID = (Int)GadgetComboBoxGetItemData(control, pos);
+ if (ladderID == 0)
+ {
+ // no ladder selected - enable buttons
+ GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, max(0, pref.getNumPlayers()/2-1));
+ comboBoxNumPlayers->winEnable( TRUE );
+ populateQMSideComboBox(pref.getSide()); // this will set side to random and disable if necessary
+ }
+ else if (ladderID > 0)
+ {
+ // ladder selected - disable buttons
+ const LadderInfo *li = TheLadderList->findLadderByIndex(ladderID);
+ if (li)
+ GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, li->playersPerTeam-1);
+ else
+ GadgetComboBoxSetSelectedPos(comboBoxNumPlayers, 0);
+ comboBoxNumPlayers->winEnable( FALSE );
+
+ populateQMSideComboBox(pref.getSide(), li); // this will set side to random and disable if necessary
+ }
+ else
+ {
+ // "Choose a ladder" selected - open overlay
+ PopulateQMLadderComboBox(); // this restores the non-"Choose a ladder" selection
+ GameSpyOpenOverlay( GSOVERLAY_LADDERSELECT );
+ }
+ }
+ }
+ if (!isInInit)
+ {
+ QuickMatchPreferences pref;
+ populateQuickMatchMapSelectListbox(pref);
+ UpdateStartButton();
+ }
+ break;
+ }
+
+ case GBM_SELECTED:
+ {
+ if (buttonPushed)
+ break;
+ GameWindow *control = (GameWindow *)mData1;
+ Int controlID = control->winGetWindowId();
+ static NameKeyType buttonOptionsID = NAMEKEY("WOLQuickMatchMenu.wnd:ButtonOptions");
+
+ if ( controlID == buttonStopID )
+ {
+#if defined(GENERALS_ONLINE)
+ buttonWiden->winEnable(FALSE);
+ buttonStart->winEnable(TRUE);
+ buttonStart->winHide(FALSE);
+ buttonStop->winHide(TRUE);
+ enableOptionsGadgets(TRUE);
+
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, TheGameText->fetch("GUI:QMAborted"), GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ pMatchmakingInterface->CancelMatchmaking();
+ }
+#else
+ PeerRequest req;
+ req.peerRequestType = PeerRequest::PEERREQUEST_STOPQUICKMATCH;
+ TheGameSpyPeerMessageQueue->addRequest(req);
+ buttonWiden->winEnable( FALSE );
+ buttonStart->winHide( FALSE );
+ buttonStop->winHide( TRUE );
+ enableOptionsGadgets(TRUE);
+ TheGameSpyInfo->addText(TheGameText->fetch("GUI:QMAborted"), GameSpyColor[GSCOLOR_DEFAULT], quickmatchTextWindow);
+#endif
+ }
+ else if ( controlID == buttonOptionsID )
+ {
+ GameWindow *win =TheWindowManager->winGetWindowFromId(parentWOLQuickMatch,buttonOptionsID);
+ if (isInfoShown())
+ {
+ hideInfoGadgets(TRUE);
+ hideOptionsGadgets(FALSE);
+ GadgetButtonSetText(win, TheGameText->fetch("GUI:PlayerInfo"));
+ }
+ else
+ {
+ hideInfoGadgets(FALSE);
+ hideOptionsGadgets(TRUE);
+ GadgetButtonSetText(win, TheGameText->fetch("GUI:Setup"));
+ }
+ }
+ else if ( controlID == buttonWidenID )
+ {
+#if defined(GENERALS_ONLINE)
+ GadgetListBoxAddEntryText(quickmatchTextWindow, TheGameText->fetch("QM:WIDENINGSEARCH"), GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
+
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ pMatchmakingInterface->WidenSearch();
+
+ // disable widen button
+ buttonWiden->winEnable(FALSE);
+ }
+#else
+ PeerRequest req;
+ req.peerRequestType = PeerRequest::PEERREQUEST_WIDENQUICKMATCHSEARCH;
+ TheGameSpyPeerMessageQueue->addRequest(req);
+ buttonWiden->winEnable( FALSE );
+#endif
+ }
+ else if ( controlID == buttonStartID )
+ {
+#if defined(GENERALS_ONLINE)
+
+ std::vector vecSelectedMapIndexes;
+ uint16_t playlistID = 0;
+ int minSelectedMaps = 0;
+
+ // get maps and playlist ID
+ std::list maps;
+ Int playlistIndex = -1;
+ GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &playlistIndex);
+
+ if (playlistIndex != -1)
+ {
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ PlaylistEntry plEntry = pMatchmakingInterface->GetCachedPlaylistFromIndex(playlistIndex);
+ if (plEntry.PlaylistID != -1)
+ {
+ playlistID = plEntry.PlaylistID;
+ minSelectedMaps = plEntry.MinSelectedMaps;
+
+ // maps
+ Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
+ for (Int i = 0; i < numMaps; ++i)
+ {
+ bool bMapSelected = GadgetListBoxGetItemData(listboxMapSelect, i, 0);
+ if (bMapSelected)
+ {
+ vecSelectedMapIndexes.push_back(i);
+ }
+ }
+ }
+ else
+ {
+ // TODO_QUICKMATCH: Error?
+ }
+ }
+ }
+ else
+ {
+ // TODO_QUICKMATCH: Error?
+ }
+
+ if (static_cast(vecSelectedMapIndexes.size()) < minSelectedMaps)
+ {
+ UnicodeString msg;
+ msg.format(L"You must select at least %d maps.", minSelectedMaps);
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, msg, GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+
+ // buttons
+ buttonWiden->winEnable(FALSE);
+ buttonStart->winHide(FALSE);
+ buttonStart->winEnable(TRUE);
+ buttonStop->winHide(TRUE);
+
+ break;
+ }
+
+ // buttons
+ buttonWiden->winEnable(FALSE);
+ buttonStart->winEnable(FALSE);
+ buttonStop->winHide(TRUE);
+
+ NGMP_OnlineServices_MatchmakingInterface* pMatchmakingInterface = NGMP_OnlineServicesManager::GetInterface();
+ if (pMatchmakingInterface != nullptr)
+ {
+ pMatchmakingInterface->StartMatchmaking(playlistID, vecSelectedMapIndexes, [](bool bSuccess)
+ {
+ // TODO_QUICKMATCH: Chat has a sound effect in TheGameSpyInfo, re-eanble it
+ if (bSuccess)
+ {
+
+ // buttons
+ buttonWiden->winEnable(TRUE);
+ buttonStart->winHide(TRUE);
+ buttonStop->winHide(FALSE);
+ }
+ else
+ {
+ Int index = GadgetListBoxAddEntryText(quickmatchTextWindow, UnicodeString(L"Failed to start matchmaking."), GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
+ GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index);
+
+ // buttons
+ buttonWiden->winEnable(FALSE);
+ buttonStart->winHide(FALSE);
+ buttonStart->winEnable(TRUE);
+ buttonStop->winHide(TRUE);
+ }
+ });
+ }
+#else
+ PeerRequest req;
+ req.peerRequestType = PeerRequest::PEERREQUEST_STARTQUICKMATCH;
+ req.qmMaps.clear();
+ Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
+ for ( Int i=0; i= maxPingEntries - 1)
+ {
+ req.QM.maxPing = TheGameSpyConfig->getPingTimeoutInMs();
+ }
+ else
+ req.QM.maxPing = (val+1)*100;
+
+ PSPlayerStats stats = TheGameSpyPSMessageQueue->findPlayerStatsByID(TheGameSpyInfo->getLocalProfileID());
+ req.QM.points = CalculateRank(stats);
+
+ Int ladderIndex, index, selected;
+ GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected );
+ ladderIndex = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected );
+ const LadderInfo *ladderInfo = nullptr;
+ if (ladderIndex < 0)
+ {
+ ladderIndex = 0;
+ }
+ if (ladderIndex)
+ {
+ ladderInfo = TheLadderList->findLadderByIndex( ladderIndex );
+ if (!ladderInfo)
+ {
+ ladderIndex = 0; // sanity
+ }
+ }
+ req.QM.ladderID = ladderIndex;
+
+ req.QM.ladderPassCRC = 0;
+
+ index = -1;
+ GadgetComboBoxGetSelectedPos( comboBoxSide, &selected );
+ if (selected >= 0)
+ index = (Int)GadgetComboBoxGetItemData( comboBoxSide, selected );
+ req.QM.side = index;
+ if (ladderInfo && ladderInfo->randomFactions)
+ {
+ Int sideNum = GameClientRandomValue(0, ladderInfo->validFactions.size()-1);
+ DEBUG_LOG(("Looking for %d out of %d random sides", sideNum, ladderInfo->validFactions.size()));
+ AsciiStringListConstIterator cit = ladderInfo->validFactions.begin();
+ while (sideNum)
+ {
+ ++cit;
+ --sideNum;
+ }
+ if (cit != ladderInfo->validFactions.end())
+ {
+ Int numPlayerTemplates = ThePlayerTemplateStore->getPlayerTemplateCount();
+ AsciiString sideStr = *cit;
+ DEBUG_LOG(("Chose %s as our side... finding", sideStr.str()));
+ for (Int c=0; cgetNthPlayerTemplate(c);
+ if (fac && fac->getSide() == sideStr)
+ {
+ DEBUG_LOG(("Found %s in index %d", sideStr.str(), c));
+ req.QM.side = c;
+ break;
+ }
+ }
+ }
+ }
+ else if( index == PLAYERTEMPLATE_RANDOM )
+ {
+ // If not a forced random ladder, then we need to resolve our pick of random right now anyway, or else
+ // we will get the same pick every darn time.
+ Int randomTries = 0;// Rare to hit Random 10 times in a row, but if it does then random will be converted to a set side by the very bug this tries to fix, so no harm done.
+
+ while( randomTries < 10 && index == PLAYERTEMPLATE_RANDOM )
+ {
+ Int numberComboBoxEntries = GadgetComboBoxGetLength(comboBoxSide);
+ Int randomPick = GameClientRandomValue(0, numberComboBoxEntries - 1);
+ index = (Int)GadgetComboBoxGetItemData( comboBoxSide, randomPick );
+ req.QM.side = index;
+
+ randomTries++;
+ }
+ }
+
+ index = -1;
+ GadgetComboBoxGetSelectedPos( comboBoxColor, &selected );
+ if (selected >= 0)
+ index = (Int)GadgetComboBoxGetItemData( comboBoxColor, selected );
+ req.QM.color = index;
+
+ OptionPreferences natPref;
+ req.QM.NAT = natPref.getFirewallBehavior();
+
+ if (ladderIndex)
+ {
+ req.QM.numPlayers = (ladderInfo)?ladderInfo->playersPerTeam*2 : 2;
+ }
+ else
+ {
+ GadgetComboBoxGetSelectedPos(comboBoxNumPlayers, &val);
+ if (val < 0)
+ val = 0;
+ req.QM.numPlayers = (val+1)*2;
+ }
+
+ Int numDiscons = 0;
+ PerGeneralMap::iterator it;
+ for(it =stats.discons.begin(); it != stats.discons.end(); ++it)
+ {
+ numDiscons += it->second;
+ }
+ for(it =stats.desyncs.begin(); it != stats.desyncs.end(); ++it)
+ {
+ numDiscons += it->second;
+ }
+ req.QM.discons = numDiscons;
+
+
+ strlcpy(req.QM.pings, TheGameSpyInfo->getPingString().str(), sizeof(req.QM.pings));
+
+ req.QM.botID = TheGameSpyConfig->getQMBotID();
+ req.QM.roomID = TheGameSpyConfig->getQMChannel();
+
+ req.QM.exeCRC = TheGlobalData->m_exeCRC;
+ req.QM.iniCRC = TheGlobalData->m_iniCRC;
+
+ TheGameSpyPeerMessageQueue->addRequest(req);
+ buttonWiden->winEnable( FALSE );
+ buttonStart->winHide( TRUE );
+ buttonStop->winHide( FALSE );
+ enableOptionsGadgets(FALSE);
+
+ if (ladderIndex > 0)
+ {
+ // save the ladder as being played upon even if we cancel out of matching early...
+ LadderPreferences ladPref;
+ ladPref.loadProfile( TheGameSpyInfo->getLocalProfileID() );
+ LadderPref p;
+ p.lastPlayDate = time(nullptr);
+ p.address = ladderInfo->address;
+ p.port = ladderInfo->port;
+ p.name = ladderInfo->name;
+ ladPref.addRecentLadder( p );
+ ladPref.write();
+ }
+#endif
+ }
+ else if ( controlID == buttonBuddiesID )
+ {
+ GameSpyToggleOverlay( GSOVERLAY_BUDDY );
+ }
+ else if ( controlID == buttonBackID )
+ {
+#if !defined(GENERALS_ONLINE)
+ TheGameSpyInfo->leaveGroupRoom();
+#endif
+ buttonPushed = true;
+ nextScreen = "Menus/WOLWelcomeMenu.wnd";
+ TheShell->pop();
+ }
+ else if ( controlID == buttonSelectAllMapsID )
+ {
+ Int numMaps = GadgetListBoxGetNumEntries(listboxMapSelect);
+ for ( Int i=0; iwinGetWindowId();
+ Int selected = (Int)mData2;
+
+ if ( controlID == listboxMapSelectID )
+ {
+ const LadderInfo *li = getLadderInfo();
+ if (selected >= 0 && (!li || !li->randomMaps))
+ {
+ Bool wasSelected = (Bool)GadgetListBoxGetItemData(control, selected, 0);
+ GadgetListBoxSetItemData(control, (void *)(!wasSelected), selected, 0);
+ Int width = 10;
+ Int height = 10;
+ const Image *img = (!wasSelected)?selectedImage:unselectedImage;
+ if ( img )
+ {
+ width = min(GadgetListBoxGetColumnWidth(control, 0), img->getImageWidth());
+ height = width;
+ }
+ GadgetListBoxAddEntryImage(control, img, selected, 0, height, width);
+ GadgetListBoxAddEntryText(control, GadgetListBoxGetText(control, selected, 1), GameSpyColor[(wasSelected)?GSCOLOR_MAP_UNSELECTED:GSCOLOR_MAP_SELECTED], selected, 1);
+ }
+ if (selected >= 0)
+ GadgetListBoxSetSelected(control, -1);
+ }
+ UpdateStartButton();
+ break;
+ }
+
+ case GEM_EDIT_DONE:
+ {
+ break;
+ }
+ default:
+ return MSG_IGNORED;
+
+ }
+
+ return MSG_HANDLED;
+}
+
+
From f0e74f60da4c386b42d3a2722a4da64bb926e890 Mon Sep 17 00:00:00 2001
From: tintinhamans <5984296+tintinhamans@users.noreply.github.com>
Date: Sat, 26 Sep 2026 16:23:46 +0200
Subject: [PATCH 2/2] fix(communicator): Decode UTF-8 display names across the
online UI
---
.../GameNetwork/GeneralsOnline/NGMP_include.h | 1 +
.../GUICallbacks/Menus/WOLBuddyOverlay.cpp | 27 ++++++++-------
.../GUICallbacks/Menus/WOLQuickMatchMenu.cpp | 2 +-
.../GeneralsOnline/NGMP_Helpers.cpp | 34 +++++++++++++++----
4 files changed, 44 insertions(+), 20 deletions(-)
diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h
index 07d590e64f5..6bee19efb2e 100644
--- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h
+++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/NGMP_include.h
@@ -28,6 +28,7 @@ void NetworkLog(ELogVerbosity logVerbosity, const char* fmt, ...);
std::string to_utf8(const std::wstring& wstr);
std::wstring from_utf8(const std::string& utf8_str);
std::wstring NormalizeSingleLineText(const std::wstring& text);
+std::wstring WidenFormatSpecifiers(const std::wstring& format);
int RoundUpLatencyToFrameInterval(int latency, int frameInterval);
int ConvertMSLatencyToFrames(int ms);
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp
index 2069e0d0419..764f417da01 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLBuddyOverlay.cpp
@@ -570,7 +570,7 @@ void updateBuddyInfo( void )
if (!pSocialInterface->IsUserFriend(profileID) && !pSocialInterface->IsUserPendingRequest(profileID))
{
UnicodeString strName;
- strName.format(L"%hs", friendsEntry.display_name.c_str());
+ strName.format(L"%s", from_utf8(friendsEntry.display_name).c_str());
// insert name into box
int index = GadgetListBoxAddEntryText(buddyControls.listboxBuddies, strName, GameSpyColor[GSCOLOR_CHAT_EMOTE], -1, -1);
@@ -591,11 +591,8 @@ void updateBuddyInfo( void )
{
FriendsEntry friendsEntry = kvPair.second;
int64_t profileID = friendsEntry.user_id;
- AsciiString strName = AsciiString(friendsEntry.display_name.c_str());
-
// insert name into box
- UnicodeString formatStr;
- formatStr.translate(strName.str());
+ UnicodeString formatStr(from_utf8(friendsEntry.display_name).c_str());
int index = GadgetListBoxAddEntryText(buddyControls.listboxBuddies, formatStr, GameSpyColor[GSCOLOR_DEFAULT], -1, -1);
GadgetListBoxSetItemData(buddyControls.listboxBuddies, (void*)(profileID), index, 0);
@@ -637,11 +634,11 @@ void updateBuddyInfo( void )
UnicodeString strName;
if (friendsEntry.online)
{
- strName.format(L"\u25CF %hs", friendsEntry.display_name.c_str());
+ strName.format(L"\u25CF %s", from_utf8(friendsEntry.display_name).c_str());
}
else
{
- strName.format(L"\u25CC %hs", friendsEntry.display_name.c_str());
+ strName.format(L"\u25CC %s", from_utf8(friendsEntry.display_name).c_str());
}
if (numUnreadMessages > 0)
@@ -668,7 +665,7 @@ void updateBuddyInfo( void )
if (friendsEntry.online)
{
UnicodeString strGameState = TheGameText->fetch("Buddy:Online");
- formatStr.format(L"%s - %hs", strGameState.str(), friendsEntry.presence.c_str());
+ formatStr.format(L"%s - %s", strGameState.str(), from_utf8(friendsEntry.presence).c_str());
}
else
{
@@ -994,7 +991,14 @@ void showNotificationBox(AsciiString nick, UnicodeString message)
}
if (nick.isNotEmpty())
+ {
+#if defined(GENERALS_ONLINE)
+ // Generals Online nicks are UTF-8 display names
+ message.format(WidenFormatSpecifiers(message.str()).c_str(), from_utf8(nick.str()).c_str());
+#else
message.format(message, nick.str());
+#endif
+ }
GadgetButtonSetText(win, message);
//GadgetStaticTextSetText(win, message);
noticeExpires = timeGetTime() + NOTIFICATION_EXPIRES;
@@ -1126,7 +1130,7 @@ void WOLBuddyOverlayInit( WindowLayout *layout, void *userData )
listboxIgnore = TheWindowManager->winGetWindowFromId( parent, listboxIgnoreID);
if (radioButtonIgnore)
- GadgetRadioSetText(radioButtonIgnore, UnicodeString(L"Block"));
+ GadgetRadioSetText(radioButtonIgnore, UnicodeString(L"Blocked"));
InitBuddyControls(BUDDY_WINDOW_BUDDIES);
@@ -2049,10 +2053,7 @@ void refreshIgnoreList()
for (FriendsEntry& blockedEntry : blockResult.vecBlocked)
{
- AsciiString strName = AsciiString(blockedEntry.display_name.c_str());
-
- UnicodeString name;
- name.translate(strName);
+ UnicodeString name(from_utf8(blockedEntry.display_name).c_str());
Int index = GadgetListBoxAddEntryText(listboxIgnore, name, GameMakeColor(255, 100, 100, 255), -1);
GadgetListBoxSetItemData(listboxIgnore, (void*)(blockedEntry.user_id), index, 0);
}
diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp
index 26697a6fb5d..da7c209c0f1 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp
@@ -1027,7 +1027,7 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData )
NGMP_OnlineServices_AuthInterface* pAuthInterface = NGMP_OnlineServicesManager::GetInterface();
if (pAuthInterface != nullptr)
{
- tmp.format(TheGameText->fetch("GUI:QuickMatchTitle"), pAuthInterface->GetDisplayName().c_str());
+ tmp.format(WidenFormatSpecifiers(TheGameText->fetch("GUI:QuickMatchTitle").str()).c_str(), pAuthInterface->GetDisplayNameW().c_str());
}
#endif
GadgetStaticTextSetText(staticTextTitle, tmp);
diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp
index 271b6e23805..3a943805065 100644
--- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp
+++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/NGMP_Helpers.cpp
@@ -4,8 +4,6 @@
#include
#include
#include
-#include
-#include
#include "../OnlineServices_Init.h"
#include "../OnlineServices_Auth.h"
@@ -14,16 +12,40 @@ std::mutex m_logMutex;
extern NGMPGame* TheNGMPGame;
+// Win32 conversions handle surrogate pairs and substitute U+FFFD for invalid input instead of throwing,
+// so a malformed name from the server can't take down the UI
std::string to_utf8(const std::wstring& wstr)
{
- std::wstring_convert> converter;
- return converter.to_bytes(wstr);
+ if (wstr.empty())
+ return std::string();
+
+ int len = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
+ std::string result(len, '\0');
+ WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(), len, nullptr, nullptr);
+ return result;
}
std::wstring from_utf8(const std::string& utf8_str)
{
- std::wstring_convert> converter;
- return converter.from_bytes(utf8_str);
+ if (utf8_str.empty())
+ return std::wstring();
+
+ int len = MultiByteToWideChar(CP_UTF8, 0, utf8_str.data(), (int)utf8_str.size(), nullptr, 0);
+ std::wstring result(len, L'\0');
+ MultiByteToWideChar(CP_UTF8, 0, utf8_str.data(), (int)utf8_str.size(), result.data(), len);
+ return result;
+}
+
+// Legacy format strings take player names as narrow %hs arguments, which get widened byte by byte and mangle UTF-8.
+// Rewriting %hs to %s lets callers pass a from_utf8() decoded name instead.
+std::wstring WidenFormatSpecifiers(const std::wstring& format)
+{
+ std::wstring widened = format;
+ for (size_t pos = widened.find(L"%hs"); pos != std::wstring::npos; pos = widened.find(L"%hs", pos + 2))
+ {
+ widened.erase(pos + 1, 1);
+ }
+ return widened;
}
std::wstring NormalizeSingleLineText(const std::wstring& text)