/* ** Command & Conquer Generals(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 int the GameEngine #include "Common/GameEngine.h" #include "Common/QuickmatchPreferences.h" #include "Common/LadderPreferences.h" #include "Common/MultiplayerSettings.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/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 "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 _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif #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 // 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 = NULL; static GameWindow *buttonBack = NULL; static GameWindow *buttonStart = NULL; static GameWindow *buttonStop = NULL; static GameWindow *buttonWiden = NULL; GameWindow *quickmatchTextWindow = NULL; static GameWindow *listboxMapSelect = NULL; //static GameWindow *textEntryMaxDisconnects = NULL; //static GameWindow *textEntryMaxPoints = NULL; //static GameWindow *textEntryMinPoints = NULL; static GameWindow *textEntryWaitTime = NULL; static GameWindow *comboBoxNumPlayers = NULL; static GameWindow *comboBoxMaxPing = NULL; static GameWindow *comboBoxLadder = NULL; static GameWindow *comboBoxDisabledLadder = NULL; // enable and disable this, but never use it. it is a stand-in for comboBoxLadder for when there are no ladders static GameWindow *comboBoxMaxDisconnects = NULL; static GameWindow *staticTextNumPlayers = NULL; static GameWindow *comboBoxSide = NULL; static GameWindow *comboBoxColor = NULL; static Bool isShuttingDown = false; static Bool buttonPushed = false; static char *nextScreen = NULL; static Bool raiseMessageBoxes = false; static Bool isInInit = FALSE; static const Image *selectedImage = NULL; static const Image *unselectedImage = NULL; static bool isPopulatingLadderBox = false; static Int maxPingEntries = 0; static Int maxPoints= 100; static Int minPoints = 0; static const LadderInfo * getLadderInfo( void ); // [SKB: Jul 01 2003 @ 7:7pm] : // German2 now has fewer maps. When trying to do a QM with a Retail version // the bool string sent to the QMBot is almost always a different size. This // mapping is kept so that we now send a string of all maps instead of just // the ones that are visible to the user. #define VARIABLE_NUMBER_OF_MAPS 1 #if VARIABLE_NUMBER_OF_MAPS typedef std::vector MapListboxIndex; static MapListboxIndex mapListboxIndex; #endif static Bool isInfoShown(void) { 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 (comboBoxLadder) comboBoxLadder->winEnable(doIt); if (comboBoxDisabledLadder) comboBoxDisabledLadder->winEnable(FALSE); if (comboBoxMaxPing) comboBoxMaxPing->winEnable(doIt); if (comboBoxMaxDisconnects) comboBoxMaxDisconnects->winEnable(doIt); } } 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 UpdateStartButton(void) { 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 = NULL) { 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. } 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; Int localProfile = TheGameSpyInfo->getLocalProfileID(); Color specialColor = GameSpyColor[GSCOLOR_MAP_SELECTED]; Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED]; Color favoriteColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED]; Int index; GadgetListBoxReset( win ); std::set usedLadders; // start with "No Ladder" index = GadgetListBoxAddEntryText( win, TheGameText->fetch("GUI:NoLadder"), normalColor, -1 ); GadgetListBoxSetItemData( win, 0, 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 ); isPopulatingLadderBox = false; } static const LadderInfo * getLadderInfo( void ) { Int index; Int selected; GadgetComboBoxGetSelectedPos( comboBoxLadder, &selected ); index = (Int)GadgetComboBoxGetItemData( comboBoxLadder, selected ); const LadderInfo *li = TheLadderList->findLadderByIndex( index ); return li; } void PopulateQMLadderComboBox( void ) { if (!parentWOLQuickMatch || !comboBoxLadder) return; isPopulatingLadderBox = true; QuickMatchPreferences pref; Int localProfile = TheGameSpyInfo->getLocalProfileID(); 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, 0 ); 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 ) { std::list maps = TheGameSpyConfig->getQMMaps(); // 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 == NULL || li->randomMaps == FALSE ); 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; } #if VARIABLE_NUMBER_OF_MAPS mapListboxIndex.clear(); #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); #if VARIABLE_NUMBER_OF_MAPS mapListboxIndex.push_back(index); #endif } else { #if VARIABLE_NUMBER_OF_MAPS // [SKB: Jul 01 2003 @ 7:9pm] : // Keep track of maps that are not visible right now so // they are added to the information sent to the QMBot. mapListboxIndex.push_back(-1); #endif } } } static void saveQuickMatchOptions( void ) { if(isInInit) return; QuickMatchPreferences pref; 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++; } } 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\n")); TheShell->popImmediate(); return; } } nextScreen = NULL; buttonPushed = false; isShuttingDown = false; raiseMessageBoxes = true; if (TheNAT != NULL) { delete TheNAT; TheNAT = NULL; } 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( NULL, 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); //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 ); TheGameSpyInfo->registerTextWindow(quickmatchTextWindow); staticTextNumPlayers = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, staticTextNumPlayersID ); comboBoxSide = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxSideID ); comboBoxColor = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, comboBoxColorID ); if (TheLadderList->getStandardLadders()->size() == 0 && TheLadderList->getSpecialLadders()->size() == 0 && TheLadderList->getLocalLadders()->size() == 0) { // no ladders, so just disable them comboBoxDisabledLadder = comboBoxLadder; comboBoxLadder = NULL; isPopulatingLadderBox = TRUE; Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED]; Int index; GadgetComboBoxReset( comboBoxDisabledLadder ); index = GadgetComboBoxAddEntry( comboBoxDisabledLadder, TheGameText->fetch("GUI:NoLadder"), normalColor ); 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 = NULL; GameWindow *staticTextLadder = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, NAMEKEY("WOLQuickMatchMenu.wnd:StaticTextLadder") ); if (staticTextLadder) staticTextLadder->winHide(TRUE); */ } GameWindow *buttonBuddies = TheWindowManager->winGetWindowFromId(NULL, buttonBuddiesID); if (buttonBuddies) buttonBuddies->winEnable(TRUE); GameWindow *staticTextTitle = TheWindowManager->winGetWindowFromId( parentWOLQuickMatch, NAMEKEY("WOLQuickMatchMenu.wnd:StaticTextTitle") ); if (staticTextTitle) { UnicodeString tmp; tmp.format(TheGameText->fetch("GUI:QuickMatchTitle"), TheGameSpyInfo->getLocalName().str()); GadgetStaticTextSetText(staticTextTitle, tmp); } // QM is not going yet, so disable the Widen Search button buttonWiden->winEnable( FALSE ); buttonStop->winHide( TRUE ); buttonStart->winHide( FALSE ); 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(); Color c = GameSpyColor[GSCOLOR_DEFAULT]; GadgetComboBoxReset( comboBoxNumPlayers ); Int i; for (i=1; i<5; ++i) { s.format(TheGameText->fetch("GUI:PlayersVersusPlayers"), i, i); GadgetComboBoxAddEntry( comboBoxNumPlayers, s, c ); } GadgetComboBoxSetSelectedPos( comboBoxNumPlayers, max(0, pref.getNumPlayers()) ); 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 ); maxPingEntries = (TheGameSpyConfig->getPingTimeoutInMs() - 1) / 100; 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); TheGameSpyGame->reset(); GadgetListBoxReset(listboxMapSelect); populateQuickMatchMapSelectListbox(pref); UpdateLocalPlayerStats(); UpdateStartButton(); TheTransitionHandler->setGroup("WOLQuickMatchMenuFade"); isInInit= FALSE; } // WOLQuickMatchMenuInit //------------------------------------------------------------------------------------------------- /** 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 != NULL) ); if (nextScreen != NULL) { TheShell->push(nextScreen); } nextScreen = NULL; } // end if //------------------------------------------------------------------------------------------------- /** WOL Quick Match Menu shutdown method */ //------------------------------------------------------------------------------------------------- void WOLQuickMatchMenuShutdown( WindowLayout *layout, void *userData ) { TheGameSpyInfo->unregisterTextWindow(quickmatchTextWindow); if (!TheGameEngine->getQuitting()) saveQuickMatchOptions(); parentWOLQuickMatch = NULL; buttonBack = NULL; quickmatchTextWindow = NULL; selectedImage = unselectedImage = NULL; isShuttingDown = true; // if we are shutting down for an immediate pop, skip the animations Bool popImmediate = *(Bool *)userData; if( popImmediate ) { shutdownComplete( layout ); return; } //end if TheShell->reverseAnimatewindow(); TheTransitionHandler->reverse("WOLQuickMatchMenuFade"); RaiseGSMessageBox(); } // WOLQuickMatchMenuShutdown #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) { 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; } /// @todo: MDC handle disconnects in-game the same way as Custom Match! if (TheShell->isAnimFinished() && !buttonPushed && TheGameSpyPeerMessageQueue) { HandleBuddyResponses(); HandlePersistentStorageResponses(); if (TheGameSpyGame && TheGameSpyGame->isGameInProgress()) { if (TheGameSpyInfo->isDisconnectedAfterGameStart(NULL)) { 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( NULL, 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 != NULL) { 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 = NULL; // 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")) { DEBUG_LOG(("Saw STATS from %s, data was '%s'\n", 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'\n", 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\n", 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"))) { // this is a command for NAT negotiations, pass if off to TheNAT sawImportantMessage = TRUE; if (TheNAT != NULL) { 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\n", val, slotNum)); change = true; } else { DEBUG_LOG(("Rejecting invalid NAT behavior %d from player %d\n", 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(); } case PeerResponse::PEERRESPONSE_QUICKMATCHSTATUS: { sawImportantMessage = TRUE; switch( resp.qmStatus.status ) { case QM_IDLE: //TheGameSpyInfo->addText(UnicodeString(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(); #if VARIABLE_NUMBER_OF_MAPS std::list::const_iterator it = maps.begin(); std::advance(it, resp.qmStatus.mapIdx); AsciiString theMap = *it; theMap.toLower(); TheGameSpyGame->setMap(theMap); #else 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; } } #endif 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]\n", GameInfoToAsciiString(TheGameSpyGame).str())); SendStatsToOtherPlayers(TheGameSpyGame); TheGameSpyGame->startGame(0); GameWindow *buttonBuddies = TheWindowManager->winGetWindowFromId(NULL, 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\n", munkee.str())); std::list::const_iterator it; for (it = responses.begin(); it != responses.end(); ++it) { PERF_LOG((" %s\n", getMessageString(*it))); } } #endif // PERF_TEST } }// WOLQuickMatchMenuUpdate //------------------------------------------------------------------------------------------------- /** 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( BitTest( state, KEY_STATE_UP ) ) { if(!buttonBack->winIsHidden()) TheWindowManager->winSendSystemMsg( window, GBM_SELECTED, (WindowMsgData)buttonBack, buttonBackID ); } // end if // don't let key fall through anywhere else return MSG_HANDLED; } // end escape } // end switch( key ) } // end char } // end switch( msg ) return MSG_IGNORED; }// WOLQuickMatchMenuInput //------------------------------------------------------------------------------------------------- /** 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: case GWM_DESTROY: { break; } // case GWM_DESTROY: 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 GWM_INPUT_FOCUS: 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 GCM_SELECTED case GBM_SELECTED: { if (buttonPushed) break; GameWindow *control = (GameWindow *)mData1; Int controlID = control->winGetWindowId(); static NameKeyType buttonOptionsID = NAMEKEY("WOLQuickMatchMenu.wnd:ButtonOptions"); if ( controlID == buttonStopID ) { 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); } 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 ) { PeerRequest req; req.peerRequestType = PeerRequest::PEERREQUEST_WIDENQUICKMATCHSEARCH; TheGameSpyPeerMessageQueue->addRequest(req); buttonWiden->winEnable( FALSE ); } else if ( controlID == buttonStartID ) { PeerRequest req; req.peerRequestType = PeerRequest::PEERREQUEST_STARTQUICKMATCH; req.qmMaps.clear(); #if VARIABLE_NUMBER_OF_MAPS for (MapListboxIndex::iterator idxIt = mapListboxIndex.begin(); idxIt != mapListboxIndex.end(); ++idxIt) { Int index = (*idxIt); if (index >= 0) { req.qmMaps.push_back(GadgetListBoxGetItemData(listboxMapSelect, index, 0)); } else { req.qmMaps.push_back(false); } } #else 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 = NULL; 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\n", 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\n", sideStr.str())); for (Int c=0; cgetNthPlayerTemplate(c); if (fac && fac->getSide() == sideStr) { DEBUG_LOG(("Found %s in index %d\n", sideStr.str(), c)); req.QM.side = c; break; } } } } 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; strncpy(req.QM.pings, TheGameSpyInfo->getPingString().str(), 17); req.QM.pings[16] = 0; 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(NULL); p.address = ladderInfo->address; p.port = ladderInfo->port; p.name = ladderInfo->name; ladPref.addRecentLadder( p ); ladPref.write(); } } else if ( controlID == buttonBuddiesID ) { GameSpyToggleOverlay( GSOVERLAY_BUDDY ); } else if ( controlID == buttonBackID ) { buttonPushed = true; TheGameSpyInfo->leaveGroupRoom(); nextScreen = "Menus/WOLWelcomeMenu.wnd"; TheShell->pop(); } //if ( controlID == buttonBack ) 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 GLM_SELECTED case GEM_EDIT_DONE: { break; } default: return MSG_IGNORED; }//Switch return MSG_HANDLED; }// WOLQuickMatchMenuSystem