CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

WOLAPIOB.CPP (116890B)


      1 //
      2 // Copyright 2020 Electronic Arts Inc.
      3 //
      4 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is free 
      5 // software: you can redistribute it and/or modify it under the terms of 
      6 // the GNU General Public License as published by the Free Software Foundation, 
      7 // either version 3 of the License, or (at your option) any later version.
      8 
      9 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed 
     10 // in the hope that it will be useful, but with permitted additional restrictions 
     11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
     12 // distributed with this program. You should have received a copy of the 
     13 // GNU General Public License along with permitted additional restrictions 
     14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
     15 
     16 #ifdef WOLAPI_INTEGRATION
     17 
     18 //	WolapiOb.cpp - Implementation of class WolapiObject.
     19 //	ajw 07/10/98
     20 
     21 #include "WolapiOb.h"
     22 #include "RAWolapi.h"
     23 #include "WolStrng.h"
     24 #include "SEditDlg.h"
     25 #include "ToolTip.h"
     26 #include "Wol_GSup.h"
     27 
     28 #include "WolDebug.h"
     29 
     30 extern void WOL_PrintMessage( IconListClass& ILTarget, const char* szText, PlayerColorType iColorRemap = PCOLOR_NONE );
     31 extern void WOL_PrintMessage( IconListClass& ILTarget, const char* szText, RemapControlType* pColorRemap );
     32 void HostNameFromGameChannelName( char* szNameToSet, const char* szChannelName );
     33 
     34 bool WOL_Options_Dialog( WolapiObject* pWO, bool bCalledFromGame );
     35 
     36 extern bool cancel_current_msgbox;
     37 
     38 const char* Game_Registry_Key();
     39 
     40 //***********************************************************************************************
     41 WolapiObject::WolapiObject() : pChat( NULL ), pDownload( NULL ), pChatSink( NULL ), pDownloadSink( NULL ),
     42 								dwChatAdvise( 0 ), dwDownloadAdvise( 0 ), pILChat( NULL ), pILUsers( NULL ),
     43 								CurrentLevel( WOL_LEVEL_TOP ), bChannelOwner( false ), GameTypeInfos( NULL ),
     44 								nGameTypeInfos( 0 ), bChatShownBefore( false ),
     45 								pILPlayers( NULL ), pChatSaveList( NULL ), iLobbyReturnAfterGame( -1 ), iLobbyLast( -1 ),
     46 								bFindEnabled( true ),
     47 								bPageEnabled( true ),
     48 								bLangFilter( true ),
     49 								bAllGamesShown( true ),
     50 								pGSupDlg( NULL ),
     51 								pShpDiscon( NULL ),
     52 								pShpLeave( NULL ),
     53 								pShpRefresh( NULL ),
     54 								pShpSquelch( NULL ),
     55 								pShpBan( NULL ),
     56 								pShpKick( NULL ),
     57 								pShpFindpage( NULL ),
     58 								pShpOptions( NULL ),
     59 								pShpLadder( NULL ),
     60 								pShpHelp( NULL ),
     61 								pShpBtnDiscon( NULL ),
     62 								pShpBtnLeave( NULL ),
     63 								pShpBtnRefresh( NULL ),
     64 								pShpBtnSquelch( NULL ),
     65 								pShpBtnBan( NULL ),
     66 								pShpBtnKick( NULL ),
     67 								pShpBtnFindpage( NULL ),
     68 								pShpBtnOptions( NULL ),
     69 								pShpBtnLadder( NULL ),
     70 								pShpBtnHelp( NULL ),
     71 								bReturningAfterGame( false ),
     72 								pTTipDiscon( NULL ),
     73 								pTTipLeave( NULL ),
     74 								pTTipRefresh( NULL ),
     75 								pTTipSquelch( NULL ),
     76 								pTTipBan( NULL ),
     77 								pTTipKick( NULL ),
     78 								pTTipFindpage( NULL ),
     79 								pTTipOptions( NULL ),
     80 								pTTipLadder( NULL ),
     81 								pTTipHelp( NULL ),
     82 								bMyRecordUpdated( false ),
     83 								bChannelListTitleUpdated( false ),
     84 								bInGame( false ),
     85 								pStaticUsers( NULL ),
     86 								bPump_In_Call_Back( false ),
     87 								bFreezeExternalPager( false ),
     88 								bDoingDisconnectPinging( false ),
     89 								bSelfDestruct( false ),
     90 								bEggSounds( true ),
     91 								bEgg8Player( false ),
     92 								bShowRankRA( true ),
     93 								bShowRankUpdated( false )
     94 {
     95 	*szMyName = 0;
     96 	*szMyRecord = 0;
     97 	*szMyRecordAM = 0;
     98 	*szChannelListTitle = 0;
     99 	*szChannelNameCurrent = 0;
    100 	*szChannelReturnOnGameEnterFail = 0;
    101 	dwTimeNextWolapiPump = ::timeGetTime() + WOLAPIPUMPWAIT;
    102 	dwTimeNextChannelUpdate = 0;
    103 	*szLadderServerHost = 0;
    104 	*szGameResServerHost1 = 0;
    105 	*szGameResServerHost2 = 0;
    106 
    107 	strcpy( DibIconInfos[ DIBICON_OWNER ].szFile, "dib_own.bmp" );
    108 	strcpy( DibIconInfos[ DIBICON_SQUELCH ].szFile, "dib_sqel.bmp" );
    109 	strcpy( DibIconInfos[ DIBICON_LATENCY ].szFile, "latency.bmp" );
    110 	strcpy( DibIconInfos[ DIBICON_ACCEPT ].szFile, "dib_acpt.bmp" );
    111 	strcpy( DibIconInfos[ DIBICON_NOTACCEPT ].szFile, "dib_acp2.bmp" );
    112 	strcpy( DibIconInfos[ DIBICON_USER ].szFile, "dib_user.bmp" );
    113 	strcpy( DibIconInfos[ DIBICON_PRIVATE ].szFile, "privgame.bmp" );
    114 	strcpy( DibIconInfos[ DIBICON_TOURNAMENT ].szFile, "tourgame.bmp" );
    115 	strcpy( DibIconInfos[ DIBICON_VOICE ].szFile, "voice.bmp" );
    116 	for( int i = 0; i != NUMDIBICONS; i++ )
    117 	{
    118 		DibIconInfos[ i ].hDIB = 0;
    119 		DibIconInfos[ i ].pDIB = NULL;
    120 	}
    121 
    122 	//	Determine name of executable of user's web browser.
    123 	//	This seems to be the "correct" way to do this, but it's bloody stupid.
    124 	*szWebBrowser = 0;
    125 	char szFile[] = "\\name_unlikely_to_conflict_77.html";	//"\\it is really dumb for me to have to create this file.html";
    126 	HANDLE hFile = ::CreateFile( szFile, GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
    127 	if( hFile != INVALID_HANDLE_VALUE )
    128 	{
    129 		::CloseHandle( hFile );
    130 		HINSTANCE hExecutable = ::FindExecutable( szFile, "", szWebBrowser );
    131 //		if( (int)hExecutable <= 32 )
    132 //		{
    133 //			debugprint( "error %i getting browser\n", hExecutable );
    134 //		}
    135 //		else
    136 //			debugprint( "szWebBrowser is %s\n", szWebBrowser );
    137 		::DeleteFile( szFile );
    138 	}
    139 }
    140 
    141 //***********************************************************************************************
    142 WolapiObject::~WolapiObject()
    143 {
    144 	DeleteSavedChat();
    145 
    146 	if( nGameTypeInfos )
    147 	{
    148 		//	Delete DIBs that were created, and the wol_gametypeinfos themselves
    149 		for( unsigned int n = 0; n != nGameTypeInfos; n++ )
    150 		{
    151 			if( GameTypeInfos[ n ].hDIB )
    152 			{
    153 				GlobalUnlock( GameTypeInfos[ n ].hDIB );		//	Release pDIB.
    154 				DestroyDIB( GameTypeInfos[ n ].hDIB );			//	Destroy mem alloc'ed for dib bits data.
    155 			}
    156 		}
    157 		delete [] GameTypeInfos;
    158 		GameTypeInfos = NULL;
    159 	}
    160 
    161 	for( int i = 0; i != NUMDIBICONS; i++ )
    162 	{
    163 		if( DibIconInfos[ i ].pDIB )
    164 		{
    165 			GlobalUnlock( DibIconInfos[ i ].hDIB );
    166 			DestroyDIB( DibIconInfos[ i ].hDIB );
    167 		}
    168 	}
    169 
    170 	if( pChatSink )
    171 		UnsetupCOMStuff();
    172 
    173 	//	Delete buttons, etc., shared by dialogs.
    174 	delete pShpBtnDiscon;
    175 	delete pShpBtnLeave;
    176 	delete pShpBtnRefresh;
    177 	delete pShpBtnSquelch;
    178 	delete pShpBtnBan;
    179 	delete pShpBtnKick;
    180 	delete pShpBtnFindpage;
    181 	delete pShpBtnOptions;
    182 	delete pShpBtnLadder;
    183 	delete pShpBtnHelp;
    184 	//	Delete shared tooltips.
    185 	delete pTTipDiscon;
    186 	delete pTTipLeave;
    187 	delete pTTipRefresh;
    188 	delete pTTipSquelch;
    189 	delete pTTipBan;
    190 	delete pTTipKick;
    191 	delete pTTipFindpage;
    192 	delete pTTipOptions;
    193 	delete pTTipLadder;
    194 	delete pTTipHelp;
    195 }
    196 
    197 //***********************************************************************************************
    198 bool WolapiObject::bSetupCOMStuff()
    199 {
    200 //	debugprint( "++++Begin WolapiObject::bSetupCOMStuff\n" );
    201 
    202 	HRESULT	hRes;
    203 
    204 	//	Grab IChat, INetUtil, set up "sinks".
    205 //	debugprint( "CoCreateInstance\n" );
    206 	CoCreateInstance( CLSID_Chat, NULL, CLSCTX_INPROC_SERVER, IID_IChat, (void**)&pChat );
    207 	if( !pChat )
    208 		return false;		//	Severe, essentially fatal.
    209 	CoCreateInstance( CLSID_NetUtil, NULL, CLSCTX_INPROC_SERVER, IID_INetUtil, (void**)&pNetUtil );
    210 	if( !pNetUtil )
    211 		return false;		//	Severe, essentially fatal.
    212 
    213 	//	Set up RAChatEventSink.
    214 	pChatSink = new RAChatEventSink( this );
    215 	pChatSink->AddRef();
    216 	//	Set up RANetUtilEventSink.
    217 	pNetUtilSink = new RANetUtilEventSink( this );
    218 	pNetUtilSink->AddRef();
    219 
    220 	//	If we could use ATL stuff, this would be different. (We'd use AtlAdvise.)
    221 
    222 	IConnectionPoint*			pConnectionPoint = NULL;
    223 	IConnectionPointContainer*	pContainer = NULL;
    224 	
    225 	// Get a connection point from the chat class for the chatsink.
    226 //	debugprint( "QueryInterface\n" );
    227 	hRes = pChat->QueryInterface( IID_IConnectionPointContainer, (void**)&pContainer );
    228 	if( !SUCCEEDED(hRes) )
    229 		return false;		//	Severe, essentially fatal.
    230 //	debugprint( "FindConnectionPoint\n" );
    231 	hRes = pContainer->FindConnectionPoint( IID_IChatEvent, &pConnectionPoint );
    232 	if( !SUCCEEDED(hRes) )
    233 		return false;		//	Severe, essentially fatal.
    234 	//	Connect chat to chatsink.
    235 //	debugprint( "Advise. pChatSink = %i, pConnectionPoint = %i\n", pChatSink, pConnectionPoint );
    236 	hRes = pConnectionPoint->Advise( (IChatEvent*)pChatSink, &dwChatAdvise );
    237 	if( !SUCCEEDED(hRes) )
    238 		return false;		//	Severe, essentially fatal.
    239 	
    240 	pContainer->Release();
    241 	pConnectionPoint->Release();
    242 
    243 	pConnectionPoint = NULL;
    244 	pContainer = NULL;
    245 	// Get a connection point from the netutil class for the netutilsink.
    246 //	debugprint( "QueryInterface\n" );
    247 	hRes = pNetUtil->QueryInterface( IID_IConnectionPointContainer, (void**)&pContainer );
    248 	if( !SUCCEEDED(hRes) )
    249 		return false;		//	Severe, essentially fatal.
    250 //	debugprint( "FindConnectionPoint\n" );
    251 	hRes = pContainer->FindConnectionPoint( IID_INetUtilEvent, &pConnectionPoint );
    252 	if( !SUCCEEDED(hRes) )
    253 		return false;		//	Severe, essentially fatal.
    254 	//	Connect netutil to netutilsink.
    255 //	debugprint( "Advise. pChatSink = %i, pConnectionPoint = %i\n", pChatSink, pConnectionPoint );
    256 	hRes = pConnectionPoint->Advise( (INetUtilEvent*)pNetUtilSink, &dwNetUtilAdvise );
    257 	if( !SUCCEEDED(hRes) )
    258 		return false;		//	Severe, essentially fatal.
    259 
    260 	pContainer->Release();
    261 	pConnectionPoint->Release();
    262 
    263 //	debugprint( "++++End WolapiObject::bSetupCOMStuff\n" );
    264 	return true;
    265 }
    266 
    267 //***********************************************************************************************
    268 void WolapiObject::UnsetupCOMStuff()
    269 {
    270 //	debugprint( "----Begin WolapiObject::UnsetupCOMStuff\n" );
    271 
    272 	HRESULT	hRes;
    273 
    274 	//	If we could use ATL stuff, this would be different. (We'd use AtlUnadvise.)
    275 
    276 	//	Unsetup RAChatEventSink and RANetUtilEventSink, release IChat.
    277 	IConnectionPoint*			pConnectionPoint = NULL;
    278 	IConnectionPointContainer*	pContainer = NULL;
    279 
    280 //	debugprint( "QueryInterface\n" );
    281 	hRes = pChat->QueryInterface( IID_IConnectionPointContainer, (void**)&pContainer );
    282 	_ASSERTE(SUCCEEDED(hRes));
    283 //	debugprint( "FindConnectionPoint\n" );
    284 	hRes = pContainer->FindConnectionPoint( IID_IChatEvent, &pConnectionPoint );
    285 	_ASSERTE(SUCCEEDED(hRes));
    286 //	debugprint( "Unadvise: %i\n", dwChatAdvise );
    287 	pConnectionPoint->Unadvise( dwChatAdvise );
    288 
    289 	pContainer->Release();
    290 	pConnectionPoint->Release();
    291 
    292 	pConnectionPoint = NULL;
    293 	pContainer = NULL;
    294 //	debugprint( "QueryInterface\n" );
    295 	hRes = pNetUtil->QueryInterface( IID_IConnectionPointContainer, (void**)&pContainer );
    296 	_ASSERTE(SUCCEEDED(hRes));
    297 //	debugprint( "FindConnectionPoint\n" );
    298 	hRes = pContainer->FindConnectionPoint( IID_INetUtilEvent, &pConnectionPoint );
    299 	_ASSERTE(SUCCEEDED(hRes));
    300 //	debugprint( "Unadvise: %i\n", dwNetUtilAdvise );
    301 	pConnectionPoint->Unadvise( dwNetUtilAdvise );
    302 
    303 	pContainer->Release();
    304 	pConnectionPoint->Release();
    305 
    306 //	debugprint( "pChat->Release\n" );
    307 	pChat->Release();
    308 
    309 //	debugprint( "pChatSink->Release\n" );
    310 	pChatSink->Release();	//	This results in pChatSink deleting itself for us.
    311 	pChatSink = NULL;
    312 
    313 //	debugprint( "pNetUtil->Release\n" );
    314 	pNetUtil->Release();
    315 
    316 //	debugprint( "pNetUtilSink->Release\n" );
    317 	pNetUtilSink->Release();	//	This results in pChatSink deleting itself for us.
    318 	pNetUtilSink = NULL;
    319 
    320 //	debugprint( "----End WolapiObject::UnsetupCOMStuff\n" );
    321 }
    322 
    323 //***********************************************************************************************
    324 void WolapiObject::LinkToChatDlg( IconListClass* pILChat, IconListClass* pILChannels, IconListClass* pILUsers, StaticButtonClass* pStaticUsers )
    325 {
    326 	//	Called to initialize this before the chat dialog is shown.
    327 
    328 	//	Set pointers to lists in dialog.
    329 	this->pILChat = pILChat;
    330 	this->pILChannels = pILChannels;
    331 	this->pILUsers = pILUsers;
    332 
    333 	this->pStaticUsers = pStaticUsers;
    334 }
    335 
    336 //***********************************************************************************************
    337 void WolapiObject::ClearListPtrs()
    338 {
    339 	//	Called to clear list pointers when chat or gamesetup dialog goes away, for safety.
    340 	pILChat = NULL;
    341 	pILChannels = NULL;
    342 	pILUsers = NULL;
    343 	
    344 	pILPlayers = NULL;
    345 
    346 	pStaticUsers = NULL;
    347 }
    348 
    349 //***********************************************************************************************
    350 void WolapiObject::LinkToGameDlg( IconListClass* pILDisc, IconListClass* pILPlayers )
    351 {
    352 	//	Called to initialize this before the gamesetup dialog is shown.
    353 
    354 	//	Set pointers to lists in dialog.
    355 	pILChat = pILDisc;
    356 	this->pILPlayers = pILPlayers;
    357 }
    358 
    359 //***********************************************************************************************
    360 void WolapiObject::PrepareButtonsAndIcons()
    361 {
    362 	//	Load shapes for buttons. Store images in this order: up, down, disabled.
    363 	//pShpDiscon = LoadShpFile( "discon.shp" ); etc
    364 	pShpDiscon = (char*)MFCD::Retrieve( "discon.shp" );
    365 	pShpLeave = (char*)MFCD::Retrieve( "leave.shp" );
    366 	pShpRefresh = (char*)MFCD::Retrieve( "refresh.shp" );
    367 	pShpSquelch = (char*)MFCD::Retrieve( "squelch.shp" );
    368 	pShpBan = (char*)MFCD::Retrieve( "ban.shp" );
    369 	pShpKick = (char*)MFCD::Retrieve( "kick.shp" );
    370 	pShpFindpage = (char*)MFCD::Retrieve( "findpage.shp" );
    371 	pShpOptions = (char*)MFCD::Retrieve( "ops.shp" );
    372 	pShpLadder = (char*)MFCD::Retrieve( "ladder.shp" );
    373 	pShpHelp = (char*)MFCD::Retrieve( "help.shp" );
    374 
    375 	//	Set up standard wol buttons, used by both main dialogs. Note hardcoded ID values: must match values in dialog.
    376 	int iWolButtons_x = 34;
    377 	int iWolButtons_y = 20;
    378 	int iWolButtons_dx = 53;
    379 	int xWolButton = iWolButtons_x;
    380 	int xTTip = 10;			//	Offset for tooltip.
    381 	int yTTip = - 5;		//	Offset for tooltip.
    382 	pShpBtnDiscon = new ShapeButtonClass( 100, pShpDiscon, xWolButton, iWolButtons_y );
    383 	pTTipDiscon = new ToolTipClass( pShpBtnDiscon, TXT_WOL_TTIP_DISCON, xWolButton + xTTip, iWolButtons_y + yTTip );
    384 	xWolButton += iWolButtons_dx;
    385 	pShpBtnLeave = new ShapeButtonClass( 101, pShpLeave, xWolButton, iWolButtons_y );
    386 	pTTipLeave = new ToolTipClass( pShpBtnLeave, TXT_WOL_TTIP_LEAVE, xWolButton + xTTip, iWolButtons_y + yTTip );
    387 	xWolButton += iWolButtons_dx;
    388 	pShpBtnRefresh = new ShapeButtonClass( 102, pShpRefresh, xWolButton, iWolButtons_y );
    389 	pTTipRefresh = new ToolTipClass( pShpBtnRefresh, TXT_WOL_TTIP_REFRESH, xWolButton + xTTip, iWolButtons_y + yTTip );
    390 	xWolButton += iWolButtons_dx;
    391 	pShpBtnSquelch = new ShapeButtonClass( 103, pShpSquelch, xWolButton, iWolButtons_y );
    392 	pTTipSquelch = new ToolTipClass( pShpBtnSquelch, TXT_WOL_TTIP_SQUELCH, xWolButton + xTTip, iWolButtons_y + yTTip );
    393 	xWolButton += iWolButtons_dx;
    394 	pShpBtnBan = new ShapeButtonClass( 104, pShpBan, xWolButton, iWolButtons_y );
    395 	pTTipBan = new ToolTipClass( pShpBtnBan, TXT_WOL_TTIP_BAN, xWolButton + xTTip, iWolButtons_y + yTTip );
    396 	xWolButton += iWolButtons_dx;
    397 	pShpBtnKick = new ShapeButtonClass( 105, pShpKick, xWolButton, iWolButtons_y );
    398 	pTTipKick = new ToolTipClass( pShpBtnKick, TXT_WOL_TTIP_KICK, xWolButton + xTTip, iWolButtons_y + yTTip );
    399 	xWolButton += iWolButtons_dx;
    400 	pShpBtnFindpage = new ShapeButtonClass( 106, pShpFindpage, xWolButton, iWolButtons_y );
    401 	pTTipFindpage = new ToolTipClass( pShpBtnFindpage, TXT_WOL_TTIP_FINDPAGE, xWolButton + xTTip, iWolButtons_y + yTTip );
    402 	xWolButton = 452;
    403 	pShpBtnOptions = new ShapeButtonClass( 107, pShpOptions, xWolButton, iWolButtons_y );
    404 	pTTipOptions = new ToolTipClass( pShpBtnOptions, TXT_WOL_TTIP_OPTIONS, xWolButton + xTTip, iWolButtons_y + yTTip, true );
    405 	xWolButton += iWolButtons_dx;
    406 	pShpBtnLadder = new ShapeButtonClass( 108, pShpLadder, xWolButton, iWolButtons_y );
    407 	pTTipLadder = new ToolTipClass( pShpBtnLadder, TXT_WOL_TTIP_LADDER, xWolButton + xTTip, iWolButtons_y + yTTip, true );
    408 	xWolButton += iWolButtons_dx;
    409 	pShpBtnHelp = new ShapeButtonClass( 109, pShpHelp, xWolButton, iWolButtons_y );
    410 	pTTipHelp = new ToolTipClass( pShpBtnHelp, TXT_WOL_TTIP_HELP, xWolButton + xTTip, iWolButtons_y + yTTip, true );
    411 
    412 	//	Load standard hard-coded icons.
    413 	HPALETTE hPal = GetCurrentScreenPalette();
    414 
    415 	int iFileLength;
    416 	const char* pFileData;
    417 	for( int iDibIcon = 0; iDibIcon != NUMDIBICONS; iDibIcon++ )
    418 	{
    419 		//pFileData = LoadFileIntoMemory( DibIconInfos[ iDibIcon ].szFile, iFileLength );
    420 		pFileData = (char*)MFCD::Retrieve( DibIconInfos[ iDibIcon ].szFile );
    421 		if( pFileData )
    422 		{
    423 			CCFileClass ccfileDib( DibIconInfos[ iDibIcon ].szFile );
    424 			iFileLength = ccfileDib.Size();
    425 			//debugprint( "Loaded %s, size is %i.\n", DibIconInfos[ iDibIcon ].szFile, iFileLength );
    426 			DibIconInfos[ iDibIcon ].hDIB = LoadDIB_FromMemory( (unsigned char*)pFileData, iFileLength );
    427 			if( DibIconInfos[ iDibIcon ].hDIB )
    428 			{
    429 				DibIconInfos[ iDibIcon ].pDIB = (char*)GlobalLock( DibIconInfos[ iDibIcon ].hDIB );
    430 				RemapDIBToPalette( hPal, DibIconInfos[ iDibIcon ].pDIB );
    431 			}
    432 //			else
    433 //				debugprint( "LoadDIB_FromMemory failed!\n" );
    434 		}
    435 //		else
    436 //			debugprint( "Couldn't find %s in mix.\n", DibIconInfos[ iDibIcon ].szFile );
    437 	}
    438 
    439 	if( DibIconInfos[ DIBICON_LATENCY ].pDIB )
    440 		fLatencyToIconWidth = (float)DIBWidth( DibIconInfos[ DIBICON_LATENCY ].pDIB ) / 1000;
    441 	else
    442 		fLatencyToIconWidth = 0;
    443 
    444 	//	All of the following is for the list of game icons...
    445 
    446 	//	Load game icons from the wol api.
    447 	LPCSTR szSkus;
    448 	if( pChat->GetGametypeList( &szSkus ) == S_OK )
    449 	{
    450 		//	Make two copies of szSkus because strtok insists on messing with them.
    451 		char* szSkus1 = new char[ strlen( szSkus ) + 1 ];
    452 		char* szSkus2 = new char[ strlen( szSkus ) + 1 ];
    453 		strcpy( szSkus1, szSkus );
    454 		strcpy( szSkus2, szSkus );
    455 		//	Count commas.
    456 		char seps[] = ",";
    457 		char* token;
    458 		nGameTypeInfos = 0;
    459 		token = strtok( szSkus1, seps );
    460 		while( token != NULL )
    461 		{
    462 			nGameTypeInfos++;
    463 			token = strtok( NULL, seps );
    464 		}
    465 		//	There are actually 2 additional game types available in wolapi - 0 (ws icon) and -1 (wwonline icon).
    466 		nGameTypeInfos += 2;
    467 		//	Create structs to hold infos.
    468 //		debugprint( "Creating %i gametypeinfos\n", nGameTypeInfos );
    469 		GameTypeInfos = new WOL_GAMETYPEINFO[ nGameTypeInfos ];
    470 		int iMyIndex = 0;
    471 		token = strtok( szSkus2, seps );
    472 		while( token != NULL )
    473 		{
    474 			GetGameTypeInfo( atoi( token ), GameTypeInfos[ iMyIndex ], hPal );
    475 			token = strtok( NULL, seps );
    476 			iMyIndex++;
    477 		}
    478 		//	Get the two extra game type infos...
    479 		GetGameTypeInfo( -1, GameTypeInfos[ iMyIndex++ ], hPal );
    480 		GetGameTypeInfo( 0, GameTypeInfos[ iMyIndex++ ], hPal );
    481 	}
    482 //	else
    483 //		debugprint( "GetGametypeList() failed.\n" );
    484 
    485 	//	Load icons that we'll need to represent Red Alert GameKinds.
    486 	//	These are available in wolapi at their old sku number locations.
    487 	GetGameTypeInfo( 2, OldRAGameTypeInfos[ 0 ], hPal );	//	RA
    488 	GetGameTypeInfo( 3, OldRAGameTypeInfos[ 1 ], hPal );	//	CS
    489 	GetGameTypeInfo( 4, OldRAGameTypeInfos[ 2 ], hPal );	//	AM
    490 
    491 	if( hPal )
    492 		DeleteObject( hPal );
    493 }
    494 
    495 //***********************************************************************************************
    496 void WolapiObject::GetGameTypeInfo( int iGameType, WOL_GAMETYPEINFO& GameTypeInfo, HPALETTE hPal )
    497 {
    498 	unsigned char* pVirtualFile;
    499 	int	iFileLength;
    500 //	debugprint( "GetGametypeInfo, type %i\n", iGameType );
    501 	LPCSTR szName;
    502 	LPCSTR szURL;
    503 	pChat->GetGametypeInfo( iGameType, 12, &pVirtualFile, &iFileLength, &szName, &szURL );
    504 	GameTypeInfo.iGameType = iGameType;
    505 	if( szName )
    506 		strcpy( GameTypeInfo.szName, szName );
    507 	else
    508 		*GameTypeInfo.szName = 0;
    509 	if( szURL )
    510 		strcpy( GameTypeInfo.szURL, szURL );
    511 	else
    512 		*GameTypeInfo.szURL = 0;
    513 
    514 //	debugprint( "LoadDIB_FromMemory( %i, %i )\n", pVirtualFile, iFileLength );
    515 	//	Create a DIB by "loading" (as if it was a file) the bitmap data.
    516 	GameTypeInfo.hDIB = LoadDIB_FromMemory( pVirtualFile, iFileLength );
    517 //	debugprint( "hDIB is %i\n", GameTypeInfo.hDIB );
    518 	if( !GameTypeInfo.hDIB )
    519 	{
    520 		GameTypeInfo.pDIB = NULL;
    521 		return;			//	Load failed. Should not ever happen.
    522 	}
    523 	GameTypeInfo.pDIB = (const char*)GlobalLock( GameTypeInfo.hDIB );
    524 
    525 //	debugprint( "@@@@@ Created gametypeinfo #%i: name %s, pDIB = %i\n", iIndex, GameTypeInfo.szName, GameTypeInfo.pDIB );
    526 
    527 	LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)GameTypeInfo.pDIB;
    528 	if( lpbi->biBitCount != 8 )
    529 	{
    530 		//	Do not use this loaded bmp, as it's not 256 color.
    531 		GlobalUnlock( GameTypeInfo.hDIB );		//	Release pDIB.
    532 		GameTypeInfo.pDIB = NULL;
    533 		DestroyDIB( GameTypeInfo.hDIB );		//	Destroy mem alloc'ed for dib bits data.
    534 		GameTypeInfo.hDIB = 0;
    535 		return;
    536 	}
    537 
    538 	//	Remap colors...
    539 	RemapDIBToPalette( hPal, GameTypeInfo.pDIB );
    540 
    541 }
    542 
    543 //***********************************************************************************************
    544 void* WolapiObject::IconForGameType( int iGameType )
    545 {
    546 	//	Returns a GameTypeInfos entry by gametype, instead of our (potentially arbitrary) index.
    547 	//	Returns NULL if type not found in list, which will of course never happen...
    548 	for( int i = 0; i != nGameTypeInfos; i++ )
    549 	{
    550 		if( GameTypeInfos[i].iGameType == iGameType )
    551 			return (void*)GameTypeInfos[i].pDIB;
    552 	}
    553 	return NULL;
    554 }
    555 
    556 //***********************************************************************************************
    557 const char* WolapiObject::NameOfGameType( int iGameType ) const
    558 {
    559 	//	Returns the name of a sku by gametype, instead of our (potentially arbitrary) index.
    560 	//	Returns NULL if type not found in list, which will of course never happen...
    561 	for( int i = 0; i != nGameTypeInfos; i++ )
    562 	{
    563 		if( GameTypeInfos[i].iGameType == iGameType )
    564 			return GameTypeInfos[i].szName;
    565 	}
    566 	return NULL;
    567 }
    568 
    569 //***********************************************************************************************
    570 const char* WolapiObject::URLForGameType( int iGameType ) const
    571 {
    572 	//	Returns NULL if type not found in list, which will of course never happen...
    573 	for( int i = 0; i != nGameTypeInfos; i++ )
    574 	{
    575 		if( GameTypeInfos[i].iGameType == iGameType )
    576 			return GameTypeInfos[i].szURL;
    577 	}
    578 	return NULL;
    579 }
    580 
    581 //***********************************************************************************************
    582 void WolapiObject::PrintMessage( const char* szText, PlayerColorType iColorRemap /* = PCOLOR_NONE */ )
    583 {
    584 	if( pILChat )
    585 		WOL_PrintMessage( *pILChat, szText, iColorRemap );
    586 }
    587 
    588 //***********************************************************************************************
    589 void WolapiObject::PrintMessage( const char* szText, RemapControlType* pColorRemap )
    590 {
    591 	if( pILChat )
    592 		WOL_PrintMessage( *pILChat, szText, pColorRemap );
    593 }
    594 
    595 //***********************************************************************************************
    596 HRESULT WolapiObject::GetChatServer()
    597 {
    598 	//	Calls RequestServerList() to get a chat server ready for us to login to.
    599 	//	Returns S_OK and sets pChatSink->pServer if successful.
    600 	WWMessageBox().Process( TXT_WOL_CONNECTING, TXT_NONE );
    601 
    602 //if( !( ::GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) )			//	ajw - allow use of test servers
    603 //{
    604 
    605 	//	Request chat server address from server server.
    606 	pChatSink->bRequestServerListWait = true;
    607 //	debugprint( "Calling RequestServerList...\n" );
    608 	if( !SUCCEEDED( pChat->RequestServerList( GAME_SKU, GAME_VERSION, "unused", "unused", 5 ) ) )
    609 	{
    610 //		debugprint( "RequestServerList call failed\n" );
    611 		return E_FAIL;
    612 	}
    613 	DWORD dwTimeLimit = timeGetTime();		//	ajw My own extra timeout at one minute, in case wolapi chokes.
    614 //	debugprint( "Called RequestServerList...\n" );
    615 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    616 	::GetAsyncKeyState( VK_ESCAPE );	//	Set up for escape key checking.
    617 	bool bCancel = false;
    618 	hresPatchResults = 0;
    619 	while( pChatSink->bRequestServerListWait && timeGetTime() - dwTimeLimit < 60000 )
    620 	{
    621 		while( timeGetTime() < dwTimeNextPump )
    622 		{
    623 			Call_Back();
    624 			if( ::GetAsyncKeyState( VK_ESCAPE ) & 1 )
    625 			{
    626 				bCancel = true;
    627 				break;
    628 			}
    629 		}
    630 //		debugprint( "PumpMessages after RequestServerList...\n" );
    631 		pChat->PumpMessages();
    632 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    633 //        Sleep( PUMPSLEEPDURATION );	//	Can't do because we want to Call_Back()
    634 		//	If an "update list" of patches has been received, instead of a server list, this flag will have been set
    635 		//	for us describing the results. We'll either cancel log in or trigger game exit.
    636 		if( hresPatchResults )
    637 		{
    638 			pChatSink->bRequestServerListWait = false;
    639 			return hresPatchResults;
    640 		}
    641 	}
    642 //	debugprint( "RequestServerList wait finished\n" );
    643 	if( bCancel )
    644 	{
    645 		Keyboard->Clear();
    646 		return USERCANCELLED;
    647 	}
    648 	if( pChatSink->pServer )
    649 		return S_OK;
    650 	else
    651 		return E_FAIL;
    652 
    653 /*
    654 }
    655 else
    656 {
    657 	//	Test using local server on LAN.
    658 
    659 	//	Bypass RequestServerList, as it is unnecessary and may not be possible if serverserver can't be reached.
    660 	//	Set SKU manually because normally RequestServerList does this for you.
    661 	pChat->SetProductSKU( GAME_SKU );
    662 	if( pChatSink->pServer )
    663 		delete pChatSink->pServer;
    664 	pChatSink->pServer = new Server;
    665 	if( !( ::GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) )
    666 		strcpy( (char*)pChatSink->pServer->conndata, "TCP;irc.westwood.com;9000" );
    667 	else
    668 		//	Control key down as well.
    669 		strcpy( (char*)pChatSink->pServer->conndata, "TCP;10.2.20.28;4000" );
    670 	strcpy( (char*)pChatSink->pServer->connlabel, "IRC" );
    671 	strcpy( (char*)pChatSink->pServer->name, "Chat");
    672 	return S_OK;
    673 }
    674 */
    675 
    676 }
    677 
    678 //***********************************************************************************************
    679 HRESULT WolapiObject::AttemptLogin( const char* szName, const char* szPass, bool bPassIsMangled )
    680 {
    681 	//	If RequestConnection() succeeds, sets pChatSink->bConnected true and returns S_OK.
    682 	//	Else returns RequestConnection() error result.
    683 	WWMessageBox().Process( TXT_WOL_ATTEMPTLOGIN, TXT_NONE );
    684 
    685 //	debugprint( "~1\n" );
    686 	strcpy( (char*)pChatSink->pServer->login, szName );
    687 	strcpy( (char*)pChatSink->pServer->password, szPass );
    688 
    689 /*
    690 //	debugprint( "RequestConnection with:\n%s,%s,%s,%s,%s - %s\n",
    691 					pChatSink->pServer->name,
    692 					pChatSink->pServer->connlabel,
    693 					pChatSink->pServer->conndata,
    694 					pChatSink->pServer->login,
    695 					pChatSink->pServer->password,
    696 					bPassIsMangled ? "(mangled)" : "(unmangled)" );
    697 */
    698 	pChatSink->bRequestConnectionWait = true;
    699 	pChatSink->hresRequestConnectionError = 0;
    700 
    701 	if( !SUCCEEDED( pChat->RequestConnection( pChatSink->pServer, 15, !bPassIsMangled ) ) )
    702 	{
    703 //		debugprint( "RequestConnection call failed\n" );
    704 		return CHAT_E_CON_ERROR;
    705 	}
    706 
    707 	DWORD dwTimeStart = timeGetTime();
    708 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    709 	::GetAsyncKeyState( VK_ESCAPE );	//	Set up for escape key checking.
    710 	bool bCancel = false;
    711 	while( pChatSink->bRequestConnectionWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
    712 	{
    713 		while( timeGetTime() < dwTimeNextPump )
    714 		{
    715 			Call_Back();
    716 			if( ::GetAsyncKeyState( VK_ESCAPE ) & 1 )
    717 			{
    718 				bCancel = true;
    719 				break;
    720 			}
    721 		}
    722 		if( bCancel )
    723 			break;
    724 		pChat->PumpMessages();
    725 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    726 //        Sleep( PUMPSLEEPDURATION );	//	Can't do because we want to Call_Back()
    727 	}
    728 	if( bCancel )
    729 	{
    730 		Keyboard->Clear();
    731 		return USERCANCELLED;
    732 	}
    733 	if( pChatSink->bRequestConnectionWait )
    734 		return CHAT_E_CON_ERROR;
    735 
    736 	if( pChatSink->bConnected )
    737 	{
    738 		strcpy( szMyName, szName );
    739 		strcpy( szMyRecord, szName );
    740 		strcpy( szMyRecordAM, szName );
    741 		return S_OK;
    742 	}
    743 	else
    744 		return pChatSink->hresRequestConnectionError;
    745 }
    746 
    747 //***********************************************************************************************
    748 bool WolapiObject::bLoggedIn()
    749 {
    750 	return pChatSink->bConnected;
    751 }
    752 
    753 //***********************************************************************************************
    754 void WolapiObject::Logout()
    755 {
    756 	//	Requests logout from wolapi. Doesn't return any error values, as what we would do if it
    757 	//	failed - force the user to stay connected?
    758 
    759 	if( bSelfDestruct )
    760 		WWMessageBox().Process( TXT_WOL_ERRORLOGOUT, TXT_NONE );
    761 	else
    762 		WWMessageBox().Process( TXT_WOL_ATTEMPTLOGOUT, TXT_NONE );
    763 
    764 //	debugprint( "RequestLogout()\n" );
    765 
    766 	pChatSink->bRequestLogoutWait = true;
    767 
    768 	if( !SUCCEEDED( pChat->RequestLogout() ) )
    769 	{
    770 //		debugprint( "RequestLogout() call failed\n" );
    771 	}
    772 
    773 	DWORD dwTimePatience = timeGetTime();		//	After 5 seconds we run out of patience and bail.
    774 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    775 	while( pChatSink->bRequestLogoutWait && timeGetTime() - dwTimePatience < 5000 )
    776 	{
    777 		while( timeGetTime() < dwTimeNextPump )
    778 			Call_Back();
    779 		pChat->PumpMessages();
    780 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    781 	}
    782 
    783 	pChatSink->bConnected = false;
    784 	*szMyName = 0;
    785 
    786 	Sound_Effect( WOLSOUND_LOGOUT );
    787 }
    788 
    789 //***********************************************************************************************
    790 bool WolapiObject::UpdateChannels( int iChannelType, CHANNELFILTER ChannelFilter, bool bAutoping )
    791 {
    792 	//	This is now non-modal.
    793 	//	Sends off a request for a new channels list.
    794 
    795 //	//	Returns false upon total failure.
    796 //	WWMessageBox().Process( TXT_WOL_WAIT, TXT_NONE );
    797 
    798 //	pChatSink->bRequestChannelListWait = true;
    799 	pChatSink->ChannelFilter = ChannelFilter;
    800 
    801 //	debugprint( "RequestChannelList(), iChannelType = %i, filter = %i\n", iChannelType, ChannelFilter );
    802 	if( !SUCCEEDED( pChat->RequestChannelList( iChannelType, bAutoping ) ) )
    803 	{
    804 //		debugprint( "RequestChannelList() call failed\n" );
    805 		return false;
    806 	}
    807 /*
    808 	DWORD dwTimeStart = timeGetTime();
    809 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    810 	while( pChatSink->bRequestChannelListWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
    811 	{
    812 		while( timeGetTime() < dwTimeNextPump )
    813 			Call_Back();
    814 		pChat->PumpMessages();
    815 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
    816 	}
    817 
    818 	if( pChatSink->bRequestChannelListWait )
    819 		return false;
    820 */
    821 	LastUpdateChannelCallLevel = CurrentLevel;
    822 
    823 	return true;
    824 }
    825 
    826 //***********************************************************************************************
    827 void WolapiObject::OnChannelList()
    828 {
    829 	//	The chatsink calls this when its OnChannelList() is called, and it has remade its internal channel list.
    830 	//	The question here is: should we display the values now in the chatsink?
    831 	//	As UpdateChannels() is now non-modal, there is the danger that we have moved away from the place in
    832 	//	the channel heirarchy where we originally called RequestChannelList().
    833 	//	To help ensure we're getting this where we expect to get it, we check the value of CurrentLevel against
    834 	//	what it was when we called UpdateChannels().
    835 	if( CurrentLevel == LastUpdateChannelCallLevel )
    836 		ListChannels();
    837 }
    838 
    839 //***********************************************************************************************
    840 void WolapiObject::ListChannels()
    841 {
    842 	//	Show pChatSink's pChannelList in pILChannels.
    843 	//	The extra data ptr hidden in each list item will hold a void pointer to the channel described.
    844 //	debugprint( "ListChannels(), pChannelList = %i\n", pChatSink->pChannelList );
    845 
    846 	static WOL_LEVEL	LevelLastListed = WOL_LEVEL_INVALID;
    847 
    848 	int iListViewIndex = 0;
    849 
    850 	//	If redrawing the same list as before, preserve the view position.
    851 	if( LevelLastListed == CurrentLevel )
    852 		iListViewIndex = pILChannels->Get_View_Index();
    853 	else
    854 		LevelLastListed = CurrentLevel;
    855 
    856 	pILChannels->Clear();
    857 	switch( CurrentLevel )
    858 	{
    859 	case WOL_LEVEL_GAMESOFTYPE:
    860 		pILChannels->Add_Item( TXT_WOL_CHANNEL_BACK, CHANNELTYPE_GAMES, NULL, ICON_SHAPE, CHANNELTYPE_GAMES );
    861 		break;
    862 	case WOL_LEVEL_LOBBIES:
    863 		pILChannels->Add_Item( TXT_WOL_CHANNEL_BACK, CHANNELTYPE_GAMES, NULL, ICON_SHAPE, CHANNELTYPE_GAMES );
    864 		break;
    865 	case WOL_LEVEL_INLOBBY:
    866 		pILChannels->Add_Item( TXT_WOL_CHANNEL_BACK, CHANNELTYPE_LOBBIES, NULL, ICON_SHAPE, CHANNELTYPE_LOBBIES );
    867 		break;
    868 	default:
    869 		pILChannels->Add_Item( TXT_WOL_CHANNEL_TOP, CHANNELTYPE_TOP, NULL, ICON_SHAPE, CHANNELTYPE_TOP );
    870 		break;
    871 	}
    872 
    873 	Channel* pChannel = pChatSink->pChannelList;
    874 	while( pChannel )
    875 	{
    876 		if( pChannel->type == 0 )
    877 		{
    878 			//	Show chat channel.
    879 			char* pShow;
    880 			int iLobby = iChannelLobbyNumber( pChannel->name );
    881 			if( iLobby == - 1 )
    882 			{
    883 				//	Regular chat channel.
    884 				pShow = new char[ strlen( (char*)pChannel->name ) + 10 ];
    885 				sprintf( pShow, "%s\t%-3u", (char*)pChannel->name, pChannel->currentUsers );
    886 				char szHelp[ 200 ];
    887 				sprintf( szHelp, TXT_WOL_TTIP_CHANLIST_CHAT, (char*)pChannel->name, pChannel->currentUsers );
    888 				pILChannels->Add_Item( pShow, szHelp, (void*)DibIconInfos[ DIBICON_USER ].pDIB, ICON_DIB, CHANNELTYPE_CHATCHANNEL, (void*)pChannel );
    889 			}
    890 			else
    891 			{
    892 				//	Channel is a lobby.
    893 				char szLobbyName[ REASONABLELOBBYINTERPRETEDNAMELEN ];
    894 				InterpretLobbyNumber( szLobbyName, iLobby );
    895 				pShow = new char[ REASONABLELOBBYINTERPRETEDNAMELEN + 10 ];
    896 				sprintf( pShow, "%s\t%-3u", szLobbyName, pChannel->currentUsers );
    897 				char szHelp[ 200 ];
    898 				sprintf( szHelp, TXT_WOL_TTIP_CHANLIST_LOBBY, szLobbyName, pChannel->currentUsers );
    899 				pILChannels->Add_Item( pShow, szHelp, IconForGameType( -1 ), ICON_DIB, CHANNELTYPE_LOBBYCHANNEL, (void*)pChannel );
    900 //				debugprint( ":::::added pChannel %i, name %s, as %s\n", pChannel, pChannel->name, pShow );
    901 			}
    902 			delete [] pShow;
    903 		}
    904 		else
    905 		{
    906 			//	Show game channel.
    907 			char* pShow = new char[ strlen( (char*)pChannel->name ) + 10 ];
    908 			char szHelp[ 200 ];
    909 			void* pGameKindIcon;
    910 			if( pChannel->type == GAME_TYPE )
    911 			{
    912 				//	Get RedAlert GameKind.
    913 				CREATEGAMEINFO::GAMEKIND GameKind = (CREATEGAMEINFO::GAMEKIND)( pChannel->reserved & 0xFF000000 );
    914 				switch( GameKind )
    915 				{
    916 				case CREATEGAMEINFO::RAGAME:
    917 					pGameKindIcon = (void*)OldRAGameTypeInfos[ 0 ].pDIB;		//	Red Alert icon
    918 					sprintf( szHelp, TXT_WOL_TTIP_CHANLIST_RAGAME, TXT_WOL_TTIP_REDALERT, 
    919 								pChannel->currentUsers, pChannel->maxUsers );
    920 					break;
    921 				case CREATEGAMEINFO::CSGAME:
    922 					pGameKindIcon = (void*)OldRAGameTypeInfos[ 1 ].pDIB;		//	CS icon
    923 					sprintf( szHelp, TXT_WOL_TTIP_CHANLIST_RAGAME, TXT_WOL_TTIP_COUNTERSTRIKE, 
    924 								pChannel->currentUsers, pChannel->maxUsers );
    925 					break;
    926 				case CREATEGAMEINFO::AMGAME:
    927 					pGameKindIcon = (void*)OldRAGameTypeInfos[ 2 ].pDIB;		//	AM icon
    928 					sprintf( szHelp, TXT_WOL_TTIP_CHANLIST_RAGAME, TXT_WOL_TTIP_AFTERMATH, 
    929 								pChannel->currentUsers, pChannel->maxUsers );
    930 					break;
    931 				default:
    932 //					debugprint( "Illegal value for GameKind channel reserved field: %s\n", (char*)pChannel->name );
    933 					pGameKindIcon = NULL;
    934 					break;
    935 				}
    936 				sprintf( pShow, "%s\t%u/%u", (char*)pChannel->name, pChannel->currentUsers, pChannel->maxUsers );
    937 			}
    938 			else
    939 			{
    940 				pGameKindIcon = IconForGameType( pChannel->type );
    941 				sprintf( pShow, "%s\t%-2u", (char*)pChannel->name, pChannel->currentUsers );
    942 				sprintf( szHelp, TXT_WOL_TTIP_CHANLIST_GAME, NameOfGameType( pChannel->type ), 
    943 							pChannel->currentUsers );
    944 			}
    945 			void* pPrivateIcon = NULL;
    946 			if( pChannel->flags & CHAN_MODE_KEY )
    947 			{
    948 				//	Game is private.
    949 				pPrivateIcon = (void*)DibIconInfos[ DIBICON_PRIVATE ].pDIB;
    950 				strcat( szHelp, TXT_WOL_TTIP_PRIVATEGAME );
    951 			}
    952 
    953 			void* pTournamentIcon = NULL;
    954 			if( pChannel->tournament )
    955 			{
    956 				//	Game is tournament.
    957 				pTournamentIcon = (void*)DibIconInfos[ DIBICON_TOURNAMENT ].pDIB;
    958 				strcat( szHelp, TXT_WOL_TTIP_TOURNAMENTGAME );
    959 			}
    960 
    961 			int iLatencyUse = pChannel->latency;
    962 			if( iLatencyUse == -1 )
    963 				iLatencyUse = 0;
    964 
    965 			static int iLatencyBarX = 227 - DIBWidth( DibIconInfos[ DIBICON_LATENCY ].pDIB ) - 19;
    966 
    967 			pILChannels->Add_Item( pShow, szHelp, pGameKindIcon, ICON_DIB,
    968 									CHANNELTYPE_GAMECHANNEL, (void*)pChannel, NULL,
    969 									pPrivateIcon, ICON_DIB, pTournamentIcon, ICON_DIB,
    970 									(void*)DibIconInfos[ DIBICON_LATENCY ].pDIB, ICON_DIB, 
    971 									iLatencyBarX, 0, iLatencyUse * fLatencyToIconWidth );
    972 			delete [] pShow;
    973 		}
    974 		pChannel = pChannel->next;
    975 	}
    976 	if( iListViewIndex )
    977 		pILChannels->Set_View_Index( iListViewIndex );	//	Not perfect but should keep list pretty stable on updates.
    978 }
    979 
    980 //***********************************************************************************************
    981 HRESULT WolapiObject::ChannelJoin( const char* szChannelName, const char* szKey )
    982 {
    983 	//	Used for CHAT channels (or lobbies) only. Channel type is set to 0.
    984 	Channel ChannelTemp;
    985 	memset( &ChannelTemp, 0, sizeof( ChannelTemp ) );
    986 	strcpy( (char*)ChannelTemp.name, szChannelName );
    987 	strcpy( (char*)ChannelTemp.key, szKey );
    988 	return ChannelJoin( &ChannelTemp );
    989 }
    990 
    991 //***********************************************************************************************
    992 HRESULT WolapiObject::ChannelJoin( Channel* pChannelToJoin )
    993 {
    994 	//	Returns an HRESULT, the meaning of which is totally customized for my own uses.
    995 
    996 	WWMessageBox().Process( TXT_WOL_WAIT, TXT_NONE );
    997 
    998 	pChatSink->bRequestChannelJoinWait = true;
    999 	pChatSink->hresRequestJoinResult = 0;
   1000 
   1001 //	debugprint( "RequestChannelJoin(), %s\n", pChannelToJoin->name );
   1002 	HRESULT hRes = pChat->RequestChannelJoin( pChannelToJoin );
   1003 	if( !SUCCEEDED( hRes ) )
   1004 	{
   1005 //		debugprint( "RequestChannelJoin() call failed, result %i ", hRes );
   1006 		DebugChatDef( hRes );
   1007 		return E_FAIL;
   1008 	}
   1009 	pChatSink->bIgnoreChannelLists = true;		//	Turn off response to channel lists.
   1010 	DWORD dwTimeStart = timeGetTime();
   1011 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1012 	while( pChatSink->bRequestChannelJoinWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   1013 	{
   1014 		while( timeGetTime() < dwTimeNextPump )
   1015 			Call_Back();
   1016 		pChat->PumpMessages();
   1017 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1018 	}
   1019 	pChatSink->bIgnoreChannelLists = false;		//	Turn on response to channel lists.
   1020 
   1021 	if( pChatSink->bRequestChannelJoinWait )
   1022 		return CHAT_E_TIMEOUT;
   1023 
   1024 	switch( pChatSink->hresRequestJoinResult )
   1025 	{
   1026 	case CHAT_E_CHANNELDOESNOTEXIST:
   1027 	case CHAT_E_BADCHANNELPASSWORD:
   1028 	case CHAT_E_BANNED:
   1029 	case CHAT_E_CHANNELFULL:
   1030 		return pChatSink->hresRequestJoinResult;
   1031 	}
   1032 
   1033 	if( !pChatSink->bJoined )
   1034 		return E_FAIL;
   1035 
   1036 	return S_OK;
   1037 }
   1038 
   1039 //***********************************************************************************************
   1040 bool WolapiObject::ChannelLeave()
   1041 {
   1042 	//	Returns false upon total failure.
   1043 	WWMessageBox().Process( TXT_WOL_WAIT, TXT_NONE );
   1044 
   1045 	pChatSink->bRequestChannelLeaveWait = true;
   1046 	pChatSink->DeleteUserList();
   1047 	
   1048 //	debugprint( "RequestChannelLeave()\n" );
   1049 	if( !SUCCEEDED( pChat->RequestChannelLeave() ) )
   1050 	{
   1051 //		debugprint( "RequestChannelLeave() call failed\n" );
   1052 		return false;
   1053 	}
   1054 	pChatSink->bIgnoreChannelLists = true;		//	Turn off response to channel lists.
   1055 	DWORD dwTimeStart = timeGetTime();
   1056 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1057 	while( pChatSink->bRequestChannelLeaveWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   1058 	{
   1059 		while( timeGetTime() < dwTimeNextPump )
   1060 			Call_Back();
   1061 		pChat->PumpMessages();
   1062 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1063 	}
   1064 	pChatSink->bIgnoreChannelLists = false;
   1065 
   1066 	if( pChatSink->bRequestChannelLeaveWait || pChatSink->bJoined )
   1067 		return false;
   1068 
   1069 	return true;
   1070 }
   1071 
   1072 /*
   1073 //***********************************************************************************************
   1074 bool WolapiObject::UserList()
   1075 {
   1076 	//	Returns false upon total failure.
   1077 	WWMessageBox().Process( TXT_WOL_WAIT, TXT_NONE );
   1078 
   1079 	pChatSink->bRequestUserListWait = true;
   1080 
   1081 	//	I no longer request a user list, as this was being done only when entering a channel, and it turns out wolapi gives
   1082 	//	us a user list automatically when joining. This function is used as a blocker that waits until the user list has
   1083 	//	definitely arrived.
   1084 //	debugprint( "RequestUserList()\n" );
   1085 //	if( !SUCCEEDED( pChat->RequestUserList() ) )
   1086 //	{
   1087 //		debugprint( "RequestUserList() call failed\n" );
   1088 //		return false;
   1089 //	}
   1090 
   1091 	DWORD dwTimeStart = timeGetTime();
   1092 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1093 	while( pChatSink->bRequestUserListWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   1094 	{
   1095 		while( timeGetTime() < dwTimeNextPump )
   1096 			Call_Back();
   1097 		pChat->PumpMessages();
   1098 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1099 	}
   1100 
   1101 	if( pChatSink->bRequestUserListWait )
   1102 	{
   1103 		pChatSink->bRequestUserListWait = false;
   1104 		return false;
   1105 	}
   1106 
   1107 	return true;
   1108 }
   1109 */
   1110 
   1111 //***********************************************************************************************
   1112 //typedef char CHANNELUSERNAME[ WOL_NAME_LEN_MAX ];
   1113 struct CHANNELUSERINFO
   1114 {
   1115 	char				szName[ WOL_NAME_LEN_MAX ];
   1116 	bool				bFlagged;
   1117 	RemapControlType*	pColorRemap;
   1118 	HousesType			House;			//	Only used if game channel.
   1119 	bool				bAccept;		//	Only used if game channel.
   1120 	char				szExtra[ 50 ];	//	Only used if game channel.
   1121 };
   1122 
   1123 //***********************************************************************************************
   1124 bool WolapiObject::ListChannelUsers()
   1125 {
   1126 	//	Show pChatSink's pUserList in pILUsers or pILPlayers (depending on CurrentLevel), after clearing it.
   1127 	//	The extra data ptr hidden in each list item will hold a void pointer to the user described.
   1128 	//	For simplicity, I destroy the old list and write a new one, even though possibly only one item
   1129 	//	may have changed.
   1130 	//	In order for the multiselect flags in the list to persist, I record the names that are flagged
   1131 	//	before clearing the list, then reset them (if found) in the new list.
   1132 	//	This is inefficient, but should be fine in this non-time-critical situation.
   1133 	//	(I also save item color here, and save all items. Now it's really inefficient.)
   1134 	//	(Oh, boy. Now I've added the persistence of house info when this is a game channel...)
   1135 	//	The idea was to avoid duplication of player data, and not have any dependence on the integrity of the chatsink's
   1136 	//	players list. Now it is a case of it working "well enough" to not require time to elegantize it.
   1137 
   1138 	//	Extra bonus, if useful: returns true if an operator (channel owner) is found in the channel, false otherwise.
   1139 
   1140 	bool bChannelOwnerFound = false;
   1141 
   1142 	bool bInLobby;
   1143 	IconListClass* pListToUse;
   1144 	if( CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
   1145 	{
   1146 		bInLobby = false;
   1147 		pListToUse = pILPlayers;
   1148 	}
   1149 	else
   1150 	{
   1151 		bInLobby = ( iChannelLobbyNumber( (unsigned char*)szChannelNameCurrent ) != -1 );
   1152 		pListToUse = pILUsers;
   1153 	}
   1154 
   1155 	if( pListToUse &&				//	Fails in rare cases when list draw is triggered before it is fully set up.
   1156 		*szChannelNameCurrent )		//	No users to list if not in a channel.
   1157 	{
   1158 		//	If redrawing the same list as before, preserve the view position.
   1159 		static char szChannelLastListed[ WOL_CHANNAME_LEN_MAX ] = { 0 };
   1160 //debugprint( "szChannelLastListed '%s', szChannelNameCurrent '%s'\n", szChannelLastListed, szChannelNameCurrent );
   1161 		int iListViewIndex = 0;
   1162 		if( strcmp( szChannelLastListed, szChannelNameCurrent ) == 0 )
   1163 			iListViewIndex = pListToUse->Get_View_Index();
   1164 		else
   1165 			strcpy( szChannelLastListed, szChannelNameCurrent );
   1166 
   1167 //debugprint( "ListChannelUsers(), pUserList = %i\n", pChatSink->pUserList );
   1168 		//	Save users in current list.
   1169 		int iCount = pListToUse->Count();
   1170 		CHANNELUSERINFO* pUsersSaved = NULL;
   1171 		int iUsersSaved = 0;
   1172 		if( iCount )
   1173 		{
   1174 			pUsersSaved = new CHANNELUSERINFO[ iCount ];
   1175 			for( int i = 0; i != iCount; i++ )
   1176 			{
   1177 				PullPlayerName_Into_From( pUsersSaved[ iUsersSaved ].szName, pListToUse->Get_Item( i ) );
   1178 				pUsersSaved[ iUsersSaved ].bFlagged = pListToUse->bItemIsMultiSelected( i );
   1179 				pUsersSaved[ iUsersSaved ].pColorRemap = pListToUse->Get_Item_Color( i );
   1180 //				debugprint( "  Saving color of %s as %i.\n", pUsersSaved[ iUsersSaved ].szName, pUsersSaved[ iUsersSaved ].pColorRemap );
   1181 				if( CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
   1182 				{
   1183 					pUsersSaved[ iUsersSaved ].House = PullPlayerHouse_From( pListToUse->Get_Item( i ) );
   1184 					pUsersSaved[ iUsersSaved ].bAccept = bItemMarkedAccepted( i );
   1185 					const char* szExtra = pListToUse->Get_Item_ExtraDataString( i );
   1186 					if( szExtra )
   1187 						strcpy( pUsersSaved[ iUsersSaved ].szExtra, szExtra );
   1188 					else
   1189 						*pUsersSaved[ iUsersSaved ].szExtra = 0;
   1190 				}
   1191 				iUsersSaved++;
   1192 			}
   1193 		}
   1194 		//	Clear list and recreate with new users list.
   1195 		pListToUse->Clear();
   1196 		User* pUser = pChatSink->pUserList;
   1197 		int iUserCount = 0;
   1198 		while( pUser )
   1199 		{
   1200 			++iUserCount;
   1201 			void* pIcon1 = NULL;
   1202 			void* pIcon2 = NULL;
   1203 			if( pUser->flags & CHAT_USER_CHANNELOWNER )
   1204 			{
   1205 				pIcon1 = (void*)DibIconInfos[ DIBICON_OWNER ].pDIB;
   1206 				bChannelOwnerFound = true;
   1207 			}
   1208 			else
   1209 			{
   1210 				if( CurrentLevel == WOL_LEVEL_INGAMECHANNEL )				
   1211 					pIcon1 = (void*)DibIconInfos[ DIBICON_NOTACCEPT ].pDIB;
   1212 				else
   1213 				{
   1214 					if( pUser->flags & CHAT_USER_VOICE )
   1215 						pIcon1 = (void*)DibIconInfos[ DIBICON_VOICE ].pDIB;
   1216 					else
   1217 						pIcon1 = (void*)DibIconInfos[ DIBICON_USER ].pDIB;
   1218 				}
   1219 			}
   1220 			if( pUser->flags & CHAT_USER_SQUELCHED )
   1221 				pIcon2 = (void*)DibIconInfos[ DIBICON_SQUELCH ].pDIB;
   1222 
   1223 			if( CurrentLevel == WOL_LEVEL_INGAMECHANNEL || bInLobby )
   1224 			{				
   1225 				int iRank = pNetUtilSink->GetUserRank( (char*)pUser->name, bShowRankRA );
   1226 				char szNameToShow[ WOL_NAME_LEN_MAX + 40 ];
   1227 				if( iRank )
   1228 				{
   1229 //					debugprint("  Found %s has rank %u\n", (char*)pUser->name, iRank );
   1230 					sprintf( szNameToShow, TXT_WOL_USERRANK, (char*)pUser->name, iRank );
   1231 				}
   1232 				else
   1233 					strcpy( szNameToShow, (char*)pUser->name );
   1234 
   1235 				static int iLatencyBarX = 124*RESFACTOR - DIBWidth( DibIconInfos[ DIBICON_LATENCY ].pDIB ) - 5 - 16;
   1236 
   1237 				//	If we have had a chance to request pings to the player, there'll be some avg. results waiting for us.
   1238 				int iLatencyBarWidth = 0;
   1239 				int iLatency;
   1240 				if( CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
   1241 				{
   1242 					unsigned long UserIP = pChatSink->GetUserIP( (char*)pUser->name );
   1243 //					debugprint( "player %s ip address %i\n", szNameToShow, UserIP );
   1244 					if( UserIP && pNetUtil->GetAvgPing( UserIP, &iLatency ) == S_OK )
   1245 					{
   1246 //						debugprint( "player %s latency %i\n", szNameToShow, iLatency );
   1247 						if( iLatency == -1 )
   1248 							iLatency = 0;
   1249 						iLatencyBarWidth = iLatency * fLatencyToIconWidth;
   1250 					}
   1251 				}
   1252 				pListToUse->Add_Item( szNameToShow, NULL, pIcon1, ICON_DIB, NULL, (void*)pUser, NULL, pIcon2, ICON_DIB, NULL, ICON_DIB, 
   1253 						(void*)DibIconInfos[ DIBICON_LATENCY ].pDIB, ICON_DIB, iLatencyBarX, 2, iLatencyBarWidth );
   1254 			}
   1255 			else
   1256 				pListToUse->Add_Item( (char*)pUser->name, NULL, pIcon1, ICON_DIB, NULL, (void*)pUser, NULL, pIcon2, ICON_DIB );
   1257 
   1258 			pUser = pUser->next;
   1259 		}
   1260 		if( pStaticUsers )
   1261 		{
   1262 			//	Display number of users in channel.
   1263 			char szCount[100];
   1264 			sprintf( szCount, TXT_WOL_USERLIST, iUserCount );
   1265 			pStaticUsers->Set_Text( szCount );
   1266 		}
   1267 		//	Reset multiselectedness, color, and item text for a user. Slow.
   1268 		//	(What a bloody, bloody hack.)
   1269 		for( int iUser = 0; iUser != iUsersSaved; iUser++ )
   1270 		{
   1271 			int iFind = pListToUse->Find( pUsersSaved[ iUser ].szName );		//	Finds any item beginning with szName...
   1272 			if( iFind != -1 )
   1273 			{
   1274 				if( CurrentLevel == WOL_LEVEL_INGAMECHANNEL )
   1275 				{
   1276 					if( pUsersSaved[ iUser ].House != HOUSE_NONE )
   1277 					{
   1278 						//	Append house text to item string, as we found a valid house name after the name, above.
   1279 						char szItem[ 120 ];
   1280 						WritePlayerListItem( szItem, pUsersSaved[ iUser ].szName, pUsersSaved[ iUser ].House );
   1281 						pListToUse->Set_Item( iFind, szItem );
   1282 					}
   1283 					if( pUsersSaved[ iUser ].bAccept )
   1284 					{
   1285 						//	Player was marked "accepted" before. If he has one now, it's because he is the host.
   1286 						//	Else it was an accepted icon before, so put one in again now. (a-hacking-we-will-go)
   1287 						if( !bItemMarkedAccepted( iFind ) )
   1288 							MarkItemAccepted( iFind, true );
   1289 					}
   1290 					if( *pUsersSaved[ iUser ].szExtra )
   1291 						pListToUse->Set_Item_ExtraDataString( iFind, pUsersSaved[ iUser ].szExtra );
   1292 				}
   1293 				if( pUsersSaved[ iUser ].bFlagged )
   1294 					pListToUse->MultiSelect( iFind, true );
   1295 //				debugprint( "  Restoring color of %s as %i.\n", pUsersSaved[ iUser ].szName, pUsersSaved[ iUser ].pColorRemap );
   1296 				pListToUse->Set_Item_Color( iFind, pUsersSaved[ iUser ].pColorRemap );
   1297 			}
   1298 //			else
   1299 //				debugprint( "ListChannelUsers() - Couldn't find %s!\n", pUsersSaved[ iUser ].szName );
   1300 		}
   1301 		delete [] pUsersSaved;
   1302 		if( iListViewIndex )
   1303 			pListToUse->Set_View_Index( iListViewIndex );	//	Not perfect but should keep list pretty stable on updates.
   1304 	}
   1305 	return bChannelOwnerFound;
   1306 }
   1307 
   1308 //***********************************************************************************************
   1309 bool WolapiObject::bItemMarkedAccepted( int iIndex )
   1310 {
   1311 	//	Returns true if the iIndex'th entry in pILPlayers has an icon pointer in position 0 that
   1312 	//	is either the host icon or the accepted icon.
   1313 	const IconList_ItemExtras* pItemExtras = pILPlayers->Get_ItemExtras( iIndex );
   1314 	return ( pItemExtras->pIcon[0] == (void*)DibIconInfos[ DIBICON_OWNER ].pDIB || 
   1315 				pItemExtras->pIcon[0] == (void*)DibIconInfos[ DIBICON_ACCEPT ].pDIB );
   1316 }
   1317 
   1318 //***********************************************************************************************
   1319 bool WolapiObject::MarkItemAccepted( int iIndex, bool bAccept )
   1320 {
   1321 	pILPlayers->Flag_To_Redraw();
   1322 	if( bAccept )
   1323 		return pILPlayers->Set_Icon( iIndex, 0, (void*)DibIconInfos[ DIBICON_ACCEPT ].pDIB, ICON_DIB );
   1324 	else
   1325 		//return pILPlayers->Set_Icon( iIndex, 0, NULL, ICON_DIB );
   1326 		return pILPlayers->Set_Icon( iIndex, 0, (void*)DibIconInfos[ DIBICON_NOTACCEPT ].pDIB, ICON_DIB );		
   1327 }
   1328 
   1329 //***********************************************************************************************
   1330 bool WolapiObject::bItemMarkedReadyToGo( int iIndex )
   1331 {
   1332 	//	Returns true if the iIndex'th entry in pILPlayers marks the player as "ready to go".
   1333 	//	This is true if the player is marked as "ready" or "need scenario".
   1334 	const char* szItem = pILPlayers->Get_Item_ExtraDataString( iIndex );
   1335 	if( !szItem )
   1336 		return false;
   1337 //	debugprint( "szItem is %s\n", szItem );
   1338 	return ( strcmp( szItem, "ready" ) == 0 || strcmp( szItem, "need scenario" ) == 0 );
   1339 }
   1340 
   1341 //***********************************************************************************************
   1342 void WolapiObject::MarkItemReadyToGo( int iIndex, const char* szReadyState )
   1343 {
   1344 	//	Set szReadyState to "ready", "need scenario", or NULL.
   1345 	//	First two cases are regarded as player being ready to go.
   1346 	pILPlayers->Flag_To_Redraw();
   1347 	pILPlayers->Set_Item_ExtraDataString( iIndex, szReadyState );
   1348 }
   1349 
   1350 //***********************************************************************************************
   1351 bool WolapiObject::bItemMarkedNeedScenario( int iIndex )
   1352 {
   1353 	//	Returns true if the iIndex'th entry in pILPlayers marks the player as ready to go, but needing scenario download.
   1354 	const char* szItem = pILPlayers->Get_Item_ExtraDataString( iIndex );
   1355 	if( !szItem )
   1356 		return false;
   1357 	return ( strcmp( szItem, "need scenario" ) == 0 );
   1358 }
   1359 
   1360 //***********************************************************************************************
   1361 void WolapiObject::PullPlayerName_Into_From( char* szDest, const char* szSource )
   1362 {
   1363 	//	Sets szDest to the "player name" found in szSource.
   1364 	//	Called "player" name because this is mainly designed for use in game channels.
   1365 	//	Player name appears first in item, separated by a space from anything later.
   1366 
   1367 	char* pSpace = strstr( szSource, " " );
   1368 	if( !pSpace )
   1369 	{
   1370 		//	No space character. Use entire item.	
   1371 		strcpy( szDest, szSource );
   1372 	}
   1373 	else
   1374 	{
   1375 		int iSpacePosition = pSpace - szSource;
   1376 		strncpy( szDest, szSource, iSpacePosition );
   1377 		szDest[ iSpacePosition ] = 0;		//	terminate
   1378 	}
   1379 //	debugprint( "PullPlayerName_Into_From: '%s' from '%s', ok?\n", szDest, szSource );
   1380 }
   1381 
   1382 //***********************************************************************************************
   1383 HousesType WolapiObject::PullPlayerHouse_From( const char* szSource )
   1384 {
   1385 	//	Pulls the house value out of a player list item in a game channel.
   1386 	//	House appears as the last word, and it's in <>.
   1387 //	char* pChar = strrchr( szSource, ' ' );		//	Last space character.		was failing on roy. uni cause of space
   1388 //	if( !pChar )
   1389 //		return HOUSE_NONE;
   1390 //	++pChar;
   1391 //	if( *pChar++ != '<' )		//	We know house has to be last, so if not the case, no house in item.
   1392 //		return HOUSE_NONE;
   1393 	char* pChar = strrchr( szSource, '<' );		//	Last < character.
   1394 	if( !pChar )
   1395 		return HOUSE_NONE;
   1396 	++pChar;
   1397 	int iLen = strlen( pChar );	//	Remaining: "housename>"
   1398 	//	Copy remaining string.
   1399 	char szHouse[ 30 ];
   1400 	strcpy( szHouse, pChar );
   1401 	*( szHouse + iLen - 1 ) = 0;	//	Terminate to remove ">"
   1402 //	debugprint( "PullPlayerHouse_From: '%s' from '%s', ok?\n", szHouse, szSource );
   1403 	//	pChar is now a valid house name.
   1404 
   1405 //	return HouseTypeClass::From_Name( szHouse );
   1406 #ifdef ENGLISH
   1407 	//	Bloody bloody hell I can't believe there are bugs in RA like the one I deal with here...
   1408 	if( strcmp( szHouse, "Russia" ) == 0 )
   1409 		return HOUSE_USSR;
   1410 	else
   1411 		return HouseTypeClass::From_Name( szHouse );	//	Fails on "Russia". (Thinks "USSR".)
   1412 #else
   1413 	for( HousesType house = HOUSE_USSR; house <= HOUSE_FRANCE; house++ )
   1414 		if( strcmp( Text_String(HouseTypeClass::As_Reference(house).Full_Name()), szHouse ) == 0 )
   1415 			return house;
   1416 //	debugprint( "dohfus" );		//	should never happen
   1417 //	Fatal( "" );
   1418 	return HOUSE_USSR;
   1419 #endif
   1420 }
   1421 
   1422 //***********************************************************************************************
   1423 void WolapiObject::WritePlayerListItem( char* szDest, const char* szName, HousesType House )
   1424 {
   1425 	//	Sets szDest to the way a player list item appears in a game channel.
   1426 	char szHouse[ 50 ];
   1427 	strcpy( szHouse, Text_String( HouseTypeClass::As_Reference( House ).Full_Name() ) );
   1428 
   1429 	int iRank = pNetUtilSink->GetUserRank( szName, bShowRankRA );	//	Horrendous inefficiency here, when called for relisting players...
   1430 	if( iRank )
   1431 		sprintf( szDest, TXT_WOL_USERRANKHOUSE, szName, iRank, szHouse );
   1432 	else
   1433 		sprintf( szDest, TXT_WOL_USERHOUSE, szName, szHouse );
   1434 //	debugprint( "WritePlayerListItem: '%s', ok?\n", szDest );
   1435 }
   1436 
   1437 //***********************************************************************************************
   1438 void WolapiObject::RequestPlayerPings()
   1439 {
   1440 	//	Does a RequestPing for every other player listed in pILPlayers.
   1441 	for( int i = 0; i < pILPlayers->Count(); i++ )
   1442 	{
   1443 		User* pUser = (User*)pILPlayers->Get_Item_ExtraDataPtr( i );
   1444 		if( pUser && !( pUser->flags & CHAT_USER_MYSELF ) )
   1445 		{
   1446 			unsigned long UserIP = pChatSink->GetUserIP( (char*)pUser->name );
   1447 			if( UserIP )
   1448 			{
   1449 				int iUnused;
   1450 				in_addr inaddrUser;
   1451 				inaddrUser.s_addr = UserIP;
   1452 				char* szIP = inet_ntoa( inaddrUser );
   1453 //				debugprint( "RequestPing of %s, ipaddr of %i, aka %s\n", (char*)pUser->name, UserIP, szIP );
   1454 				pNetUtil->RequestPing( szIP, 1000, &iUnused );
   1455 			}
   1456 		}
   1457 	}
   1458 }
   1459 
   1460 //***********************************************************************************************
   1461 void WolapiObject::SendMessage( const char* szMessage, IconListClass& ILUsers, bool bAction )
   1462 {
   1463 	//	Send regular chat message.
   1464 
   1465 	if( *szMessage == 0 )
   1466 		return;
   1467 
   1468 	if( strlen( szMessage ) > 4 && szMessage[0] == 63 && szMessage[1] == 97 && szMessage[2] == 106 && szMessage[3] == 119 )
   1469 	{
   1470 		int i = atoi( szMessage + 4 );
   1471 		if( i >= VOX_ACCOMPLISHED && i <= VOX_LOAD1 )
   1472 			Speak( (VoxType)i );
   1473 		return;
   1474 	}
   1475 	if( strlen( szMessage ) > 4 && szMessage[0] == 35 && szMessage[1] == 97 && szMessage[2] == 106 && szMessage[3] == 119 )
   1476 	{
   1477 		int i = atoi( szMessage + 4 );
   1478 		if( i >= VOX_ACCOMPLISHED && i <= VOX_LOAD1 )
   1479 			Speak( (VoxType)i );
   1480 	}
   1481 
   1482 	//	Iterate through ILUsers looking for selected entries. Build up a users list of selected
   1483 	//	items. If the list turns out to be blank, send message publicly.
   1484 	User* pUserListSend = NULL;
   1485 	User* pUserNew;
   1486 	User* pUserTail = NULL;
   1487 	int iCount = ILUsers.Count();
   1488 	int iPrivatePrintLen = 1;
   1489 	for( int i = 0; i != iCount; i++ )
   1490 	{
   1491 		if( ILUsers.bItemIsMultiSelected( i ) )
   1492 		{
   1493 			pUserNew = new User;
   1494 			*pUserNew = *( (User*)ILUsers.Get_Item_ExtraDataPtr( i ) );
   1495 //			debugprint( "Copied %s for sendmessage.\n", pUserNew->name );
   1496 			pUserNew->next = NULL;			//	(We don't want the value that was just copied!)
   1497 			if( !pUserTail )
   1498 			{
   1499 				//	First User in list.
   1500 				pUserListSend = pUserNew;
   1501 			}
   1502 			else
   1503 			{
   1504 				pUserTail->next = pUserNew;
   1505 			}
   1506 			pUserTail = pUserNew;
   1507 			iPrivatePrintLen += ( strlen( (char*)pUserNew->name ) + 2 );		//	Extra space and comma.
   1508 		}
   1509 	}
   1510 	if( pUserListSend )
   1511 	{
   1512 		//	Send private message.
   1513 		if( !bAction )
   1514 			pChat->RequestPrivateMessage( pUserListSend, szMessage );
   1515 		else
   1516 			pChat->RequestPrivateAction( pUserListSend, szMessage );
   1517 		char* szPrint = 0;
   1518 		if( iPrivatePrintLen > 50 )
   1519 		{
   1520 			//	Too many users specified to print out. Just say "multiple users".
   1521 			if( !bAction )
   1522 			{
   1523 				szPrint = new char[ strlen( szMessage ) + 135 ];
   1524 				sprintf( szPrint, "%s %s", TXT_WOL_PRIVATETOMULTIPLE, szMessage );
   1525 			}
   1526 			else
   1527 			{
   1528 				szPrint = new char[ strlen( szMessage ) + strlen( szMyName ) + 138 ];
   1529 				sprintf( szPrint, "%s %s %s", TXT_WOL_PRIVATETOMULTIPLE, szMyName, szMessage );
   1530 			}
   1531 		}
   1532 		else
   1533 		{
   1534 			if( !bAction )
   1535 				szPrint = new char[ strlen( szMessage ) + iPrivatePrintLen + 120 ];
   1536 			else
   1537 				szPrint = new char[ strlen( szMessage ) + iPrivatePrintLen + 125 + strlen( szMyName ) ];
   1538 			//strcpy( szPrint, "<Private to " );
   1539 			sprintf( szPrint, "<%s ", TXT_WOL_PRIVATETO );
   1540 			User* pUserPrint = pUserListSend;
   1541 			while( pUserPrint )
   1542 			{
   1543 				strcat( szPrint, (char*)pUserPrint->name );
   1544 				if( pUserPrint->next )
   1545 					strcat( szPrint, ", " );
   1546 				else
   1547 					strcat( szPrint, ">: " );
   1548 				pUserPrint = pUserPrint->next;
   1549 			}
   1550 			if( bAction )
   1551 			{
   1552 				strcat( szPrint, szMyName );
   1553 				strcat( szPrint, " " );
   1554 			}
   1555 			strcat( szPrint, szMessage );
   1556 		}
   1557 		if( !bAction )
   1558 			PrintMessage( szPrint, WOLCOLORREMAP_SELFSPEAKING );
   1559 		else
   1560 		{
   1561 			PrintMessage( szPrint, WOLCOLORREMAP_ACTION );
   1562 			pChatSink->ActionEggSound( szMessage );
   1563 		}
   1564 		delete [] szPrint;
   1565 	}
   1566 	else
   1567 	{
   1568 		//	Send public message.
   1569 		if( !bAction )
   1570 		{
   1571 			//	Easter egg related.
   1572 			if( _stricmp( szMessage, "/nousersounds" ) == 0 )
   1573 			{
   1574 				bEggSounds = false;
   1575 				return;
   1576 			}
   1577 			else if( _stricmp( szMessage, "/usersounds" ) == 0 )		//	Left as obvious text in the exe, for someone to find... :-)
   1578 			{
   1579 				bEggSounds = true;
   1580 				return;
   1581 			}
   1582 			else if( _stricmp( szMessage, "/8playergames" ) == 0 )		//	Left as obvious text in the exe, for someone to find... :-)
   1583 			{
   1584 				bEgg8Player = true;
   1585 				return;
   1586 			}
   1587 			HRESULT hRes = pChat->RequestPublicMessage( szMessage );
   1588 			if( hRes != S_OK )
   1589 			{
   1590 //				debugprint( "                           RequestPublicMessage() failed with: " );
   1591 //				DebugChatDef( hRes );
   1592 			}
   1593 		}
   1594 		else
   1595 		{
   1596 			HRESULT hRes = pChat->RequestPublicAction( szMessage );
   1597 			if( hRes != S_OK )
   1598 			{
   1599 //				debugprint( "                           RequestPublicAction() failed with: " );
   1600 //				DebugChatDef( hRes );
   1601 			}
   1602 		}
   1603 		char* szPrint = new char[ strlen( szMessage ) + strlen( szMyName ) + 10 ];
   1604 		if( !bAction )
   1605 		{
   1606 			sprintf( szPrint, "%s: %s", szMyName, szMessage );
   1607 			PrintMessage( szPrint, WOLCOLORREMAP_SELFSPEAKING );
   1608 		}
   1609 		else
   1610 		{
   1611 			sprintf( szPrint, "%s %s", szMyName, szMessage );
   1612 			PrintMessage( szPrint, WOLCOLORREMAP_ACTION );
   1613 			pChatSink->ActionEggSound( szMessage );
   1614 		}
   1615 		delete [] szPrint;
   1616 	}
   1617 }
   1618 
   1619 //***********************************************************************************************
   1620 bool WolapiObject::ChannelCreate( const char* szChannelName, const char* szKey, bool bGame /* = false */, 
   1621 									int iMaxPlayers /* = 0 */, bool bTournament /* = false */, int iLobby /* = 0 */,
   1622 									CREATEGAMEINFO::GAMEKIND GameKind /* = red alert */ )
   1623 {
   1624 	//	Create a channel.
   1625 	//	szKey is NULL if a public channel is to be created, else channel password.
   1626 
   1627 	//	Returns true if everything goes okay.
   1628 
   1629 	if( pChatSink->bJoined )
   1630 	{
   1631 		//	This never happens. Here just in case.
   1632 //		debugprint( "WolapiObject::ChannelCreate called when bJoined is true!\n" );
   1633 		return false;
   1634 //		Fatal( "WolapiObject::ChannelCreate called when bJoined is true!" );
   1635 	}
   1636 	
   1637 	Channel ChannelNew;
   1638 
   1639 	//	Prepare the struct.
   1640 	memset( &ChannelNew, 0, sizeof( ChannelNew ) );
   1641 	
   1642 	if( !bGame )
   1643 	{
   1644 		//	ChannelNew.type = 0;	0 for chat channel.
   1645 		strcpy( (char*)ChannelNew.name, szChannelName );
   1646 	}
   1647 	else
   1648 	{
   1649 		ChannelNew.type = GAME_TYPE;
   1650 		ChannelNew.maxUsers = iMaxPlayers;
   1651 		ChannelNew.tournament = bTournament;
   1652 		//	Channel 'reserved' stores GameKind in the highest byte, and
   1653 		//	lobby number to return to in the lower three bytes.
   1654 		//	Note: If lobby number is -1 (no lobby to return to), it's encoded as 0x00FFFFFF
   1655 		ChannelNew.reserved = ( iLobby & 0x00FFFFFF ) | GameKind;
   1656 		strcpy( (char*)ChannelNew.name, szChannelName );
   1657 	}
   1658 
   1659 //	debugprint( "RequestChannelCreate(), channel name: '%s'\n", szChannelName );
   1660 
   1661 	if( szKey )
   1662 		strcpy( (char*)ChannelNew.key, szKey );
   1663 
   1664 	WWMessageBox().Process( TXT_WOL_WAIT, TXT_NONE );
   1665 
   1666 	pChatSink->bRequestChannelCreateWait = true;
   1667 	
   1668 	HRESULT hRes = pChat->RequestChannelCreate( &ChannelNew );
   1669 	if( !SUCCEEDED( hRes ) )
   1670 	{
   1671 //		debugprint( "RequestChannelCreate() call failed:" );
   1672 		DebugChatDef( hRes );
   1673 		return false;
   1674 	}
   1675 	pChatSink->bIgnoreChannelLists = true;		//	Turn off response to channel lists.
   1676 	DWORD dwTimeStart = timeGetTime();
   1677 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1678 	while( pChatSink->bRequestChannelCreateWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   1679 	{
   1680 		while( timeGetTime() < dwTimeNextPump )
   1681 			Call_Back();
   1682 		pChat->PumpMessages();
   1683 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1684 	}
   1685 	pChatSink->bIgnoreChannelLists = false;
   1686 
   1687 	if( pChatSink->bRequestChannelCreateWait || !pChatSink->bJoined )
   1688 		return false;		//	Timed out or callback got fail value.
   1689 
   1690 	if( bGame )
   1691 		iLobbyReturnAfterGame = iLobby;
   1692 
   1693 	return true;
   1694 }
   1695 
   1696 //***********************************************************************************************
   1697 void WolapiObject::DoFindPage()
   1698 {
   1699 	//	User presses find/page button.
   1700 	SimpleEditDlgClass* pFindPageDlg;
   1701 
   1702 	//	Ask user for user desired.
   1703 	Fancy_Text_Print( TXT_NONE, 0, 0, TBLACK, TBLACK, TPF_TEXT );	//	Required before String_Pixel_Width() call, for god's sake.
   1704 	pFindPageDlg = new SimpleEditDlgClass( 400, TXT_WOL_PAGELOCATE, TXT_WOL_USERNAMEPROMPT, WOL_NAME_LEN_MAX );
   1705 	pFindPageDlg->SetButtons( TXT_WOL_LOCATE, Text_String( TXT_CANCEL ), TXT_WOL_PAGE );
   1706 	bPump_In_Call_Back = true;
   1707 	const char* szNameDlgResult = pFindPageDlg->Show();
   1708 	bPump_In_Call_Back = false;
   1709 
   1710 	if( strcmp( szNameDlgResult, Text_String( TXT_CANCEL ) ) == 0 || !*pFindPageDlg->szEdit )
   1711 	{
   1712 		delete pFindPageDlg;
   1713 		return;
   1714 	}
   1715 
   1716 	if( strcmp( szNameDlgResult, TXT_WOL_LOCATE ) == 0 )
   1717 	{
   1718 		//	Locate user.
   1719 		HRESULT hRes = Locate( pFindPageDlg->szEdit );
   1720 		switch( hRes )
   1721 		{
   1722 		case CHAT_S_FIND_NOTHERE:
   1723 			bPump_In_Call_Back = true;
   1724 			WWMessageBox().Process( TXT_WOL_FIND_NOTHERE );
   1725 			bPump_In_Call_Back = false;
   1726 			break;
   1727 		case CHAT_S_FIND_NOCHAN:
   1728 			bPump_In_Call_Back = true;
   1729 			WWMessageBox().Process( TXT_WOL_FIND_NOCHAN );
   1730 			bPump_In_Call_Back = false;
   1731 			break;
   1732 		case CHAT_S_FIND_OFF:
   1733 			bPump_In_Call_Back = true;
   1734 			WWMessageBox().Process( TXT_WOL_FIND_OFF );
   1735 			bPump_In_Call_Back = false;
   1736 			break;
   1737 		case CHAT_E_TIMEOUT:
   1738 			bPump_In_Call_Back = true;
   1739 			WWMessageBox().Process( TXT_WOL_TIMEOUT );
   1740 			bPump_In_Call_Back = false;
   1741 			break;
   1742 		case E_FAIL:
   1743 			GenericErrorMessage();
   1744 			break;
   1745 		case S_OK:
   1746 		{
   1747 			char* szChannel = (char*)pChatSink->OnFindChannel.name;
   1748 			int iLobby = iChannelLobbyNumber( (unsigned char*)szChannel );
   1749 			char* szFound;
   1750 			if( iLobby != -1 )
   1751 			{
   1752 				char szLobbyName[ REASONABLELOBBYINTERPRETEDNAMELEN ];
   1753 				InterpretLobbyNumber( szLobbyName, iLobby );
   1754 				szFound = new char[ strlen( TXT_WOL_FOUNDIN ) + strlen( szLobbyName ) + 5 ];
   1755 				sprintf( szFound, TXT_WOL_FOUNDIN, szLobbyName );
   1756 			}
   1757 			else
   1758 			{
   1759 				szFound = new char[ strlen( TXT_WOL_FOUNDIN ) + strlen( szChannel ) + 5 ];
   1760 				sprintf( szFound, TXT_WOL_FOUNDIN, szChannel );
   1761 			}
   1762 			bPump_In_Call_Back = true;
   1763 			WWMessageBox().Process( szFound );
   1764 			bPump_In_Call_Back = false;
   1765 			delete [] szFound;
   1766 			break;
   1767 		}
   1768 		}
   1769 	}
   1770 	else
   1771 	{
   1772 		//	Page user.
   1773 		//	Ask user for text to send.
   1774 		SimpleEditDlgClass* pMessDlg = new SimpleEditDlgClass( 600, TXT_WOL_PAGEMESSAGETITLE, 
   1775 																TXT_WOL_PAGEMESSAGEPROMPT, MAXCHATSENDLENGTH );
   1776 		bPump_In_Call_Back = true;
   1777 		if( strcmp( pMessDlg->Show(), Text_String( TXT_OK ) ) == 0 && *pMessDlg->szEdit )
   1778 		{
   1779 			switch( Page( pFindPageDlg->szEdit, pMessDlg->szEdit, true ) )
   1780 			{
   1781 			case CHAT_S_PAGE_NOTHERE:
   1782 				WWMessageBox().Process( TXT_WOL_PAGE_NOTHERE );
   1783 				break;
   1784 			case CHAT_S_PAGE_OFF:
   1785 				WWMessageBox().Process( TXT_WOL_PAGE_OFF );
   1786 				break;
   1787 			case CHAT_E_TIMEOUT:
   1788 				WWMessageBox().Process( TXT_WOL_TIMEOUT );
   1789 				break;
   1790 			case E_FAIL:
   1791 				GenericErrorMessage();
   1792 				break;
   1793 			case S_OK:
   1794 				char szMessage[ WOL_NAME_LEN_MAX + 30 ];
   1795 				sprintf( szMessage, TXT_WOL_WASPAGED, pFindPageDlg->szEdit );
   1796 				PrintMessage( szMessage, WOLCOLORREMAP_LOCALMACHINEMESS );
   1797 				break;
   1798 			}
   1799 		}
   1800 		bPump_In_Call_Back = false;
   1801 	}
   1802 
   1803 	delete pFindPageDlg;
   1804 }
   1805 
   1806 //***********************************************************************************************
   1807 HRESULT WolapiObject::Locate( const char* szUser )
   1808 {
   1809 	//	Returns HRESULT with possibly customized meanings.
   1810 
   1811 	char* szMessage = new char[ strlen( TXT_WOL_LOCATING ) + strlen( szUser ) + 5 ];
   1812 	sprintf( szMessage, TXT_WOL_LOCATING, szUser );
   1813 	WWMessageBox().Process( szMessage, TXT_NONE );
   1814 	delete [] szMessage;
   1815 
   1816 	pChatSink->bRequestFindWait = true;
   1817 	
   1818 	User userFind;
   1819 	strcpy( (char*)userFind.name, szUser );
   1820 
   1821 //	debugprint( "RequestFind()\n" );
   1822 	if( !SUCCEEDED( pChat->RequestFind( &userFind ) ) )
   1823 	{
   1824 //		debugprint( "RequestFind() call failed\n" );
   1825 		return 0;
   1826 	}
   1827 	DWORD dwTimeStart = timeGetTime();
   1828 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1829 	while( pChatSink->bRequestFindWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   1830 	{
   1831 		while( timeGetTime() < dwTimeNextPump )
   1832 			Call_Back();
   1833 		pChat->PumpMessages();
   1834 //		debugprint( ">Find pump\n" );
   1835 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1836 	}
   1837 
   1838 	if( pChatSink->bRequestFindWait )
   1839 		return CHAT_E_TIMEOUT;
   1840 
   1841 	return pChatSink->hresRequestFindResult;
   1842 }
   1843 
   1844 //***********************************************************************************************
   1845 HRESULT WolapiObject::Page( const char* szUser, const char* szSend, bool bWaitForResult )
   1846 {
   1847 	//	Returns HRESULT with possibly customized meanings.
   1848 
   1849 	if( bWaitForResult )
   1850 	{
   1851 		char* szMessage = new char[ strlen( TXT_WOL_PAGING ) + strlen( szUser ) + 5 ];
   1852 		sprintf( szMessage, TXT_WOL_PAGING, szUser );
   1853 		WWMessageBox().Process( szMessage, TXT_NONE );
   1854 		delete [] szMessage;
   1855 	}
   1856 
   1857 	pChatSink->bRequestPageWait = true;
   1858 	
   1859 	User userFind;
   1860 	strcpy( (char*)userFind.name, szUser );
   1861 
   1862 //	debugprint( "RequestPage()\n" );
   1863 	if( !SUCCEEDED( pChat->RequestPage( &userFind, szSend ) ) )
   1864 	{
   1865 //		debugprint( "RequestPage() call failed\n" );
   1866 		return 0;
   1867 	}
   1868 	if( !bWaitForResult )
   1869 		return 0;
   1870 	DWORD dwTimeStart = timeGetTime();
   1871 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1872 	while( pChatSink->bRequestPageWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   1873 	{
   1874 		while( timeGetTime() < dwTimeNextPump )
   1875 			Call_Back();
   1876 		pChat->PumpMessages();
   1877 //		debugprint( ">Page pump\n" );
   1878 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   1879 	}
   1880 
   1881 	if( pChatSink->bRequestPageWait )
   1882 		return CHAT_E_TIMEOUT;
   1883 
   1884 	return pChatSink->hresRequestPageResult;
   1885 }
   1886 
   1887 //***********************************************************************************************
   1888 void WolapiObject::DoKick( IconListClass* pILUsersOrPlayers, bool bAndBan )
   1889 {
   1890 	//	Kick selected users.
   1891 
   1892 	if( CurrentLevel != WOL_LEVEL_INCHATCHANNEL && CurrentLevel != WOL_LEVEL_INLOBBY && CurrentLevel != WOL_LEVEL_INGAMECHANNEL )
   1893 	{
   1894 		PrintMessage( TXT_WOL_YOURENOTINCHANNEL, WOLCOLORREMAP_LOCALMACHINEMESS );
   1895 		Sound_Effect( WOLSOUND_ERROR );
   1896 	}
   1897 	else if( !bChannelOwner )
   1898 	{
   1899 		PrintMessage( TXT_WOL_ONLYOWNERCANKICK, WOLCOLORREMAP_LOCALMACHINEMESS );
   1900 		Sound_Effect( WOLSOUND_ERROR );
   1901 	}
   1902 	else
   1903 	{
   1904 		int iFound = 0;
   1905 		for( int i = 0; i < pILUsersOrPlayers->Count(); i++ )
   1906 		{
   1907 			if( pILUsersOrPlayers->bItemIsMultiSelected( i ) )
   1908 			{
   1909 				User* pUser = (User*)pILUsersOrPlayers->Get_Item_ExtraDataPtr( i );
   1910 				if( pUser && strcmp( (char*)pUser->name, szMyName ) != 0 )			//	Don't kick yourself.
   1911 				{
   1912 					Kick( pUser );
   1913 					if( bAndBan )
   1914 						Ban( pUser );
   1915 					iFound++;
   1916 					if( iFound < 5 )
   1917 						Sound_Effect( (VocType)( VOC_SCREAM1 + ( rand() % 9 ) ) );
   1918 				}
   1919 			}
   1920 		}
   1921 		if( !iFound )
   1922 		{
   1923 			PrintMessage( TXT_WOL_NOONETOKICK, WOLCOLORREMAP_LOCALMACHINEMESS );
   1924 			Sound_Effect( WOLSOUND_ERROR );
   1925 		}
   1926 	}
   1927 }
   1928 
   1929 //***********************************************************************************************
   1930 bool WolapiObject::Kick( User* pUserToKick )
   1931 {
   1932 	//	Returns false if something terrible happens.
   1933 //	debugprint( "RequestUserKick()\n" );
   1934 	if( !SUCCEEDED( pChat->RequestUserKick( pUserToKick ) ) )
   1935 	{
   1936 //		debugprint( "RequestUserKick() call failed\n" );
   1937 		return false;
   1938 	}
   1939 	return true;
   1940 }
   1941 
   1942 //***********************************************************************************************
   1943 bool WolapiObject::Ban( User* pUserToKick )
   1944 {
   1945 	//	Returns false if something terrible happens.
   1946 //	debugprint( "RequestChannelBan()\n" );
   1947 	if( !SUCCEEDED( pChat->RequestChannelBan( (char*)pUserToKick->name, true ) ) )
   1948 	{
   1949 //		debugprint( "RequestUserKick() call failed\n" );
   1950 		return false;
   1951 	}
   1952 	return true;
   1953 }
   1954 
   1955 //***********************************************************************************************
   1956 void WolapiObject::DoSquelch( IconListClass* pILUsersOrPlayers )
   1957 {
   1958 	//	Squelch/unsquelch selected users.
   1959 	bool bFound = false;
   1960 	for( int i = 0; i < pILUsersOrPlayers->Count(); i++ )
   1961 	{
   1962 		if( pILUsersOrPlayers->bItemIsMultiSelected( i ) )
   1963 		{
   1964 			User* pUser = (User*)pILUsersOrPlayers->Get_Item_ExtraDataPtr( i );
   1965 			if( pUser )
   1966 			{
   1967 				if( strcmp( (char*)pUser->name, szMyName ) != 0 )		//	Don't squelch yourself.
   1968 				{
   1969 					Squelch( pUser );
   1970 //					char szMess[ 150 ];
   1971 //					if( Squelch( pUser ) )
   1972 //						sprintf( szMess, TXT_WOL_USERISSQUELCHED, (char*)pUser->name );
   1973 //					else
   1974 //						sprintf( szMess, TXT_WOL_USERISNOTSQUELCHED, (char*)pUser->name );
   1975 //					WOL_PrintMessage( chatlist, szMess, WOLCOLORREMAP_LOCALMACHINEMESS );
   1976 
   1977 					bFound = true;
   1978 					pILUsersOrPlayers->Flag_To_Redraw();
   1979 				}
   1980 				else
   1981 					PrintMessage( TXT_WOL_CANTSQUELCHSELF, WOLCOLORREMAP_LOCALMACHINEMESS );
   1982 			}
   1983 		}
   1984 	}
   1985 	if( bFound )
   1986 	{
   1987 		Sound_Effect( VOC_SQUISH );
   1988 		ListChannelUsers();		//	Refresh displayed user list.
   1989 	}
   1990 }
   1991 
   1992 //***********************************************************************************************
   1993 bool WolapiObject::Squelch( User* pUserToSquelch )
   1994 {
   1995 	//	Returns true if user is now squelched, false if not squelched.
   1996 	//	Sets User pointer flags value.
   1997 //	debugprint( "Squelch:: pUser is %i, flags is %i\n", pUserToSquelch, pUserToSquelch->flags );
   1998 
   1999 	if( pUserToSquelch->flags & CHAT_USER_SQUELCHED )
   2000 	{
   2001 		pChat->SetSquelch( pUserToSquelch, false );
   2002 		pUserToSquelch->flags &= ~CHAT_USER_SQUELCHED;
   2003 		return false;
   2004 	}
   2005 	pChat->SetSquelch( pUserToSquelch, true );
   2006 	pUserToSquelch->flags |= CHAT_USER_SQUELCHED;
   2007 	return true;
   2008 }
   2009 
   2010 //***********************************************************************************************
   2011 void WolapiObject::DoOptions()
   2012 {
   2013 	//	Show options dialog.
   2014 	bPump_In_Call_Back = true;
   2015 	WOL_Options_Dialog( this, false );
   2016 	bPump_In_Call_Back = false;
   2017 	//	Set trigger for an immediate channel list update, in case local lobby games filter was changed.
   2018 	dwTimeNextChannelUpdate = ::timeGetTime();
   2019 }
   2020 
   2021 //***********************************************************************************************
   2022 bool WolapiObject::DoLadder()
   2023 {
   2024 	bPump_In_Call_Back = true;
   2025 	if( WWMessageBox().Process( TXT_WOL_LADDERSHELL, TXT_YES, TXT_NO ) == 0 )
   2026 	{
   2027 		bPump_In_Call_Back = false;
   2028 #ifdef ENGLISH 
   2029 		//return SpawnBrowser( "http://www.westwood.com/ra_ladders.html" );
   2030 		return SpawnBrowser( "http://www.westwood.com/westwoodonline/tournaments/redalert/index.html" );
   2031 #else
   2032 #ifdef GERMAN
   2033 //		return SpawnBrowser( "http://www.westwood.com/ra_ladders_german.html" );
   2034 		return SpawnBrowser( "http://www.westwood.com/westwoodonline/tournaments/redalert/index.html" );
   2035 #else
   2036 //		return SpawnBrowser( "http://www.westwood.com/ra_ladders_french.html" );
   2037 		return SpawnBrowser( "http://www.westwood.com/westwoodonline/tournaments/redalert/index.html" );
   2038 #endif
   2039 #endif
   2040 	}
   2041 
   2042 	bPump_In_Call_Back = false;
   2043 	return false;
   2044 }
   2045 
   2046 //***********************************************************************************************
   2047 bool WolapiObject::DoHelp()
   2048 {
   2049 	bPump_In_Call_Back = true;
   2050 	if( WWMessageBox().Process( TXT_WOL_HELPSHELL, TXT_YES, TXT_NO ) == 0 )
   2051 	{
   2052 		bPump_In_Call_Back = false;
   2053 		const char* szURL;
   2054 		if( pChat->GetHelpURL( &szURL ) == S_OK )
   2055 			return SpawnBrowser( szURL );
   2056 		GenericErrorMessage();
   2057 	}
   2058 
   2059 	bPump_In_Call_Back = false;
   2060 	return false;
   2061 }
   2062 
   2063 //***********************************************************************************************
   2064 bool WolapiObject::DoWebRegistration()
   2065 {
   2066 	//	Get the executable name from the registry.
   2067 	HKEY hKey;
   2068 	if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SOFTWARE\\Westwood\\Register", 0, KEY_READ, &hKey ) != ERROR_SUCCESS )
   2069 	{
   2070 		GenericErrorMessage();
   2071 		return false;
   2072 	}
   2073 	char szPath[ _MAX_PATH + 1 ];
   2074 	DWORD dwBufSize = _MAX_PATH;
   2075 	if( RegQueryValueEx( hKey, "InstallPath", 0, NULL, (LPBYTE)szPath, &dwBufSize ) != ERROR_SUCCESS )
   2076 	{
   2077 		GenericErrorMessage();
   2078 		return false;
   2079 	}
   2080 	RegCloseKey( hKey );
   2081 //	debugprint( "Registration app is '%s'\n", szPath );
   2082 
   2083 	bPump_In_Call_Back = true;
   2084 	if( WWMessageBox().Process( TXT_WOL_WEBREGISTRATIONSHELL, TXT_YES, TXT_NO ) == 0 )
   2085 	{
   2086 		bPump_In_Call_Back = false;
   2087 		::ShellExecute( NULL, "open", szPath, NULL, ".", SW_SHOW );
   2088 		return true;
   2089 	}
   2090 
   2091 	bPump_In_Call_Back = false;
   2092 	return false;
   2093 }
   2094 
   2095 //***********************************************************************************************
   2096 bool WolapiObject::DoGameAdvertising( const Channel* pChannel )
   2097 {
   2098 	const char* szURL = URLForGameType( pChannel->type );
   2099 	if( !szURL )
   2100 	{
   2101 		GenericErrorMessage();
   2102 		return false;
   2103 	}
   2104 
   2105 	char szQuestion[512];
   2106 	sprintf( szQuestion, TXT_WOL_GAMEADVERTSHELL, NameOfGameType( pChannel->type ) );
   2107 	bPump_In_Call_Back = true;
   2108 	if( WWMessageBox().Process( szQuestion, TXT_YES, TXT_NO ) == 0 )
   2109 	{
   2110 		bPump_In_Call_Back = false;
   2111 		return SpawnBrowser( szURL );
   2112 	}
   2113 
   2114 	bPump_In_Call_Back = false;
   2115 	return false;
   2116 }
   2117 
   2118 //***********************************************************************************************
   2119 bool WolapiObject::SpawnBrowser( const char* szURL )
   2120 {
   2121 	//	Attempts to launch user's web browser, and monitors it, waiting for user to close it, at which
   2122 	//	point we bring focus back to the game.
   2123 
   2124 	//	Loosely based on Dune2000 example.
   2125 
   2126 	bool bSuccess = false;
   2127 	STARTUPINFO si;
   2128 	PROCESS_INFORMATION pi;
   2129 
   2130 	ZeroMemory( &si, sizeof(si) );
   2131 	si.cb = sizeof(si);
   2132 
   2133 	if( *szWebBrowser )
   2134 	{
   2135 		char szCommandLine[ _MAX_PATH + 300 ];
   2136 		sprintf( szCommandLine, "\"%s\" %s", szWebBrowser, szURL );
   2137 //		debugprint( "About to CreateProcess: '%s'\n", szCommandLine );
   2138 		Hide_Mouse();
   2139 		BlackPalette.Set( FADE_PALETTE_FAST, Call_Back );
   2140 //		::ShowWindow( MainWindow, SW_SHOWMINIMIZED );
   2141 		SeenPage.Clear();
   2142 		if(	::CreateProcess(	NULL,
   2143 								szCommandLine,	//	Command line.
   2144 								NULL,			//	Process handle not inheritable.
   2145 								NULL,			//	Thread handle not inheritable.
   2146 								FALSE,			//	Set handle inheritance to FALSE.
   2147 								0,				//	No creation flags.
   2148 								NULL,			//	Use parent’s environment block.
   2149 								NULL,			//	Use parent’s starting directory.
   2150 								&si,			//	Pointer to STARTUPINFO structure.
   2151 								&pi ) )			//	Pointer to PROCESS_INFORMATION structure.
   2152 		{
   2153 			if( pi.hProcess )
   2154 			{
   2155 //				debugprint( "CreateProcess: '%s'\n", szCommandLine );
   2156 				bSuccess = true;
   2157 				::WaitForInputIdle( pi.hProcess, 5000 );
   2158 				bPump_In_Call_Back = true;
   2159 				for( ; ; )
   2160 				{
   2161 					DWORD dwActive;
   2162 					Call_Back();
   2163 					Sleep( 200 );
   2164 					::GetExitCodeProcess( pi.hProcess, &dwActive );
   2165 					if( dwActive != STILL_ACTIVE || cancel_current_msgbox )
   2166 					{
   2167 						//	Either user closed the browser app, or game is starting and we should return focus to game.
   2168 						cancel_current_msgbox = false;
   2169 						::SetForegroundWindow( MainWindow );
   2170 						::ShowWindow( MainWindow, SW_RESTORE );
   2171 						break;
   2172 					}
   2173 					if( ::GetTopWindow( NULL ) == MainWindow )
   2174 					{
   2175 						::ShowWindow( MainWindow, SW_RESTORE );		//	In case it was topmost but minimized.
   2176 					   break;
   2177 					}
   2178 				}
   2179 				bPump_In_Call_Back = false;
   2180 				GamePalette.Set( FADE_PALETTE_FAST, Call_Back );
   2181 				Show_Mouse();
   2182 			}
   2183 		}
   2184 	}
   2185 
   2186 	if( !bSuccess )
   2187 	{
   2188 		//	This was the old way - does not pop you back into game when finished...
   2189 		if( (int)::ShellExecute( NULL, NULL, szURL, NULL, ".", SW_SHOW ) <= 32 )
   2190 		{
   2191 //			debugprint( "ShellExecute\n" );
   2192 			//	ShellExecute failed as well. Just print a message instead.
   2193 			GamePalette.Set();
   2194 			::ShowWindow( MainWindow, SW_RESTORE );
   2195 			char szError[ 300 ];
   2196 			sprintf( szError, TXT_WOL_CANTLAUNCHBROWSER, szURL );
   2197 			Show_Mouse();
   2198 			WWMessageBox().Process( szError );
   2199 			return false;
   2200 		}
   2201 		//	(We return immediately after launching in this case.)
   2202 		GamePalette.Set();
   2203 		Show_Mouse();
   2204 	}
   2205 	return true;
   2206 }
   2207 
   2208 //***********************************************************************************************
   2209 void WolapiObject::ChannelListTitle( const char* szTitle )
   2210 {
   2211 	strcpy( szChannelListTitle, szTitle );
   2212 	bChannelListTitleUpdated = true;
   2213 }
   2214 
   2215 //***********************************************************************************************
   2216 bool WolapiObject::EnterLevel_Top()
   2217 {
   2218 	//	<Showing the top level choices.>
   2219 
   2220 //	debugprint( "*** EnterLevel_Top\n" );
   2221 	//	(Might as well hardcode the channels tree.)
   2222 
   2223 	ChannelListTitle( TXT_WOL_TOPLEVELTITLE );
   2224 	pILChannels->Clear();
   2225 	//void* pTopIcon = (void*)DibIconInfos[ DIBICON_ACCEPT ].pDIB;
   2226 	void* pTopIcon = IconForGameType( 0 );
   2227 	pILChannels->Add_Item( TXT_WOL_OFFICIALCHAT, CHANNELTYPE_OFFICIALCHAT, pTopIcon, ICON_DIB, CHANNELTYPE_OFFICIALCHAT );
   2228 	pILChannels->Add_Item( TXT_WOL_USERCHAT, CHANNELTYPE_USERCHAT, pTopIcon, ICON_DIB, CHANNELTYPE_USERCHAT );
   2229 	pILChannels->Add_Item( TXT_WOL_GAMECHANNELS, CHANNELTYPE_GAMES, pTopIcon, ICON_DIB, CHANNELTYPE_GAMES );
   2230 
   2231 	//	Set wol buttons enabled/disabled.
   2232 	pShpBtnLeave->Disable();
   2233 	pShpBtnRefresh->Disable();
   2234 	pShpBtnSquelch->Disable();
   2235 	pShpBtnBan->Disable();
   2236 	pShpBtnKick->Disable();
   2237 	
   2238 	CurrentLevel = WOL_LEVEL_TOP;
   2239 
   2240 	return true;
   2241 }
   2242 
   2243 //***********************************************************************************************
   2244 bool WolapiObject::EnterLevel_OfficialChat()
   2245 {
   2246 	//	<Showing available official chat channels.>
   2247 
   2248 //	debugprint( "*** EnterLevel_OfficialChat\n" );
   2249 	//	(Might as well hardcode the channels tree.)
   2250 
   2251 	CurrentLevel = WOL_LEVEL_OFFICIALCHAT;
   2252 	if( !UpdateChannels( 0, CHANNELFILTER_OFFICIAL, false ) )
   2253 	{
   2254 		GenericErrorMessage();
   2255 		return false;
   2256 	}
   2257 
   2258 	ChannelListTitle( TXT_WOL_OFFICIALCHAT );
   2259 	pILChannels->Clear();
   2260 	pILChannels->Add_Item( TXT_WOL_CHANNELLISTLOADING, CHANNELTYPE_LOADING, NULL, ICON_SHAPE, CHANNELTYPE_LOADING );
   2261 	dwTimeNextChannelUpdate = ::timeGetTime();		//	Set trigger for an immediate channel list update.
   2262 
   2263 	//	Set wol buttons enabled/disabled.
   2264 	pShpBtnLeave->Disable();
   2265 	pShpBtnRefresh->Enable();
   2266 	pShpBtnSquelch->Disable();
   2267 	pShpBtnBan->Disable();
   2268 	pShpBtnKick->Disable();
   2269 	
   2270 	return true;
   2271 }
   2272 
   2273 //***********************************************************************************************
   2274 bool WolapiObject::EnterLevel_UserChat()
   2275 {
   2276 	//	<Showing available user chat channels.>
   2277 
   2278 //	debugprint( "*** EnterLevel_UserChat\n" );
   2279 	//	(Might as well hardcode the channels tree.)
   2280 
   2281 	CurrentLevel = WOL_LEVEL_USERCHAT;
   2282 	if( !UpdateChannels( 0, CHANNELFILTER_UNOFFICIAL, false ) )
   2283 	{
   2284 		GenericErrorMessage();
   2285 		return false;
   2286 	}
   2287 
   2288 	ChannelListTitle( TXT_WOL_USERCHAT );
   2289 	pILChannels->Clear();
   2290 	pILChannels->Add_Item( TXT_WOL_CHANNELLISTLOADING, CHANNELTYPE_LOADING, NULL, ICON_SHAPE, CHANNELTYPE_LOADING );
   2291 	dwTimeNextChannelUpdate = ::timeGetTime();		//	Set trigger for an immediate channel list update.
   2292 
   2293 	//	Set wol buttons enabled/disabled.
   2294 	pShpBtnLeave->Disable();
   2295 	pShpBtnRefresh->Enable();
   2296 	pShpBtnSquelch->Disable();
   2297 	pShpBtnBan->Disable();
   2298 	pShpBtnKick->Disable();
   2299 	
   2300 	return true;
   2301 }
   2302 
   2303 //***********************************************************************************************
   2304 bool WolapiObject::EnterLevel_Games()
   2305 {
   2306 	//	<Showing each westwood game type.>
   2307 
   2308 //	debugprint( "*** EnterLevel_Games\n" );
   2309 	//	(Might as well hardcode the channels tree.)
   2310 
   2311 	CurrentLevel = WOL_LEVEL_GAMES;
   2312 
   2313 	ChannelListTitle( TXT_WOL_GAMECHANNELS );
   2314 	pILChannels->Clear();
   2315 	pILChannels->Add_Item( TXT_WOL_CHANNEL_TOP, CHANNELTYPE_TOP, NULL, ICON_SHAPE, CHANNELTYPE_TOP );
   2316 
   2317 	//	Create entry for our lobbies at the top.
   2318 	bool bFound = false;
   2319 	//	(There are actually 2 additional game types at the end of GameTypeInfos - for ws icon and wwonline icon.)
   2320 	for( int i = 0; i < nGameTypeInfos - 2; i++ )
   2321 	{
   2322 		if( GameTypeInfos[ i ].iGameType == GAME_TYPE )
   2323 		{
   2324 			//pILChannels->Add_Item( GameTypeInfos[ i ].szName, CHANNELTYPE_LOBBIES, (void*)GameTypeInfos[ i ].pDIB, ICON_DIB, CHANNELTYPE_LOBBIES );
   2325 			pILChannels->Add_Item( TXT_WOL_REDALERTLOBBIES, CHANNELTYPE_LOBBIES, (void*)GameTypeInfos[ i ].pDIB, ICON_DIB, CHANNELTYPE_LOBBIES );
   2326 			bFound = true;
   2327 			break;
   2328 		}
   2329 	}
   2330 	if( !bFound )
   2331 	{
   2332 		//	In the production version, this should never happen, as there should always be a gametypeinfo created that matches
   2333 		//	our game type. It depends on the recentness of the WOR file accompanying the wolapi.dll.
   2334 		pILChannels->Add_Item( TXT_WOL_REDALERTLOBBIES, CHANNELTYPE_LOBBIES, (void*)OldRAGameTypeInfos[ 0 ].pDIB, ICON_DIB, CHANNELTYPE_LOBBIES );
   2335 	}
   2336 
   2337 	//	A pointer to the GameTypeInfos entry is stored in the item for convenience later.
   2338 	for( i = 0; i < nGameTypeInfos - 2; i++ )
   2339 	{
   2340 		int iType = GameTypeInfos[ i ].iGameType;
   2341 		if( iType != GAME_TYPE )		//	Else it is our game - skip it here since we put it at the top.
   2342 		{
   2343 			if( iType != 2 && iType != 3 && iType != 4 )	//	Hack needed for the time being, to prevent the old ra games from being seen.
   2344 			{
   2345 				char szHelp[ 200 ];
   2346 				sprintf( szHelp, TXT_WOL_TTIP_CHANNELTYPE_GAMESOFTYPE, GameTypeInfos[ i ].szName );
   2347 				pILChannels->Add_Item( GameTypeInfos[ i ].szName, szHelp, (void*)GameTypeInfos[ i ].pDIB, ICON_DIB, CHANNELTYPE_GAMESOFTYPE, (void*)&GameTypeInfos[ i ] );
   2348 			}
   2349 		}
   2350 	}
   2351 
   2352 	//	Set wol buttons enabled/disabled.
   2353 	pShpBtnLeave->Disable();
   2354 	pShpBtnRefresh->Disable();
   2355 	pShpBtnSquelch->Disable();
   2356 	pShpBtnBan->Disable();
   2357 	pShpBtnKick->Disable();
   2358 	
   2359 	return true;
   2360 }
   2361 
   2362 //***********************************************************************************************
   2363 bool WolapiObject::EnterLevel_GamesOfType( WOL_GAMETYPEINFO* pGameTypeInfo )
   2364 {
   2365 	//	<Showing current game channels of a specific type - not our own game type.>
   2366 
   2367 //	debugprint( "*** EnterLevel_GamesOfType: pGameTypeInfo->szName %s, iGameType %i, URL %s\n", pGameTypeInfo->szName, pGameTypeInfo->iGameType, pGameTypeInfo->szURL );
   2368 
   2369 	CurrentLevel = WOL_LEVEL_GAMESOFTYPE;
   2370 	if( !UpdateChannels( pGameTypeInfo->iGameType, CHANNELFILTER_NO, true ) )
   2371 	{
   2372 		GenericErrorMessage();
   2373 		return false;
   2374 	}
   2375 
   2376 	ChannelListTitle( pGameTypeInfo->szName );
   2377 	pILChannels->Clear();
   2378 	pILChannels->Add_Item( TXT_WOL_CHANNELLISTLOADING, CHANNELTYPE_LOADING, NULL, ICON_SHAPE, CHANNELTYPE_LOADING );
   2379 	dwTimeNextChannelUpdate = ::timeGetTime();		//	Set trigger for an immediate channel list update.
   2380 
   2381 	//	Set wol buttons enabled/disabled.
   2382 	pShpBtnLeave->Disable();
   2383 	pShpBtnRefresh->Enable();
   2384 	pShpBtnSquelch->Disable();
   2385 	pShpBtnBan->Disable();
   2386 	pShpBtnKick->Disable();
   2387 	
   2388 	return true;
   2389 }
   2390 
   2391 //***********************************************************************************************
   2392 bool WolapiObject::EnterLevel_Lobbies()
   2393 {
   2394 	//	<Showing available lobbies.>
   2395 
   2396 //	debugprint( "*** EnterLevel_Lobbies\n" );
   2397 
   2398 	CurrentLevel = WOL_LEVEL_LOBBIES;
   2399 	if( !UpdateChannels( 0, CHANNELFILTER_LOBBIES, false ) )
   2400 	{
   2401 		GenericErrorMessage();
   2402 		return false;
   2403 	}
   2404 
   2405 	ChannelListTitle( TXT_WOL_REDALERTLOBBIES );
   2406 	pILChannels->Clear();
   2407 	pILChannels->Add_Item( TXT_WOL_CHANNELLISTLOADING, CHANNELTYPE_LOADING, NULL, ICON_SHAPE, CHANNELTYPE_LOADING );
   2408 	dwTimeNextChannelUpdate = ::timeGetTime();		//	Set trigger for an immediate channel list update.
   2409 
   2410 	//	Set wol buttons enabled/disabled.
   2411 	pShpBtnLeave->Disable();
   2412 	pShpBtnRefresh->Enable();
   2413 	pShpBtnSquelch->Disable();
   2414 	pShpBtnBan->Disable();
   2415 	pShpBtnKick->Disable();
   2416 	
   2417 	return true;
   2418 }
   2419 
   2420 //***********************************************************************************************
   2421 bool WolapiObject::OnEnteringChatChannel( const char* szChannelName, bool bICreatedChannel, int iLobby )
   2422 {
   2423 	//	Called when a chat channel (or lobby) has been successfully joined.
   2424 //	debugprint( "*** OnEnteringChatChannel '%s'\n", szChannelName );
   2425 	
   2426 //	//	Block until we have a userlist.		- Not necessary - always comes immediately following OnJoin.
   2427 //	if( !UserList() )
   2428 //		return false;
   2429 
   2430 	//	Request ladders if this is a lobby.
   2431 	if( iLobby != -1 )
   2432 		RequestLadders( NULL );
   2433 
   2434 	//	Set channels list.
   2435 	pILChannels->Clear();
   2436 	//	Add a "return" choice at the top of the channel list, based on where we want to go 'back' to...
   2437 	if( iLobby == -1 )
   2438 	{
   2439 		switch( CurrentLevel )
   2440 		{
   2441 		case WOL_LEVEL_OFFICIALCHAT:
   2442 			pILChannels->Add_Item( TXT_WOL_CHANNEL_BACK, CHANNELTYPE_OFFICIALCHAT, NULL, ICON_SHAPE, CHANNELTYPE_OFFICIALCHAT );
   2443 			break;
   2444 		case WOL_LEVEL_USERCHAT:
   2445 			pILChannels->Add_Item( TXT_WOL_CHANNEL_BACK, CHANNELTYPE_USERCHAT, NULL, ICON_SHAPE, CHANNELTYPE_USERCHAT );
   2446 			break;
   2447 		default:
   2448 			//	If entering a channel from anywhere else, user must have created the channel.
   2449 			//	Make "back" take them to user channels list.
   2450 //			if( bICreatedChannel )		//	ajw  just verifying
   2451 				pILChannels->Add_Item( TXT_WOL_CHANNEL_BACK, CHANNELTYPE_USERCHAT, NULL, ICON_SHAPE, CHANNELTYPE_USERCHAT );
   2452 /*			else
   2453 			{			
   2454 //				debugprint( "Case that should not occur in OnEnteringChatChannel. CurrentLevel %i\n", CurrentLevel );
   2455 				pILChannels->Add_Item( "ERROR in OnEnteringChatChannel", NULL, NULL, ICON_SHAPE, CHANNELTYPE_TOP );
   2456 			}
   2457 */
   2458 			break;
   2459 		}
   2460 	}
   2461 	else
   2462 	{
   2463 		pILChannels->Add_Item( TXT_WOL_CHANNEL_BACK, CHANNELTYPE_LOBBIES, NULL, ICON_SHAPE, CHANNELTYPE_LOBBIES );
   2464 	}
   2465 
   2466 	char* szMess;
   2467 	if( iLobby == -1 )
   2468 	{
   2469 		CurrentLevel = WOL_LEVEL_INCHATCHANNEL;
   2470 		szMess = new char[ strlen( TXT_WOL_YOUJOINED ) + strlen( szChannelName ) + 5 ];
   2471 		sprintf( szMess, TXT_WOL_YOUJOINED, szChannelName );
   2472 		ChannelListTitle( szChannelName );
   2473 	}
   2474 	else
   2475 	{
   2476 		CurrentLevel = WOL_LEVEL_INLOBBY;
   2477 		char szLobbyName[ REASONABLELOBBYINTERPRETEDNAMELEN ];
   2478 		InterpretLobbyNumber( szLobbyName, iLobby );
   2479 		szMess = new char[ strlen( TXT_WOL_YOUJOINEDLOBBY ) + REASONABLELOBBYINTERPRETEDNAMELEN + 10 ];
   2480 		sprintf( szMess, TXT_WOL_YOUJOINEDLOBBY, szLobbyName );
   2481 		ChannelListTitle( szLobbyName );
   2482 		iLobbyLast = iLobby;
   2483 		dwTimeNextChannelUpdate = ::timeGetTime();		//	Set trigger for an immediate channel list update.
   2484 	}
   2485 
   2486 	strcpy( szChannelNameCurrent, szChannelName );
   2487 
   2488 	bChannelOwner = bICreatedChannel;
   2489 
   2490 	//	Set users list.
   2491 	ListChannelUsers();
   2492 
   2493 	PrintMessage( szMess, WOLCOLORREMAP_LOCALMACHINEMESS );
   2494 	delete [] szMess;
   2495 
   2496 	Sound_Effect( WOLSOUND_ENTERCHAN );
   2497 
   2498 	//	Set wol buttons enabled/disabled.
   2499 	pShpBtnLeave->Enable();
   2500 	if( CurrentLevel == WOL_LEVEL_INLOBBY )
   2501 		pShpBtnRefresh->Enable();
   2502 	else
   2503 		pShpBtnRefresh->Disable();
   2504 	pShpBtnSquelch->Enable();
   2505 	if( bChannelOwner )
   2506 	{
   2507 		pShpBtnBan->Enable();
   2508 		pShpBtnKick->Enable();
   2509 	}
   2510 	else
   2511 	{
   2512 		pShpBtnBan->Disable();
   2513 		pShpBtnKick->Disable();
   2514 	}
   2515 
   2516 	return true;
   2517 }
   2518 
   2519 //***********************************************************************************************
   2520 void WolapiObject::OnExitingChatChannel()
   2521 {
   2522 	//	Called when we successfully ExitChannel, or we get kicked out. (Lobbies included.)
   2523 
   2524 	//	Clear users list.
   2525 	pILUsers->Clear();
   2526 	if( pStaticUsers )
   2527 		pStaticUsers->Set_Text( TXT_WOL_NOUSERLIST );
   2528 
   2529 //	debugprint( "*** OnExitingChatChannel() - szChannelNameCurrent '%s', CurrentLevel %i\n", szChannelNameCurrent, CurrentLevel );
   2530 	int iLobby = iChannelLobbyNumber( (unsigned char*)szChannelNameCurrent );
   2531 	char* szMess;
   2532 	if( iLobby == -1 )
   2533 	{
   2534 		szMess = new char[ strlen( TXT_WOL_YOULEFT ) + strlen( szChannelNameCurrent ) + 5 ];
   2535 		sprintf( szMess, TXT_WOL_YOULEFT, szChannelNameCurrent );
   2536 	}
   2537 	else
   2538 	{
   2539 		//	Channel is a lobby.
   2540 		char szLobbyName[ REASONABLELOBBYINTERPRETEDNAMELEN ];
   2541 		InterpretLobbyNumber( szLobbyName, iLobby );
   2542 		szMess = new char[ strlen( TXT_WOL_YOULEFTLOBBY ) + REASONABLELOBBYINTERPRETEDNAMELEN + 10 ];
   2543 		sprintf( szMess, TXT_WOL_YOULEFTLOBBY, szLobbyName );
   2544 	}
   2545 	PrintMessage( szMess, WOLCOLORREMAP_LOCALMACHINEMESS );
   2546 	delete [] szMess;
   2547 
   2548 	*szChannelNameCurrent = 0;
   2549 	CurrentLevel = WOL_LEVEL_INVALID;
   2550 
   2551 	Sound_Effect( WOLSOUND_EXITCHAN );
   2552 }
   2553 
   2554 //***********************************************************************************************
   2555 bool WolapiObject::ExitChatChannelForGameChannel()
   2556 {
   2557 	//	We are about to try and join/create a game channel, and are currently in a chat channel.
   2558 
   2559 	//	Save this channel name, so we can come back to it if game channel join/create fails.
   2560 	strcpy( szChannelReturnOnGameEnterFail, szChannelNameCurrent );
   2561 
   2562 	if( !ChannelLeave() )
   2563 	{
   2564 		GenericErrorMessage();
   2565 		return false;
   2566 	}
   2567 	return true;
   2568 }
   2569 
   2570 //***********************************************************************************************
   2571 bool WolapiObject::OnEnteringGameChannel( const char* szChannelName, bool bICreatedChannel, 
   2572 											const CREATEGAMEINFO& CreateGameInfo )
   2573 {
   2574 	//	Called when a game channel has been successfully joined, while still in chat dialog,
   2575 	//	before game dialog has been created.
   2576 	//	CreateGameInfo is copied to GameInfoCurrent, so that we know what kind of a game we're in during setup.
   2577 
   2578 //	debugprint( "*** OnEnteringGameChannel() - %s\n", szChannelName );
   2579 
   2580 	CurrentLevel = WOL_LEVEL_INGAMECHANNEL;
   2581 	strcpy( szChannelNameCurrent, szChannelName );
   2582 
   2583 	bChannelOwner = bICreatedChannel;
   2584 //	GameKindCurrent = GameKind;
   2585 	GameInfoCurrent = CreateGameInfo;
   2586 	strcpy( GameInfoCurrent.szPassword, CreateGameInfo.szPassword );
   2587 
   2588 	//	Remove shared buttons from wolchat's command list.
   2589 	pShpBtnDiscon->Zap();
   2590 	pShpBtnLeave->Zap();
   2591 	pShpBtnRefresh->Zap();
   2592 	pShpBtnSquelch->Zap();
   2593 	pShpBtnBan->Zap();
   2594 	pShpBtnKick->Zap();
   2595 	pShpBtnFindpage->Zap();
   2596 	pShpBtnOptions->Zap();
   2597 	pShpBtnLadder->Zap();
   2598 	pShpBtnHelp->Zap();
   2599 
   2600 	//	Set wol buttons enabled/disabled.
   2601 	pShpBtnLeave->Enable();
   2602 	pShpBtnRefresh->Disable();
   2603 	pShpBtnSquelch->Enable();
   2604 	if( bChannelOwner )
   2605 	{
   2606 		pShpBtnBan->Enable();
   2607 		pShpBtnKick->Enable();
   2608 	}
   2609 	else
   2610 	{
   2611 		pShpBtnBan->Disable();
   2612 		pShpBtnKick->Disable();
   2613 	}
   2614 
   2615 	if( CreateGameInfo.GameKind == CREATEGAMEINFO::AMGAME )
   2616 	{
   2617 		if( bShowRankRA )
   2618 		{
   2619 			//	Switch to "show AM rankings" mode.
   2620 			bShowRankRA = false;
   2621 			bMyRecordUpdated = true;
   2622 			bShowRankUpdated = true;
   2623 		}
   2624 	}
   2625 	else
   2626 	{
   2627 		if( !bShowRankRA )
   2628 		{
   2629 			//	Switch to "show RA rankings" mode.
   2630 			bShowRankRA = true;
   2631 			bMyRecordUpdated = true;
   2632 			bShowRankUpdated = true;
   2633 		}
   2634 	}
   2635 
   2636 	return true;
   2637 }
   2638 
   2639 //***********************************************************************************************
   2640 bool WolapiObject::OnEnteringGameSetup()
   2641 {
   2642 	//	Called when entering the game setup screen. Controls are initialized. OnEnteringGameChannel
   2643 	//	has just been called earlier.
   2644 
   2645 	//	Returns false only if we find there is not host - he must have simultaneously left.
   2646 
   2647 //	//	Block until we have a userlist.		- Not necessary - always comes immediately following OnJoin.
   2648 //	if( !UserList() )
   2649 //		return false;
   2650 
   2651 	//	Request ladders.
   2652 	RequestLadders( NULL );
   2653 
   2654 	//	Request IP addresses.
   2655 	RequestIPs( NULL );
   2656 
   2657 	//	Set users list.
   2658 	if( !ListChannelUsers() )
   2659 	{
   2660 		//	No host was found currently in channel!
   2661 		return false;
   2662 	}
   2663 
   2664 	if( !pGSupDlg->bHost )
   2665 	{
   2666 		char* szMess = new char[ strlen( TXT_WOL_YOUJOINEDGAME ) + WOL_NAME_LEN_MAX + 5 ];
   2667 		char szHostName[ WOL_NAME_LEN_MAX ];
   2668 		HostNameFromGameChannelName( szHostName, szChannelNameCurrent );
   2669 		sprintf( szMess, TXT_WOL_YOUJOINEDGAME, szHostName );
   2670 		PrintMessage( szMess, WOLCOLORREMAP_LOCALMACHINEMESS );
   2671 		delete [] szMess;
   2672 	}
   2673 	else
   2674 		PrintMessage( TXT_WOL_YOUCREATEDGAME, WOLCOLORREMAP_LOCALMACHINEMESS );
   2675 
   2676 	return true;
   2677 }
   2678 
   2679 //***********************************************************************************************
   2680 void WolapiObject::OnFailedToEnterGameChannel()
   2681 {
   2682 	if( *szChannelReturnOnGameEnterFail == 0 )
   2683 		return;
   2684 
   2685 	//	This is called when we fail to join/create a game channel.
   2686 	*szChannelNameCurrent = 0;
   2687 
   2688 	//	Because we don't save the channel key as well, assume the usual lobby password. If we fail, we'll return to top level.
   2689 	HRESULT hRes = ChannelJoin( szChannelReturnOnGameEnterFail, LOBBYPASSWORD );
   2690 	switch( hRes )
   2691 	{
   2692 	case S_OK:
   2693 		OnEnteringChatChannel( szChannelReturnOnGameEnterFail, false, iChannelLobbyNumber( (unsigned char*)szChannelReturnOnGameEnterFail ) );
   2694 		break;
   2695 	default:
   2696 		//	ChannelJoin returned fail value.
   2697 		//	(Now only applies if you could ever enter a game channel from a non-lobby.)
   2698 		//	There is the possibility that the channel we were in disappeared in the instant between leaving it and
   2699 		//	failing to join the game channel. <sigh> Or, the channel has a password, that we didn't record. In either
   2700 		//	case, go back to the top level.
   2701 		GenericErrorMessage();
   2702 		EnterLevel_Top();
   2703 	}
   2704 }
   2705 
   2706 //***********************************************************************************************
   2707 void WolapiObject::OnExitingGameChannel()
   2708 {
   2709 	//	This is called after we leave a game channel, while still in the game setup dialog.
   2710 
   2711 	//	Remove shared buttons from wolgsup's command list.
   2712 	pShpBtnDiscon->Zap();
   2713 	pShpBtnLeave->Zap();
   2714 	pShpBtnRefresh->Zap();
   2715 	pShpBtnSquelch->Zap();
   2716 	pShpBtnBan->Zap();
   2717 	pShpBtnKick->Zap();
   2718 	pShpBtnFindpage->Zap();
   2719 	pShpBtnOptions->Zap();
   2720 	pShpBtnLadder->Zap();
   2721 	pShpBtnHelp->Zap();
   2722 
   2723 	CurrentLevel = WOL_LEVEL_INVALID;
   2724 	*szChannelNameCurrent = 0;
   2725 }
   2726 
   2727 //***********************************************************************************************
   2728 void WolapiObject::RejoinLobbyAfterGame()
   2729 {
   2730 	//	Called to rejoin lobby after EITHER a game, or the game setup dialog.
   2731 //debugprint( "RejoinLobbyAfterGame, iLobbyReturnAfterGame is %i\n", iLobbyReturnAfterGame );
   2732 
   2733 	if( iLobbyReturnAfterGame == -1 )
   2734 	{
   2735 		//	Will never happen presumably, if games are always entered via a lobby chat channel.
   2736 		//	We will naturally reenter the top level.
   2737 	}
   2738 	else
   2739 	{
   2740 		char szChannelToJoin[ WOL_CHANNAME_LEN_MAX ];
   2741 		//sprintf( szChannelToJoin, "Lob_%i_%i", GAME_TYPE, iLobbyReturnAfterGame );
   2742 		sprintf( szChannelToJoin, "%s%i", LOB_PREFIX, iLobbyReturnAfterGame );
   2743 //debugprint( "RejoinLobbyAfterGame, channel is %s\n", szChannelToJoin );
   2744 
   2745 		HRESULT hRes = ChannelJoin( szChannelToJoin, LOBBYPASSWORD );
   2746 		switch( hRes )
   2747 		{
   2748 		case S_OK:
   2749 			//OnEnteringChatChannel( szChannelToJoin, false );		Done automatically now in wol_chat.
   2750 			break;
   2751 		default:
   2752 			//	Something went wrong when trying to rejoin the lobby we were in.
   2753 			//	We'll go back to the top level instead, which happens automatically if we do this...
   2754 			iLobbyReturnAfterGame = -1;
   2755 			break;
   2756 		}
   2757 	}
   2758 }
   2759 
   2760 //***********************************************************************************************
   2761 bool WolapiObject::RequestLadders( const char* szName )
   2762 {
   2763 	//	If szName is NULL, calls RequestLadderList() until all ladder structs for all users in pChatSink's current 
   2764 	//	list have been asked for. Does not wait for results - these come in asynchronously. The previous list is
   2765 	//	erased before new results come in.
   2766 	//	If szName is valid, asks for specific name only. Result is appended to current ladder list.
   2767 	//	This function does not block.
   2768 
   2769 	if( szName && *szName )
   2770 	{
   2771 //		debugprint( "RequestLadderList( %s )\n", szName );
   2772 		if( !SUCCEEDED( pNetUtil->RequestLadderList( szLadderServerHost, iLadderServerPort, szName, LADDER_CODE_RA, -1, 0, 0 ) ) )
   2773 		{
   2774 //			debugprint( "RequestLadderList() call failed\n" );
   2775 			return false;
   2776 		}
   2777 		if( !SUCCEEDED( pNetUtil->RequestLadderList( szLadderServerHost, iLadderServerPort, szName, LADDER_CODE_AM, -1, 0, 0 ) ) )
   2778 		{
   2779 //			debugprint( "RequestLadderList() call failed\n" );
   2780 			return false;
   2781 		}
   2782 		return true;
   2783 	}
   2784 
   2785 	char szNames[ ( WOL_NAME_LEN_MAX + 1 ) * 30 ];			//	Neal says max is actually 25 names requested at once. Do 24...
   2786 
   2787 	pNetUtilSink->DeleteLadderList();
   2788 
   2789 	//	Do not request more than this number of times, to prevent overloads to ladder server.
   2790 	//	If we have that many people in the channel, forget about doing ladders for all of them.
   2791 	//	Probably this will never come into play (except while testing), because lobbies will be limited in # of users.
   2792 	int iCallLimit = 4;
   2793 
   2794 	User* pUser = pChatSink->pUserList;
   2795 	while( pUser )
   2796 	{
   2797 		//	Reset names string.
   2798 		*szNames = 0;
   2799 		//	Get 24 users from list and add names to string.
   2800 		for( int i = 0; i != 24; ++i )
   2801 		{
   2802 			strcat( szNames, (char*)pUser->name );
   2803 			strcat( szNames, ":" );
   2804 			pUser = pUser->next;
   2805 			if( !pUser )
   2806 				break;
   2807 		}
   2808 		//	Remove last colon.
   2809 		szNames[ strlen( szNames ) - 1 ] = 0;
   2810 
   2811 //		debugprint( "RequestLadderList( %s )\n", szNames );
   2812 		if( !SUCCEEDED( pNetUtil->RequestLadderList( szLadderServerHost, iLadderServerPort, szNames, LADDER_CODE_RA, -1, 0, 0 ) ) )
   2813 		{
   2814 //			debugprint( "RequestLadderList() call failed\n" );
   2815 			return false;
   2816 		}
   2817 		if( !SUCCEEDED( pNetUtil->RequestLadderList( szLadderServerHost, iLadderServerPort, szNames, LADDER_CODE_AM, -1, 0, 0 ) ) )
   2818 		{
   2819 //			debugprint( "RequestLadderList() call failed\n" );
   2820 			return false;
   2821 		}
   2822 		if( --iCallLimit == 0 )
   2823 			return false;
   2824 	}
   2825 	return true;
   2826 }
   2827 
   2828 //***********************************************************************************************
   2829 bool WolapiObject::RequestIPs( const char* szName )
   2830 {
   2831 	//	If szName is NULL, calls RequestUserIP() until IPs for all users in pChatSink's current 
   2832 	//	list have been asked for. Does not wait for results - these come in asynchronously. The previous list is
   2833 	//	erased before new results come in.
   2834 	//	If szName is valid, asks for specific name only. Result is appended to current IP list.
   2835 	//	This function does not block.
   2836 
   2837 	if( szName && *szName )
   2838 	{
   2839 		User user;
   2840 		strcpy( (char*)user.name, szName );
   2841 //		debugprint( "RequestUserIP( %s )\n", szName );
   2842 		if( !SUCCEEDED( pChat->RequestUserIP( &user ) ) )
   2843 		{
   2844 //			debugprint( "RequestUserIP() call failed\n" );
   2845 			return false;
   2846 		}
   2847 		return true;
   2848 	}
   2849 
   2850 	//	Do all users in current chatsink list.
   2851 
   2852 	pChatSink->DeleteUserIPList();	//	Clear old user IPs. (To keep searches fast, if we go in and out of game channels a lot.)
   2853 
   2854 	User* pUser = pChatSink->pUserList;
   2855 	while( pUser )
   2856 	{
   2857 		if( !( pUser->flags & CHAT_USER_MYSELF ) )
   2858 		{
   2859 			if( !SUCCEEDED( pChat->RequestUserIP( pUser ) ) )
   2860 			{
   2861 //				debugprint( "RequestUserIP() call failed\n" );
   2862 				return false;
   2863 			}
   2864 		}
   2865 		pUser = pUser->next;
   2866 	}
   2867 	return true;
   2868 }
   2869 
   2870 //***********************************************************************************************
   2871 void WolapiObject::SaveChat()
   2872 {
   2873 	//	Basically, a big hack to avoiding restructuring things so that the dialogs are persistent
   2874 	//	objects. Save the contents of the chat list in the chat dialog so that we can refresh it
   2875 	//	after returning from the game setup dialog (if necessary).
   2876 	//	This turns out to be the easiest and most straightforward way to implement this.
   2877 	pChatSaveLast = pChatSaveList = NULL;
   2878 	CHATSAVE* pChatSaveNew;
   2879 
   2880 	for( int i = 0; i != pILChat->Count(); i++ )
   2881 	{
   2882 		pChatSaveNew = new CHATSAVE;
   2883 		const char* szItem = pILChat->Get_Item( i );
   2884 		if( strlen( szItem ) < SAVECHATWIDTH )
   2885 			strcpy( pChatSaveNew->szText, szItem );
   2886 		const IconList_ItemExtras* pItemExtras = pILChat->Get_ItemExtras( i );
   2887 		pChatSaveNew->ItemExtras.pColorRemap = pItemExtras->pColorRemap;			
   2888 		pChatSaveNew->next = NULL;
   2889 
   2890 		if( pChatSaveLast )
   2891 			pChatSaveLast->next = pChatSaveNew;
   2892 		else
   2893 			pChatSaveList = pChatSaveNew;
   2894 		pChatSaveLast = pChatSaveNew;
   2895 	}
   2896 }
   2897 
   2898 //***********************************************************************************************
   2899 void WolapiObject::RestoreChat()
   2900 {
   2901 	//	See SaveChat()...
   2902 	CHATSAVE* pChatSave = pChatSaveList;
   2903 	while( pChatSave )
   2904 	{
   2905 		PrintMessage( pChatSave->szText, pChatSave->ItemExtras.pColorRemap );
   2906 		pChatSave = pChatSave->next;
   2907 	}
   2908 }
   2909 
   2910 //***********************************************************************************************
   2911 void WolapiObject::AddHostLeftMessageToSavedChat( const char* szName )
   2912 {
   2913 	CHATSAVE* pChatSaveNew;
   2914 	pChatSaveNew = new CHATSAVE;
   2915 	sprintf( pChatSaveNew->szText, TXT_WOL_HOSTLEFTGAME, szName );
   2916 	pChatSaveNew->ItemExtras.pColorRemap = &ColorRemaps[ WOLCOLORREMAP_LOCALMACHINEMESS ];
   2917 	pChatSaveNew->next = NULL;
   2918 	if( pChatSaveLast )
   2919 		pChatSaveLast->next = pChatSaveNew;
   2920 	else
   2921 		pChatSaveList = pChatSaveNew;
   2922 	pChatSaveLast = pChatSaveNew;
   2923 }
   2924 
   2925 //***********************************************************************************************
   2926 void WolapiObject::AddMessageToSavedChat( const char* szMessage )
   2927 {
   2928 	CHATSAVE* pChatSaveNew;
   2929 	pChatSaveNew = new CHATSAVE;
   2930 	strcpy( pChatSaveNew->szText, szMessage );
   2931 	pChatSaveNew->ItemExtras.pColorRemap = &ColorRemaps[ WOLCOLORREMAP_LOCALMACHINEMESS ];
   2932 	pChatSaveNew->next = NULL;
   2933 	if( pChatSaveLast )
   2934 		pChatSaveLast->next = pChatSaveNew;
   2935 	else
   2936 		pChatSaveList = pChatSaveNew;
   2937 	pChatSaveLast = pChatSaveNew;
   2938 }
   2939 
   2940 //***********************************************************************************************
   2941 void WolapiObject::DeleteSavedChat()
   2942 {
   2943 	//	See SaveChat()...
   2944 	CHATSAVE* pChatSaveNext;
   2945 	while( pChatSaveList )
   2946 	{
   2947 		pChatSaveNext = pChatSaveList->next;
   2948 		delete pChatSaveList;
   2949 		pChatSaveList = pChatSaveNext;
   2950 	}
   2951 }
   2952 
   2953 //***********************************************************************************************
   2954 void WolapiObject::GenericErrorMessage()
   2955 {
   2956 	//	Displays generic "something bad happened" error message.
   2957 	bPump_In_Call_Back = true;
   2958 	WWMessageBox().Process( TXT_WOL_ERRORMESSAGE );
   2959 	bPump_In_Call_Back = false;
   2960 }
   2961 
   2962 //***********************************************************************************************
   2963 bool WolapiObject::GetNameOfBeginningLobby( char* szNameToSet )
   2964 {
   2965 	//	Checks for game lobbies, sets szNameToSet to the channel name that the new user should enter and returns true if succeeds.
   2966 	if( !GetLobbyChannels() )
   2967 		return false;
   2968 
   2969 	//	Chatsink should now have a list of lobbies.
   2970 	int iCount = 0;
   2971 	Channel* pChannel = pChatSink->pChannelList;
   2972 	if( !pChannel )
   2973 		//	List is empty.
   2974 		return false;
   2975 
   2976 	//	Return the name of the first lobby with less than 50 users.
   2977 	while( pChannel )
   2978 	{
   2979 		if( pChannel->currentUsers < 50 )
   2980 		{
   2981 			strcpy( szNameToSet, (char*)pChannel->name );
   2982 			return true;
   2983 		}
   2984 		++iCount;
   2985 		pChannel = pChannel->next;
   2986 	}
   2987 
   2988 	//	All lobbies have 50 or more users. So just choose a random one.
   2989 	int iChoice = ( rand() % iCount );
   2990 	pChannel = pChatSink->pChannelList;
   2991 	for( int i = 0; i != iChoice; i++ )
   2992 		pChannel = pChannel->next;
   2993 
   2994 	strcpy( szNameToSet, (char*)pChannel->name );
   2995 
   2996 	return true;
   2997 }
   2998 
   2999 //***********************************************************************************************
   3000 bool WolapiObject::GetLobbyChannels()
   3001 {
   3002 	//	Modal version of UpdateChannels, for fetching lobby names.
   3003 
   3004 //	//	Returns false upon total failure.	ajxxx do same for other calls
   3005 //	WWMessageBox().Process( TXT_WOL_WAIT, TXT_NONE );
   3006 
   3007 	pChatSink->bRequestChannelListForLobbiesWait = true;
   3008 	pChatSink->ChannelFilter = CHANNELFILTER_LOBBIES;
   3009 
   3010 //	debugprint( "RequestChannelList() for lobbies\n" );
   3011 	if( !SUCCEEDED( pChat->RequestChannelList( 0, false ) ) )
   3012 	{
   3013 //		debugprint( "RequestChannelList() call failed\n" );
   3014 		return false;
   3015 	}
   3016 
   3017 	DWORD dwTimeStart = timeGetTime();
   3018 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   3019 	while( pChatSink->bRequestChannelListForLobbiesWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   3020 	{
   3021 		while( timeGetTime() < dwTimeNextPump )
   3022 			Call_Back();
   3023 		pChat->PumpMessages();
   3024 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   3025 	}
   3026 
   3027 	if( pChatSink->bRequestChannelListForLobbiesWait )
   3028 		return false;
   3029 
   3030 	return true;
   3031 }
   3032 
   3033 //***********************************************************************************************
   3034 const char* WolapiObject::pGameHostName()
   3035 {
   3036 	//	Returns a POINTER (careful - temporary!) to the name of the creator of the game channel we're in, or null.
   3037 	//	Uses players' list as its means of reference.
   3038 	if( pILPlayers )
   3039 	{
   3040 		for( int i = 0; i != pILPlayers->Count(); i++ )
   3041 		{
   3042 			User* pUser = (User*)pILPlayers->Get_Item_ExtraDataPtr( i );
   3043 			if( pUser && pUser->flags & CHAT_USER_CHANNELOWNER )
   3044 				return (char*)pUser->name;
   3045 		}
   3046 	}
   3047 	return NULL;
   3048 }
   3049 
   3050 //***********************************************************************************************
   3051 User* WolapiObject::pGameHost()
   3052 {
   3053 	//	Returns a POINTER (careful - temporary!) to the creator of the game channel we're in, or null.
   3054 	//	Uses players' list as its means of reference.
   3055 	if( pILPlayers )
   3056 	{
   3057 		for( int i = 0; i != pILPlayers->Count(); i++ )
   3058 		{
   3059 			User* pUser = (User*)pILPlayers->Get_Item_ExtraDataPtr( i );
   3060 			if( pUser && pUser->flags & CHAT_USER_CHANNELOWNER )
   3061 				return pUser;
   3062 		}
   3063 	}
   3064 	return NULL;
   3065 }
   3066 
   3067 //***********************************************************************************************
   3068 bool WolapiObject::SendGameOpt( const char* szSend, User* pUserPriv )
   3069 {
   3070 	//	Used during game setup to send public or private game options string.
   3071 	//	If pUserPriv is NULL, message is public, else private to pUserPriv.
   3072 	if( !pUserPriv )
   3073 	{
   3074 //		debugprint( "Send public game opt: '%s'\n", szSend );
   3075 		if( !SUCCEEDED( pChat->RequestPublicGameOptions( szSend ) ) )
   3076 		{
   3077 //			debugprint( "RequestPublicGameOptions() call failed\n" );
   3078 			return false;
   3079 		}
   3080 	}
   3081 	else
   3082 	{
   3083 //		debugprint( "Send private game opt to %s: '%s'\n", (char*)pUserPriv->name, szSend );
   3084 		if( !SUCCEEDED( pChat->RequestPrivateGameOptions( pUserPriv, szSend ) ) )
   3085 		{
   3086 //			debugprint( "RequestPrivateGameOptions() call failed\n" );
   3087 			return false;
   3088 		}
   3089 	}
   3090 	return true;
   3091 }
   3092 
   3093 //***********************************************************************************************
   3094 bool WolapiObject::RequestGameStart()
   3095 {
   3096 	//	Host is starting a game.
   3097 
   3098 /*
   3099 	//	Block any users that join the channel in the next microsecond from becoming involved, and
   3100 	//	block any users that leave from being recognized as having left.
   3101 	//what if someone leaves?
   3102 	//	This is done to preserve the integrity of the ChatSink's user list
   3103 	pWO->pChatSink->bIgnoreJoin = true;
   3104 */
   3105 	pChatSink->bRequestGameStartWait = true;
   3106 
   3107 //	debugprint( "RequestGameStart()\n" );
   3108 	if( !SUCCEEDED( pChat->RequestGameStart( pChatSink->pUserList ) ) )
   3109 	{
   3110 //		debugprint( "RequestGameStart() call failed\n" );
   3111 		return false;
   3112 	}
   3113 
   3114 	DWORD dwTimeStart = timeGetTime();
   3115 	DWORD dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   3116 	while( pChatSink->bRequestGameStartWait && timeGetTime() - dwTimeStart < EMERGENCY_TIMEOUT )
   3117 	{
   3118 		while( timeGetTime() < dwTimeNextPump )
   3119 			Call_Back();
   3120 		pChat->PumpMessages();
   3121 		dwTimeNextPump = timeGetTime() + PUMPSLEEPDURATION;
   3122 	}
   3123 
   3124 	if( pChatSink->bRequestGameStartWait )
   3125 	{
   3126 //		debugprint( "WolapiObject::RequestGameStart returning false\n" );
   3127 		pChatSink->bRequestGameStartWait = false;
   3128 		return false;
   3129 	}
   3130 
   3131 //	debugprint( "WolapiObject::RequestGameStart returning true\n" );
   3132 	return true;
   3133 }
   3134 
   3135 //***********************************************************************************************
   3136 bool WolapiObject::SendGo( const char* szSend )
   3137 {
   3138 	//	Send a "GO" message to all players included in the list that came back from OnGameStart.
   3139 	//	(Don't just broadcast it. We don't want to include any users that may have joined the channel
   3140 	//	in the last microsecond.)
   3141 //	debugprint( "SendGo()\n" );
   3142 
   3143 	User* pUser = pChatSink->pGameUserList;
   3144 
   3145 	while( pUser )
   3146 	{
   3147 //		if( !( pUser->flags & CHAT_USER_MYSELF ) )			Method changed. I now wait for go message to bounce back to me.
   3148 //		{
   3149 //			debugprint( "Send private game opt to %s: '%s'\n", (char*)pUser->name, szSend );
   3150 			if( !SUCCEEDED( pChat->RequestPrivateGameOptions( pUser, szSend ) ) )
   3151 			{
   3152 //				debugprint( "RequestPrivateGameOptions() call failed\n" );
   3153 				return false;
   3154 			}
   3155 //		}
   3156 		pUser = pUser->next;
   3157 	}
   3158 	return true;
   3159 }
   3160 
   3161 //***********************************************************************************************
   3162 void WolapiObject::Init_DisconnectPinging()
   3163 {
   3164 	//	Sets us up to begin "disconnect pinging" - the pinging that occurs when connection is broken
   3165 	//	during a tournament game. The idea is to try and figure out who is responsible for the connection
   3166 	//	going down. We do this by repeatedly pinging the opponent and the game results server. The number
   3167 	//	of successful pings is sent in the game results package.
   3168 	iDisconnectPingCurrent = 0;
   3169 	for( int i = 0; i != DISCONNECT_PING_COUNT; ++i )
   3170 	{
   3171 		DisconnectPingResult_Server[ i ] = PING_UNSTARTED;
   3172 		DisconnectPingResult_Opponent[ i ] = PING_UNSTARTED;
   3173 	}
   3174 	bDisconnectPingingCompleted = false;
   3175 	bDoingDisconnectPinging = true;
   3176 }
   3177 
   3178 //***********************************************************************************************
   3179 bool WolapiObject::Pump_DisconnectPinging()
   3180 {
   3181 	//	Called repeatedly and continuously when it seems a tournament game connection with the opponent
   3182 	//	has been broken. Does PumpMessages() and requests new pings when previous results have been received.
   3183 	//	Returns true when the required number of pings have been completed.
   3184 
   3185 	if( ::timeGetTime() > dwTimeNextWolapiPump )
   3186 	{
   3187 		pChat->PumpMessages();
   3188 		pNetUtil->PumpMessages();
   3189 		dwTimeNextWolapiPump = ::timeGetTime() + WOLAPIPUMPWAIT;
   3190 	}
   3191 
   3192 	switch( DisconnectPingResult_Server[ iDisconnectPingCurrent ] )
   3193 	{
   3194 	case PING_UNSTARTED:
   3195 		//	Pings have yet to be requested.
   3196 		//	Ping game results server.
   3197 		int iUnused;
   3198 		if( *szGameResServerHost1 )
   3199 		{
   3200 //			debugprint( "RequestPing ( gameres server )\n" );
   3201 			if( pNetUtil->RequestPing( szGameResServerHost1, 1000, &iUnused ) != S_OK )
   3202 			{
   3203 //				debugprint( "RequestPing() ( gameres server ) failed\n" );
   3204 				DisconnectPingResult_Server[ iDisconnectPingCurrent ] = PING_BAD;
   3205 			}
   3206 			DisconnectPingResult_Server[ iDisconnectPingCurrent ] = PING_WAITING;
   3207 		}
   3208 		else
   3209 			//	We never got an address for the gameresults server. Fake fail result.
   3210 			DisconnectPingResult_Server[ iDisconnectPingCurrent ] = PING_BAD;
   3211 
   3212 		//	Ping opponent.
   3213 		in_addr inaddr;
   3214 		char* szIP;
   3215 		inaddr.s_addr = TournamentOpponentIP;
   3216 		szIP = inet_ntoa( inaddr );
   3217 //		debugprint( "RequestPing ( opponent )\n" );
   3218 		if( pNetUtil->RequestPing( szIP, 1000, &iUnused ) != S_OK )
   3219 		{
   3220 //			debugprint( "RequestPing() ( opponent ) failed\n" );
   3221 			DisconnectPingResult_Opponent[ iDisconnectPingCurrent ] = PING_BAD;
   3222 		}
   3223 		else
   3224 			DisconnectPingResult_Opponent[ iDisconnectPingCurrent ] = PING_WAITING;
   3225 		break;
   3226 	case PING_WAITING:
   3227 		//	Ping results still pending. (Callback will set vars when results arrive.)
   3228 		break;
   3229 	default:
   3230 		//	Ping result for server is in.
   3231 		if( DisconnectPingResult_Opponent[ iDisconnectPingCurrent ] == PING_WAITING )
   3232 			break;
   3233 		//	Both results are in. Begin new ping, or end disconnect pinging.
   3234 		iDisconnectPingCurrent++;
   3235 		if( iDisconnectPingCurrent == DISCONNECT_PING_COUNT )
   3236 		{
   3237 			bDisconnectPingingCompleted = true;
   3238 			bDoingDisconnectPinging = false;
   3239 			return true;
   3240 		}
   3241 		break;
   3242 	}
   3243 	return false;
   3244 }
   3245 
   3246 //***********************************************************************************************
   3247 void WolapiObject::DisconnectPingResultsString( char* szStringToSet )
   3248 {
   3249 	int iGoodServerPings = 0;
   3250 	int iGoodPlayerPings = 0;
   3251 	for( int i = 0; i < DISCONNECT_PING_COUNT; ++i )
   3252 	{
   3253 		if( DisconnectPingResult_Server[ i ] == PING_GOOD )			++iGoodServerPings;
   3254 		if( DisconnectPingResult_Opponent[ i ] == PING_GOOD )		++iGoodPlayerPings;
   3255 	}
   3256 
   3257 	sprintf( szStringToSet, "%1u/%1u %1u/%1u", iGoodServerPings, DISCONNECT_PING_COUNT, iGoodPlayerPings, DISCONNECT_PING_COUNT );
   3258 }
   3259 
   3260 //***********************************************************************************************
   3261 void WolapiObject::SetOptionDefaults()
   3262 {
   3263 	//	Get stored defaults for options.
   3264 	HKEY hKey;
   3265 	if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, Game_Registry_Key(), 0, KEY_READ, &hKey ) == ERROR_SUCCESS )
   3266 	{
   3267 		DWORD dwValue;
   3268 		DWORD dwBufSize = sizeof( DWORD );
   3269 		if( RegQueryValueEx( hKey, "WOLAPI Find Enabled", 0, NULL, (LPBYTE)&dwValue, &dwBufSize ) != ERROR_SUCCESS )
   3270 			bFindEnabled = true;
   3271 		else
   3272 			bFindEnabled = (bool)dwValue;
   3273 		if( RegQueryValueEx( hKey, "WOLAPI Page Enabled", 0, NULL, (LPBYTE)&dwValue, &dwBufSize ) != ERROR_SUCCESS )
   3274 			bPageEnabled = true;
   3275 		else
   3276 			bPageEnabled = (bool)dwValue;
   3277 		if( RegQueryValueEx( hKey, "WOLAPI Lang Filter", 0, NULL, (LPBYTE)&dwValue, &dwBufSize ) != ERROR_SUCCESS )
   3278 			bLangFilter = true;
   3279 		else
   3280 			bLangFilter = (bool)dwValue;
   3281 		if( RegQueryValueEx( hKey, "WOLAPI Show All Games", 0, NULL, (LPBYTE)&dwValue, &dwBufSize ) != ERROR_SUCCESS )
   3282 			bAllGamesShown = true;
   3283 		else
   3284 			bAllGamesShown = (bool)dwValue;
   3285 
   3286 		RegCloseKey( hKey );
   3287 	}
   3288 	pChat->SetFindPage( bFindEnabled, bPageEnabled );
   3289 	pChat->SetLangFilter( bLangFilter );
   3290 }
   3291 
   3292 //***********************************************************************************************
   3293 void WolapiObject::SetOptions( bool bEnableFind, bool bEnablePage, bool bLangFilterOn, bool bShowAllGames )
   3294 {
   3295 	//	Set options and remember them in registry.
   3296 
   3297 	bFindEnabled = bEnableFind;
   3298 	bPageEnabled = bEnablePage;
   3299 	bLangFilter = bLangFilterOn;
   3300 	bAllGamesShown = bShowAllGames;
   3301 
   3302 	HKEY hKey;
   3303 	if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, Game_Registry_Key(), 0, KEY_WRITE, &hKey ) == ERROR_SUCCESS )
   3304 	{
   3305 		DWORD dwValue = bFindEnabled ? 1 : 0;
   3306 		RegSetValueEx( hKey, "WOLAPI Find Enabled", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof( dwValue ) );
   3307 		dwValue = bPageEnabled ? 1 : 0;
   3308 		RegSetValueEx( hKey, "WOLAPI Page Enabled", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof( dwValue ) );
   3309 		dwValue = bLangFilter ? 1 : 0;
   3310 		RegSetValueEx( hKey, "WOLAPI Lang Filter", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof( dwValue ) );
   3311 		dwValue = bAllGamesShown ? 1 : 0;
   3312 		RegSetValueEx( hKey, "WOLAPI Show All Games", 0, REG_DWORD, (LPBYTE)&dwValue, sizeof( dwValue ) );
   3313 
   3314 		RegCloseKey( hKey );
   3315 	}
   3316 	pChat->SetFindPage( bFindEnabled, bPageEnabled );
   3317 	pChat->SetLangFilter( bLangFilter );
   3318 }
   3319 
   3320 //***********************************************************************************************
   3321 HPALETTE GetCurrentScreenPalette()
   3322 {
   3323 	//	Get the palette of the current screen.
   3324 	//	Returns 0 if can't get palette.
   3325 	//	Remember to DeleteObject the HPALETTE if non-zero.
   3326 	GraphicViewPortClass draw_window(	LogicPage->Get_Graphic_Buffer(),
   3327 										WindowList[WINDOW_MAIN][WINDOWX] + LogicPage->Get_XPos(),
   3328 										WindowList[WINDOW_MAIN][WINDOWY] + LogicPage->Get_YPos(),
   3329 										WindowList[WINDOW_MAIN][WINDOWWIDTH],
   3330 										WindowList[WINDOW_MAIN][WINDOWHEIGHT] );
   3331 	LPDIRECTDRAWSURFACE lpDDS = draw_window.Get_Graphic_Buffer()->Get_DD_Surface();
   3332 	LPDIRECTDRAWPALETTE lpDDP;
   3333 	HPALETTE hPal = 0;
   3334 	if( lpDDS->GetPalette( &lpDDP ) == DD_OK )
   3335 	{
   3336 		PALETTEENTRY pe[256];
   3337 		if( lpDDP->GetEntries( 0, 0, 256, pe ) == DD_OK )
   3338 		{
   3339 			LOGPALETTE* pLogPal = (LOGPALETTE*)new char[ sizeof( LOGPALETTE ) + sizeof( PALETTEENTRY ) * 255 ];
   3340 			pLogPal->palVersion = 0x300;
   3341 			pLogPal->palNumEntries = 256;
   3342 			for( int i = 0; i != 256; i++ )
   3343 			{
   3344 				pLogPal->palPalEntry[i].peRed = pe[i].peRed;
   3345 				pLogPal->palPalEntry[i].peGreen = pe[i].peGreen;
   3346 				pLogPal->palPalEntry[i].peBlue = pe[i].peBlue;
   3347 				pLogPal->palPalEntry[i].peFlags = 0;
   3348 //				debugprint( "DD Palette %03u: %03u, %03u, %03u\n", i, pe[i].peRed, pe[i].peGreen, pe[i].peBlue );
   3349 			}
   3350 			hPal = CreatePalette( pLogPal );
   3351 			delete [] pLogPal;
   3352 		}
   3353 //		else
   3354 //			debugprint( "DD GetEntries failed.\n" );
   3355 	}
   3356 //	else
   3357 //		debugprint( "DD GetPalette failed.\n" );
   3358 	
   3359 	return hPal;
   3360 }
   3361 
   3362 //***********************************************************************************************
   3363 void RemapDIBToPalette( HPALETTE hPal, const char* pDIB )		//	Note: pDIB is treated as non-const.
   3364 {
   3365 	//	Converts pDIB's actual pixel data to proper values for a different palette (hPal).
   3366 	//	Obeys convention that index 0 in the DIB's palette should map to transparent. For our purposes, make it black.
   3367 	
   3368 	//	Set the values of the qNewPalette array to hold the new destination palette index we want 
   3369 	//	a bmp palette entry to map to.
   3370 	unsigned char qNewPalette[ 256 ];
   3371 
   3372 	LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)pDIB;
   3373 
   3374 	RGBQUAD* pRGBEntry = (RGBQUAD*)( (char*)lpbi + lpbi->biSize );		//	This now points to the first entry in the bmp's palette table.
   3375 
   3376 //	debugprint( "Starting rgbquads at %i\n", pRGBEntry );
   3377 
   3378 	//	Index zero is supposed to be transparent. In our case, that means make it black.
   3379 	qNewPalette[0] = GetNearestPaletteIndex( hPal, RGB( 0, 0, 0 ) );
   3380 	pRGBEntry++;
   3381 
   3382 	for( int i = 1; i != 256; i++ )
   3383 	{
   3384 		qNewPalette[i] = GetNearestPaletteIndex( hPal, RGB( pRGBEntry->rgbRed, pRGBEntry->rgbGreen, pRGBEntry->rgbBlue ) );
   3385 //		if( iIndex == 1 )
   3386 //			debugprint( "Remapping bmp %03u to new %03u\n", i, qNewPalette[i] );
   3387 		pRGBEntry++;
   3388 	}
   3389 
   3390 	//	Convert the data to values that match game palette.
   3391 	int iWidth = DIBWidth( pDIB );
   3392 	int iHeight = DIBHeight( pDIB );
   3393 	int iSrcPitch = ( iWidth + 3 ) & ~3;
   3394 	int iLength = iSrcPitch * iHeight;
   3395 	unsigned char* pBits = (unsigned char*)FindDIBBits( pDIB );
   3396 //	debugprint( "First Byte value %03u will become %03u\n", *pBits, qNewPalette[ *pBits ] );
   3397 	for( i = 0; i != iLength; i++ )
   3398 	{
   3399 		*pBits++ = qNewPalette[ *pBits ];
   3400 	}
   3401 }
   3402 
   3403 /*
   3404 //***********************************************************************************************
   3405 char* LoadFileIntoMemory( const char* szFileName, int& iLength )
   3406 {
   3407 	//	Loads a file into a buffer.
   3408 	//	Delete[] the pointer when you're done with the buffer.
   3409 	HANDLE hFile;
   3410 	hFile = CreateFile( szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
   3411 	if( hFile == INVALID_HANDLE_VALUE )
   3412 		return NULL;
   3413 	iLength = GetFileSize( hFile, NULL );
   3414 	char* pData = new char[ iLength ];
   3415 	DWORD dwBytesRead;
   3416 	ReadFile( hFile, pData, iLength, &dwBytesRead, NULL );
   3417 	if( dwBytesRead != iLength )
   3418 //		debugprint( "######LoadFileIntoMemory expected %i bytes and got %i\n", iLength, dwBytesRead );
   3419 	CloseHandle( hFile );
   3420 	return pData;
   3421 }
   3422 */
   3423 
   3424 //***********************************************************************************************
   3425 void HostNameFromGameChannelName( char* szNameToSet, const char* szChannelName )
   3426 {
   3427 	int iApostrophe = strcspn( szChannelName, "'" );
   3428 	memcpy( szNameToSet, szChannelName, iApostrophe );
   3429 	szNameToSet[ iApostrophe ] = 0;
   3430 }
   3431 
   3432 #endif