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)