/*
** 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: PopupHostGame.cpp /////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//
// Electronic Arts Pacific.
//
// Confidential Information
// Copyright (C) 2002 - All Rights Reserved
//
//-----------------------------------------------------------------------------
//
// created: Jul 2002
//
// Filename: PopupHostGame.cpp
//
// author: Chris Huybregts
//
// purpose: Contains the Callbacks for the Host Game Popus
//
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// USER INCLUDES //////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GlobalData.h"
#include "Common/NameKeyGenerator.h"
#include "Common/Version.h"
#include "GameClient/WindowLayout.h"
#include "GameClient/Gadget.h"
#include "GameClient/GameText.h"
#include "GameClient/KeyDefs.h"
#include "GameClient/GadgetTextEntry.h"
#include "GameClient/GadgetCheckBox.h"
#include "GameClient/GadgetComboBox.h"
#include "GameClient/GadgetListBox.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#include "GameNetwork/GameSpy/Peerdefs.h"
#include "GameNetwork/GameSpy/PeerThread.h"
#include "GameNetwork/GameSpyOverlay.h"
#include "GameNetwork/GameSpy/LadderDefs.h"
#include "Common/CustomMatchPreferences.h"
#include "Common/LadderPreferences.h"
//-----------------------------------------------------------------------------
// DEFINES ////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
static NameKeyType parentPopupID = NAMEKEY_INVALID;
static NameKeyType textEntryGameNameID = NAMEKEY_INVALID;
static NameKeyType buttonCreateGameID = NAMEKEY_INVALID;
static NameKeyType checkBoxAllowObserversID = NAMEKEY_INVALID;
static NameKeyType textEntryGameDescriptionID = NAMEKEY_INVALID;
static NameKeyType buttonCancelID = NAMEKEY_INVALID;
static NameKeyType textEntryLadderPasswordID = NAMEKEY_INVALID;
static NameKeyType comboBoxLadderNameID = NAMEKEY_INVALID;
static NameKeyType textEntryGamePasswordID = NAMEKEY_INVALID;
static GameWindow *parentPopup = NULL;
static GameWindow *textEntryGameName = NULL;
static GameWindow *buttonCreateGame = NULL;
static GameWindow *checkBoxAllowObservers = NULL;
static GameWindow *textEntryGameDescription = NULL;
static GameWindow *buttonCancel = NULL;
static GameWindow *comboBoxLadderName = NULL;
static GameWindow *textEntryLadderPassword = NULL;
static GameWindow *textEntryGamePassword = NULL;
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
void createGame( void );
//-----------------------------------------------------------------------------
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
// Ladders --------------------------------------------------------------------------------
static bool isPopulatingLadderBox = false;
void CustomMatchHideHostPopup(Bool hide)
{
if (!parentPopup)
return;
parentPopup->winHide( hide );
}
void HandleCustomLadderSelection(Int ladderID)
{
if (!parentPopup)
return;
CustomMatchPreferences pref;
if (ladderID == 0)
{
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();
}
void PopulateCustomLadderListBox( GameWindow *win )
{
if (!parentPopup || !win)
return;
isPopulatingLadderBox = true;
CustomMatchPreferences pref;
Color specialColor = GameSpyColor[GSCOLOR_MAP_SELECTED];
Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
Color favoriteColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
Color localColor = 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 (info && info->index > 0 && info->validCustom)
{
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( TheGameSpyInfo->getLocalProfileID() );
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 (info && info->index > 0 && info->validCustom && usedLadders.find(info) == usedLadders.end())
{
usedLadders.insert(info);
index = GadgetListBoxAddEntryText( win, info->name, favoriteColor, -1 );
GadgetListBoxSetItemData( win, (void *)(info->index), index );
}
}
// local ladders
const LadderInfoList *lil = TheLadderList->getLocalLadders();
LadderInfoList::const_iterator lit;
for (lit = lil->begin(); lit != lil->end(); ++lit)
{
const LadderInfo *info = *lit;
if (info && info->index < 0 && info->validCustom && usedLadders.find(info) == usedLadders.end())
{
usedLadders.insert(info);
index = GadgetListBoxAddEntryText( win, info->name, localColor, -1 );
GadgetListBoxSetItemData( win, (void *)(info->index), index );
}
}
// special ladders
lil = TheLadderList->getSpecialLadders();
for (lit = lil->begin(); lit != lil->end(); ++lit)
{
const LadderInfo *info = *lit;
if (info && info->index > 0 && info->validCustom && 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 (info && info->index > 0 && info->validCustom && 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;
}
void PopulateCustomLadderComboBox( void )
{
if (!parentPopup || !comboBoxLadderName)
return;
isPopulatingLadderBox = true;
CustomMatchPreferences pref;
AsciiString userPrefFilename;
Int localProfile = TheGameSpyInfo->getLocalProfileID();
userPrefFilename.format("GeneralsOnline\\CustomPref%d.ini", localProfile);
pref.load(userPrefFilename);
std::set usedLadders;
Color specialColor = GameSpyColor[GSCOLOR_MAP_SELECTED];
Color normalColor = GameSpyColor[GSCOLOR_MAP_UNSELECTED];
Int index;
GadgetComboBoxReset( comboBoxLadderName );
index = GadgetComboBoxAddEntry( comboBoxLadderName, TheGameText->fetch("GUI:NoLadder"), normalColor );
GadgetComboBoxSetItemData( comboBoxLadderName, index, 0 );
Int selectedPos = 0;
AsciiString lastLadderAddr = pref.getLastLadderAddr();
UnsignedShort lastLadderPort = pref.getLastLadderPort();
const LadderInfo *info = TheLadderList->findLadder( lastLadderAddr, lastLadderPort );
if (info && info->validCustom)
{
usedLadders.insert(info);
index = GadgetComboBoxAddEntry( comboBoxLadderName, info->name, specialColor );
GadgetComboBoxSetItemData( comboBoxLadderName, index, (void *)(info->index) );
selectedPos = index;
}
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 (info && info->validCustom && usedLadders.find(info) == usedLadders.end())
{
usedLadders.insert(info);
index = GadgetComboBoxAddEntry( comboBoxLadderName, info->name, normalColor );
GadgetComboBoxSetItemData( comboBoxLadderName, index, (void *)(info->index) );
}
}
index = GadgetComboBoxAddEntry( comboBoxLadderName, TheGameText->fetch("GUI:ChooseLadder"), normalColor );
GadgetComboBoxSetItemData( comboBoxLadderName, index, (void *)-1 );
GadgetComboBoxSetSelectedPos( comboBoxLadderName, selectedPos );
isPopulatingLadderBox = false;
}
// Window Functions -----------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/** Initialize the PopupHostGameInit menu */
//-------------------------------------------------------------------------------------------------
void PopupHostGameInit( WindowLayout *layout, void *userData )
{
parentPopupID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:ParentHostPopUp"));
parentPopup = TheWindowManager->winGetWindowFromId(NULL, parentPopupID);
textEntryGameNameID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:TextEntryGameName"));
textEntryGameName = TheWindowManager->winGetWindowFromId(parentPopup, textEntryGameNameID);
UnicodeString name;
name.translate(TheGameSpyInfo->getLocalName());
GadgetTextEntrySetText(textEntryGameName, name);
textEntryGameDescriptionID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:TextEntryGameDescription"));
textEntryGameDescription = TheWindowManager->winGetWindowFromId(parentPopup, textEntryGameDescriptionID);
GadgetTextEntrySetText(textEntryGameDescription, UnicodeString::TheEmptyString);
textEntryLadderPasswordID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:TextEntryLadderPassword"));
textEntryLadderPassword = TheWindowManager->winGetWindowFromId(parentPopup, textEntryLadderPasswordID);
GadgetTextEntrySetText(textEntryLadderPassword, UnicodeString::TheEmptyString);
textEntryGamePasswordID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:TextEntryGamePassword"));
textEntryGamePassword = TheWindowManager->winGetWindowFromId(parentPopup, textEntryGamePasswordID);
GadgetTextEntrySetText(textEntryGamePassword, UnicodeString::TheEmptyString);
buttonCreateGameID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:ButtonCreateGame"));
buttonCreateGame = TheWindowManager->winGetWindowFromId(parentPopup, buttonCreateGameID);
buttonCancelID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:ButtonCancel"));
buttonCancel = TheWindowManager->winGetWindowFromId(parentPopup, buttonCancelID);
checkBoxAllowObserversID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:CheckBoxAllowObservers"));
checkBoxAllowObservers = TheWindowManager->winGetWindowFromId(parentPopup, checkBoxAllowObserversID);
CustomMatchPreferences customPref;
// disabling observers for Multiplayer test
#ifndef _PLAYTEST
GadgetCheckBoxSetChecked(checkBoxAllowObservers, customPref.allowsObservers());
#else
if (checkBoxAllowObservers)
{
GadgetCheckBoxSetChecked(checkBoxAllowObservers, FALSE);
checkBoxAllowObservers->winEnable(FALSE);
}
#endif
comboBoxLadderNameID = TheNameKeyGenerator->nameToKey(AsciiString("PopupHostGame.wnd:ComboBoxLadderName"));
comboBoxLadderName = TheWindowManager->winGetWindowFromId(parentPopup, comboBoxLadderNameID);
if (comboBoxLadderName)
GadgetComboBoxReset(comboBoxLadderName);
PopulateCustomLadderComboBox();
TheWindowManager->winSetFocus( parentPopup );
TheWindowManager->winSetModal( parentPopup );
}
//-------------------------------------------------------------------------------------------------
/** PopupHostGameInput callback */
//-------------------------------------------------------------------------------------------------
WindowMsgHandledType PopupHostGameInput( 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 ) )
{
TheWindowManager->winSendSystemMsg( window, GBM_SELECTED,
(WindowMsgData)buttonCancel, buttonCancelID );
} // 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;
}
//-------------------------------------------------------------------------------------------------
/** PopupHostGameSystem callback */
//-------------------------------------------------------------------------------------------------
WindowMsgHandledType PopupHostGameSystem( GameWindow *window, UnsignedInt msg, WindowMsgData mData1, WindowMsgData mData2 )
{
switch( msg )
{
// --------------------------------------------------------------------------------------------
case GWM_CREATE:
{
break;
} // end create
//---------------------------------------------------------------------------------------------
case GWM_DESTROY:
{
parentPopup = NULL;
break;
} // end case
//----------------------------------------------------------------------------------------------
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;
break;
} // end input
//----------------------------------------------------------------------------------------------
case GEM_UPDATE_TEXT:
{
GameWindow *control = (GameWindow *)mData1;
Int controlID = control->winGetWindowId();
if ( controlID == textEntryGameNameID )
{
UnicodeString txtInput;
// grab the game's name
txtInput.set(GadgetTextEntryGetText( textEntryGameName ));
// Clean up the text (remove leading/trailing chars, etc)
const WideChar *c = txtInput.str();
while (c && (iswspace(*c)))
c++;
if (c)
txtInput = UnicodeString(c);
else
txtInput = UnicodeString::TheEmptyString;
// Put the whitespace-free version in the box
GadgetTextEntrySetText( textEntryGameName, txtInput );
}// if ( controlID == textEntryPlayerNameID )
break;
}//case GEM_UPDATE_TEXT:
//---------------------------------------------------------------------------------------------
case GCM_SELECTED:
{
GameWindow *control = (GameWindow *)mData1;
Int controlID = control->winGetWindowId();
Int pos = -1;
GadgetComboBoxGetSelectedPos(control, &pos);
if (controlID == comboBoxLadderNameID && !isPopulatingLadderBox)
{
if (pos >= 0)
{
Int ladderID = (Int)GadgetComboBoxGetItemData(control, pos);
if (ladderID < 0)
{
// "Choose a ladder" selected - open overlay
PopulateCustomLadderComboBox(); // this restores the non-"Choose a ladder" selection
GameSpyOpenOverlay( GSOVERLAY_LADDERSELECT );
}
}
}
break;
} // case GCM_SELECTED
//---------------------------------------------------------------------------------------------
case GBM_SELECTED:
{
GameWindow *control = (GameWindow *)mData1;
Int controlID = control->winGetWindowId();
if( controlID == buttonCancelID )
{
parentPopup = NULL;
GameSpyCloseOverlay(GSOVERLAY_GAMEOPTIONS);
SetLobbyAttemptHostJoin( FALSE );
}
else if( controlID == buttonCreateGameID)
{
UnicodeString name;
name = GadgetTextEntryGetText(textEntryGameName);
name.trim();
if(name.getLength() <= 0)
{
name.translate(TheGameSpyInfo->getLocalName());
GadgetTextEntrySetText(textEntryGameName, name);
}
createGame();
parentPopup = NULL;
GameSpyCloseOverlay(GSOVERLAY_GAMEOPTIONS);
}
break;
}
default:
return MSG_IGNORED;
} // end switch
return MSG_HANDLED;
}
//-----------------------------------------------------------------------------
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
void createGame( void )
{
TheGameSpyInfo->setCurrentGroupRoom(0);
PeerRequest req;
UnicodeString gameName = GadgetTextEntryGetText(textEntryGameName);
req.peerRequestType = PeerRequest::PEERREQUEST_CREATESTAGINGROOM;
req.text = gameName.str();
TheGameSpyGame->setGameName(gameName);
AsciiString passwd;
passwd.translate(GadgetTextEntryGetText(textEntryGamePassword));
req.password = passwd.str();
CustomMatchPreferences customPref;
Bool aO = GadgetCheckBoxIsChecked(checkBoxAllowObservers);
customPref.setAllowsObserver(aO);
customPref.write();
req.stagingRoomCreation.allowObservers = aO;
TheGameSpyGame->setAllowObservers(aO);
req.stagingRoomCreation.exeCRC = TheGlobalData->m_exeCRC;
req.stagingRoomCreation.iniCRC = TheGlobalData->m_iniCRC;
req.stagingRoomCreation.gameVersion = TheGameSpyInfo->getInternalIP();
req.stagingRoomCreation.restrictGameList = TheGameSpyConfig->restrictGamesToLobby();
Int ladderSelectPos = -1, ladderID = -1;
GadgetComboBoxGetSelectedPos(comboBoxLadderName, &ladderSelectPos);
req.ladderIP = "localhost";
req.stagingRoomCreation.ladPort = 0;
if (ladderSelectPos >= 0)
{
ladderID = (Int)GadgetComboBoxGetItemData(comboBoxLadderName, ladderSelectPos);
if (ladderID != 0)
{
// actual ladder
const LadderInfo *info = TheLadderList->findLadderByIndex(ladderID);
if (info)
{
req.ladderIP = info->address.str();
req.stagingRoomCreation.ladPort = info->port;
}
}
}
TheGameSpyGame->setLadderIP(req.ladderIP.c_str());
TheGameSpyGame->setLadderPort(req.stagingRoomCreation.ladPort);
req.hostPingStr = TheGameSpyInfo->getPingString().str();
TheGameSpyPeerMessageQueue->addRequest(req);
}