1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8 
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 #include "sys/platform.h"
30 #include "idlib/BitMsg.h"
31 #include "idlib/Str.h"
32 #include "idlib/LangDict.h"
33 #include "framework/async/NetworkSystem.h"
34 #include "framework/FileSystem.h"
35 #include "framework/DeclEntityDef.h"
36 #include "ui/UserInterface.h"
37 
38 #include "gamesys/SysCvar.h"
39 #include "Player.h"
40 #include "Game_local.h"
41 
42 #include "MultiplayerGame.h"
43 
44 // could be a problem if players manage to go down sudden deaths till this .. oh well
45 #define LASTMAN_NOLIVES -20
46 
47 idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" );
48 
49 // global sounds transmitted by index - 0 .. SND_COUNT
50 // sounds in this list get precached on MP start
51 const char *idMultiplayerGame::GlobalSoundStrings[] = {
52 	"sound/feedback/voc_youwin.wav",
53 	"sound/feedback/voc_youlose.wav",
54 	"sound/feedback/fight.wav",
55 	"sound/feedback/vote_now.wav",
56 	"sound/feedback/vote_passed.wav",
57 	"sound/feedback/vote_failed.wav",
58 	"sound/feedback/three.wav",
59 	"sound/feedback/two.wav",
60 	"sound/feedback/one.wav",
61 	"sound/feedback/sudden_death.wav",
62 #ifdef CTF
63 	"sound/ctf/flag_capped_yours.wav",
64 	"sound/ctf/flag_capped_theirs.wav",
65 	"sound/ctf/flag_return.wav",
66 	"sound/ctf/flag_taken_yours.wav",
67 	"sound/ctf/flag_taken_theirs.wav",
68 	"sound/ctf/flag_dropped_yours.wav",
69 	"sound/ctf/flag_dropped_theirs.wav"
70 #endif
71 };
72 
73 // handy verbose
74 const char *idMultiplayerGame::GameStateStrings[] = {
75 	"INACTIVE",
76 	"WARMUP",
77 	"COUNTDOWN",
78 	"GAMEON",
79 	"SUDDENDEATH",
80 	"GAMEREVIEW",
81 	"NEXTGAME"
82 };
83 
84 const char *idMultiplayerGame::MPGuis[] = {
85 	"guis/mphud.gui",
86 	"guis/mpmain.gui",
87 	"guis/mpmsgmode.gui",
88 	"guis/netmenu.gui",
89 	NULL
90 };
91 
92 const char *idMultiplayerGame::ThrottleVars[] = {
93 	"ui_spectate",
94 	"ui_ready",
95 	"ui_team",
96 	NULL
97 };
98 
99 const char *idMultiplayerGame::ThrottleVarsInEnglish[] = {
100 	"#str_06738",
101 	"#str_06737",
102 	"#str_01991",
103 	NULL
104 };
105 
106 const int idMultiplayerGame::ThrottleDelay[] = {
107 	8,
108 	5,
109 	5
110 };
111 
112 /*
113 ================
114 idMultiplayerGame::idMultiplayerGame
115 ================
116 */
idMultiplayerGame()117 idMultiplayerGame::idMultiplayerGame() {
118 	scoreBoard = NULL;
119 	spectateGui = NULL;
120 	guiChat = NULL;
121 	mainGui = NULL;
122 	mapList = NULL;
123 	msgmodeGui = NULL;
124 	lastGameType = GAME_SP;
125 
126 #ifdef CTF
127 	teamFlags[0] = NULL;
128 	teamFlags[1] = NULL;
129 
130 	teamPoints[0] = 0;
131 	teamPoints[1] = 0;
132 
133 	flagMsgOn = true;
134 
135 	player_blue_flag = -1;
136 	player_red_flag = -1;
137 #endif
138 
139 	Clear();
140 }
141 
142 /*
143 ================
144 idMultiplayerGame::Shutdown
145 ================
146 */
Shutdown(void)147 void idMultiplayerGame::Shutdown( void ) {
148 	Clear();
149 }
150 
151 /*
152 ================
153 idMultiplayerGame::SetMenuSkin
154 ================
155 */
SetMenuSkin(void)156 void idMultiplayerGame::SetMenuSkin( void ) {
157 	// skins
158 	idStr str = cvarSystem->GetCVarString( "mod_validSkins" );
159 	idStr uiSkin = cvarSystem->GetCVarString( "ui_skin" );
160 	idStr skin;
161 	int skinId = 1;
162 	int count = 1;
163 	while ( str.Length() ) {
164 		int n = str.Find( ";" );
165 		if ( n >= 0 ) {
166 			skin = str.Left( n );
167 			str = str.Right( str.Length() - n - 1 );
168 		} else {
169 			skin = str;
170 			str = "";
171 		}
172 		if ( skin.Icmp( uiSkin ) == 0 ) {
173 			skinId = count;
174 		}
175 		count++;
176 	}
177 
178 	for ( int i = 0; i < count; i++ ) {
179 		mainGui->SetStateInt( va( "skin%i", i+1 ), 0 );
180 	}
181 	mainGui->SetStateInt( va( "skin%i", skinId ), 1 );
182 }
183 
184 /*
185 ================
186 idMultiplayerGame::Reset
187 ================
188 */
Reset()189 void idMultiplayerGame::Reset() {
190 	Clear();
191 	assert( !scoreBoard && !spectateGui && !guiChat && !mainGui && !mapList );
192 
193 #ifdef CTF
194 	// CTF uses its own scoreboard
195 	if ( IsGametypeFlagBased() )
196 		scoreBoard = uiManager->FindGui( "guis/ctfscoreboard.gui", true, false, true );
197 	else
198 #endif
199 	scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true );
200 
201 	spectateGui = uiManager->FindGui( "guis/spectate.gui", true, false, true );
202 	guiChat = uiManager->FindGui( "guis/chat.gui", true, false, true );
203 	mainGui = uiManager->FindGui( "guis/mpmain.gui", true, false, true );
204 	mapList = uiManager->AllocListGUI( );
205 	mapList->Config( mainGui, "mapList" );
206 	// set this GUI so that our Draw function is still called when it becomes the active/fullscreen GUI
207 	mainGui->SetStateBool( "gameDraw", true );
208 	mainGui->SetKeyBindingNames();
209 	mainGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) );
210 	SetMenuSkin();
211 	msgmodeGui = uiManager->FindGui( "guis/mpmsgmode.gui", true, false, true );
212 	msgmodeGui->SetStateBool( "gameDraw", true );
213 	ClearGuis();
214 	ClearChatData();
215 	warmupEndTime = 0;
216 }
217 
218 /*
219 ================
220 idMultiplayerGame::ServerClientConnect
221 ================
222 */
ServerClientConnect(int clientNum)223 void idMultiplayerGame::ServerClientConnect( int clientNum ) {
224 	memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
225 }
226 
227 /*
228 ================
229 idMultiplayerGame::SpawnPlayer
230 ================
231 */
SpawnPlayer(int clientNum)232 void idMultiplayerGame::SpawnPlayer( int clientNum ) {
233 
234 	bool ingame = playerState[ clientNum ].ingame;
235 
236 	memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
237 	if ( !gameLocal.isClient ) {
238 		idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
239 		p->spawnedTime = gameLocal.time;
240 
241 		if ( IsGametypeTeamBased() ) {  /* CTF */
242 			SwitchToTeam( clientNum, -1, p->team );
243 		}
244 		p->tourneyRank = 0;
245 		if ( gameLocal.gameType == GAME_TOURNEY && gameState == GAMEON ) {
246 			p->tourneyRank++;
247 		}
248 		playerState[ clientNum ].ingame = ingame;
249 	}
250 }
251 
252 /*
253 ================
254 idMultiplayerGame::Clear
255 ================
256 */
Clear()257 void idMultiplayerGame::Clear() {
258 	int i;
259 
260 	gameState = INACTIVE;
261 	nextState = INACTIVE;
262 	pingUpdateTime = 0;
263 	vote = VOTE_NONE;
264 	voteTimeOut = 0;
265 	voteExecTime = 0;
266 	nextStateSwitch = 0;
267 	matchStartedTime = 0;
268 	currentTourneyPlayer[ 0 ] = -1;
269 	currentTourneyPlayer[ 1 ] = -1;
270 	one = two = three = false;
271 	memset( &playerState, 0 , sizeof( playerState ) );
272 	lastWinner = -1;
273 	currentMenu = 0;
274 	bCurrentMenuMsg = false;
275 	nextMenu = 0;
276 	pureReady = false;
277 	scoreBoard = NULL;
278 	spectateGui = NULL;
279 	guiChat = NULL;
280 	mainGui = NULL;
281 	msgmodeGui = NULL;
282 	if ( mapList ) {
283 		uiManager->FreeListGUI( mapList );
284 		mapList = NULL;
285 	}
286 	fragLimitTimeout = 0;
287 	memset( &switchThrottle, 0, sizeof( switchThrottle ) );
288 	voiceChatThrottle = 0;
289 	for ( i = 0; i < NUM_CHAT_NOTIFY; i++ ) {
290 		chatHistory[ i ].line.Clear();
291 	}
292 	warmupText.Clear();
293 	voteValue.Clear();
294 	voteString.Clear();
295 	startFragLimit = -1;
296 }
297 
298 /*
299 ================
300 idMultiplayerGame::ClearGuis
301 ================
302 */
ClearGuis()303 void idMultiplayerGame::ClearGuis() {
304 	int i;
305 
306 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
307 		scoreBoard->SetStateString( va( "player%i",i+1 ), "" );
308 		scoreBoard->SetStateString( va( "player%i_score", i+1 ), "" );
309 		scoreBoard->SetStateString( va( "player%i_tdm_tscore", i+1 ), "" );
310 		scoreBoard->SetStateString( va( "player%i_tdm_score", i+1 ), "" );
311 		scoreBoard->SetStateString( va( "player%i_wins", i+1 ), "" );
312 		scoreBoard->SetStateString( va( "player%i_status", i+1 ), "" );
313 		scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 );
314 		scoreBoard->SetStateInt( "rank_self", 0 );
315 
316 		idPlayer *player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
317 		if ( !player || !player->hud ) {
318 			continue;
319 		}
320 		player->hud->SetStateString( va( "player%i",i+1 ), "" );
321 		player->hud->SetStateString( va( "player%i_score", i+1 ), "" );
322 		player->hud->SetStateString( va( "player%i_ready", i+1 ), "" );
323 		scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 );
324 		player->hud->SetStateInt( "rank_self", 0 );
325 
326 	}
327 
328 #ifdef CTF
329 	ClearHUDStatus();
330 #endif
331 }
332 
333 #ifdef CTF
334 /*
335 ================
336 idMultiplayerGame::ClearHUDStatus
337 ================
338 */
ClearHUDStatus(void)339 void idMultiplayerGame::ClearHUDStatus( void ) {
340 	int i;
341 
342 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
343 
344 		idPlayer *player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
345 		if ( !player || !player->hud ) {
346 			continue;
347 		}
348 
349 		player->hud->SetStateInt( "red_flagstatus", 0 );
350 		player->hud->SetStateInt( "blue_flagstatus", 0 );
351 		if ( IsGametypeFlagBased())
352 			player->hud->SetStateInt( "self_team", player->team );
353 		else
354 			player->hud->SetStateInt( "self_team", -1 ); // Invisible.
355 	}
356 
357 }
358 
359 /*
360 ================
361 idMultiplayerGame::GetFlagPoints
362 
363 Gets number of captures in CTF game.
364 
365 0 = red team
366 1 = blue team
367 ================
368 */
GetFlagPoints(int team)369 int idMultiplayerGame::GetFlagPoints( int team )
370 {
371 	assert( team <= 1 );
372 
373 	return teamPoints[ team ];
374 }
375 #endif
376 
377 /*
378 ================
379 idMultiplayerGame::UpdatePlayerRanks
380 ================
381 */
UpdatePlayerRanks()382 void idMultiplayerGame::UpdatePlayerRanks() {
383 	int i, j, k;
384 	idPlayer *players[MAX_CLIENTS];
385 	idEntity *ent;
386 	idPlayer *player;
387 
388 	memset( players, 0, sizeof( players ) );
389 	numRankedPlayers = 0;
390 
391 	for ( i = 0; i < gameLocal.numClients; i++ ) {
392 		ent = gameLocal.entities[ i ];
393 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
394 			continue;
395 		}
396 		player = static_cast< idPlayer * >( ent );
397 		if ( !CanPlay( player ) ) {
398 			continue;
399 		}
400 		if ( gameLocal.gameType == GAME_TOURNEY ) {
401 			if ( i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] ) {
402 				continue;
403 			}
404 		}
405 		if ( gameLocal.gameType == GAME_LASTMAN && playerState[ i ].fragCount == LASTMAN_NOLIVES ) {
406 			continue;
407 		}
408 		for ( j = 0; j < numRankedPlayers; j++ ) {
409 			bool insert = false;
410 
411 			if ( IsGametypeTeamBased() ) { /* CTF */
412 				if ( player->team != players[ j ]->team ) {
413 					if ( playerState[ i ].teamFragCount > playerState[ players[ j ]->entityNumber ].teamFragCount ) {
414 						// team scores
415 						insert = true;
416 					} else if ( playerState[ i ].teamFragCount == playerState[ players[ j ]->entityNumber ].teamFragCount && player->team < players[ j ]->team ) {
417 						// at equal scores, sort by team number
418 						insert = true;
419 					}
420 				} else if ( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount ) {
421 					// in the same team, sort by frag count
422 					insert = true;
423 				}
424 			} else {
425 				insert = ( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount );
426 			}
427 			if ( insert ) {
428 				for ( k = numRankedPlayers; k > j; k-- ) {
429 					players[ k ] = players[ k-1 ];
430 				}
431 				players[ j ] = player;
432 				break;
433 			}
434 		}
435 		if ( j == numRankedPlayers ) {
436 			players[ numRankedPlayers ] = player;
437 		}
438 		numRankedPlayers++;
439 	}
440 
441 	memcpy( rankedPlayers, players, sizeof( players ) );
442 }
443 
444 
445 /*
446 ================
447 idMultiplayerGame::UpdateRankColor
448 ================
449 */
UpdateRankColor(idUserInterface * gui,const char * mask,int i,const idVec3 & vec)450 void idMultiplayerGame::UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ) {
451 	for ( int j = 1; j < 4; j++ ) {
452 		gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] );
453 	}
454 }
455 
456 /*
457 ================
458 idMultiplayerGame::UpdateScoreboard
459 ================
460 */
UpdateScoreboard(idUserInterface * scoreBoard,idPlayer * player)461 void idMultiplayerGame::UpdateScoreboard( idUserInterface *scoreBoard, idPlayer *player ) {
462 	int i, j, iline, k;
463 	idStr gameinfo;
464 #ifdef _D3XP
465 	idStr livesinfo;
466 	idStr timeinfo;
467 #endif
468 
469 	idEntity *ent;
470 	idPlayer *p;
471 	int value;
472 
473 	scoreBoard->SetStateString( "scoretext", gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04242" ) : common->GetLanguageDict()->GetString( "#str_04243" ) );
474 
475 	iline = 0; // the display lines
476 	if ( gameState != WARMUP ) {
477 		for ( i = 0; i < numRankedPlayers; i++ ) {
478 			// ranked player
479 			iline++;
480 			scoreBoard->SetStateString( va( "player%i", iline ), rankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ) );
481 			if ( IsGametypeTeamBased() ) { /* CTF */
482 				value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
483 				scoreBoard->SetStateInt( va( "player%i_tdm_score", iline ), value );
484 				value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].teamFragCount );
485 				scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), va( "/ %i", value ) );
486 				scoreBoard->SetStateString( va( "player%i_score", iline ), "" );
487 			} else {
488 				value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
489 				scoreBoard->SetStateInt( va( "player%i_score", iline ), value );
490 				scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
491 				scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
492 			}
493 
494 			value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ rankedPlayers[ i ]->entityNumber ].wins );
495 			scoreBoard->SetStateInt( va( "player%i_wins", iline ), value );
496 			scoreBoard->SetStateInt( va( "player%i_ping", iline ), playerState[ rankedPlayers[ i ]->entityNumber ].ping );
497 			// set the color band
498 			scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
499 			UpdateRankColor( scoreBoard, "rank%i_color%i", iline, rankedPlayers[ i ]->colorBar );
500 			if ( rankedPlayers[ i ] == player ) {
501 				// highlight who we are
502 				scoreBoard->SetStateInt( "rank_self", iline );
503 			}
504 		}
505 	}
506 
507 	// if warmup, this draws everyone, otherwise it goes over spectators only
508 	// when doing warmup we loop twice to draw ready/not ready first *then* spectators
509 	// NOTE: in tourney, shows spectators according to their playing rank order?
510 	for ( k = 0; k < ( gameState == WARMUP ? 2 : 1 ); k++ ) {
511 		for ( i = 0; i < MAX_CLIENTS; i++ ) {
512 			ent = gameLocal.entities[ i ];
513 			if ( !ent || !ent->IsType( idPlayer::Type ) ) {
514 				continue;
515 			}
516 			if ( gameState != WARMUP ) {
517 				// check he's not covered by ranks already
518 				for ( j = 0; j < numRankedPlayers; j++ ) {
519 					if ( ent == rankedPlayers[ j ] ) {
520 						break;
521 					}
522 				}
523 				if ( j != numRankedPlayers ) {
524 					continue;
525 				}
526 			}
527 			p = static_cast< idPlayer * >( ent );
528 			if ( gameState == WARMUP ) {
529 				if ( k == 0 && p->spectating ) {
530 					continue;
531 				}
532 				if ( k == 1 && !p->spectating ) {
533 					continue;
534 				}
535 			}
536 
537 			iline++;
538 			if ( !playerState[ i ].ingame ) {
539 				scoreBoard->SetStateString( va( "player%i", iline ), common->GetLanguageDict()->GetString( "#str_04244" ) );
540 				scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04245" ) );
541 				// no color band
542 				scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
543 			} else {
544 				scoreBoard->SetStateString( va( "player%i", iline ), gameLocal.userInfo[ i ].GetString( "ui_name" ) );
545 				if ( gameState == WARMUP ) {
546 					if ( p->spectating ) {
547 						scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04246" ) );
548 						// no color band
549 						scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
550 					} else {
551 						scoreBoard->SetStateString( va( "player%i_score", iline ), p->IsReady() ? common->GetLanguageDict()->GetString( "#str_04247" ) : common->GetLanguageDict()->GetString( "#str_04248" ) );
552 						// set the color band
553 						scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
554 						UpdateRankColor( scoreBoard, "rank%i_color%i", iline, p->colorBar );
555 					}
556 				} else {
557 					if ( gameLocal.gameType == GAME_LASTMAN && playerState[ i ].fragCount == LASTMAN_NOLIVES ) {
558 						scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_06736" ) );
559 						// set the color band
560 						scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
561 						UpdateRankColor( scoreBoard, "rank%i_color%i", iline, p->colorBar );
562 					} else {
563 						scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04246" ) );
564 						// no color band
565 						scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
566 					}
567 				}
568 			}
569 			scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
570 			scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
571 			scoreBoard->SetStateString( va( "player%i_wins", iline ), "" );
572 			scoreBoard->SetStateInt( va( "player%i_ping", iline ), playerState[ i ].ping );
573 			if ( i == player->entityNumber ) {
574 				// highlight who we are
575 				scoreBoard->SetStateInt( "rank_self", iline );
576 			}
577 		}
578 	}
579 
580 	// clear remaining lines (empty slots)
581 	iline++;
582 #ifdef _D3XP
583 	while ( iline < MAX_CLIENTS ) { //Max players is now 8
584 #else
585 	while ( iline < 5 ) {
586 #endif
587 		scoreBoard->SetStateString( va( "player%i", iline ), "" );
588 		scoreBoard->SetStateString( va( "player%i_score", iline ), "" );
589 		scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
590 		scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
591 		scoreBoard->SetStateString( va( "player%i_wins", iline ), "" );
592 		scoreBoard->SetStateString( va( "player%i_ping", iline ), "" );
593 		scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
594 		iline++;
595 	}
596 
597 	gameinfo = va( "%s: %s", common->GetLanguageDict()->GetString( "#str_02376" ), gameLocal.serverInfo.GetString( "si_gameType" ) );
598 	if ( gameLocal.gameType == GAME_LASTMAN ) {
599 		if ( gameState == GAMEON || gameState == SUDDENDEATH ) {
600 			livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_04264" ), startFragLimit );
601 		} else {
602 			livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_04264" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
603 		}
604 #ifdef CTF
605 	} else if ( gameLocal.gameType != GAME_CTF ) {
606 #else
607 	} else {
608 #endif
609 		livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_01982" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
610 	}
611 	if ( gameLocal.serverInfo.GetInt( "si_timeLimit" ) > 0 ) {
612 		timeinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_01983" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) );
613 	} else {
614 		timeinfo = va("%s", common->GetLanguageDict()->GetString( "#str_07209" ));
615 	}
616 	scoreBoard->SetStateString( "gameinfo", gameinfo );
617 	scoreBoard->SetStateString( "livesinfo", livesinfo );
618 	scoreBoard->SetStateString( "timeinfo", timeinfo );
619 
620 	scoreBoard->Redraw( gameLocal.time );
621 }
622 
623 #ifdef CTF
624 /*
625 ================
626 idMultiplayerGame::UpdateCTFScoreboard
627 ================
628 */
629 void idMultiplayerGame::UpdateCTFScoreboard( idUserInterface *scoreBoard, idPlayer *player ) {
630 	int i, j;
631 	idStr gameinfo;
632 	idEntity *ent;
633 	int value;
634 
635 	// The display lines
636 	int ilines[2] = {0,0};
637 
638 	// The team strings
639 	char redTeam[] = "red";
640 	char blueTeam[] = "blue";
641 	char *curTeam = NULL;
642 
643 	/* Word "frags" */
644 	scoreBoard->SetStateString( "scoretext", gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04242" ) : common->GetLanguageDict()->GetString( "#str_04243" ) );
645 
646 	// Blank the flag carrier on the scoreboard.  We update these in the loop below if necessary.
647 	if ( this->player_blue_flag == -1 )
648 		scoreBoard->SetStateInt( "player_blue_flag", 0 );
649 
650 	if ( this->player_red_flag == -1 )
651 		scoreBoard->SetStateInt( "player_red_flag", 0 );
652 
653 	if ( gameState != WARMUP ) {
654 		for ( i = 0; i < numRankedPlayers; i++ ) {
655 
656 			idPlayer *player = rankedPlayers[ i ];
657 			assert( player );
658 
659 			if ( player->team == 0 )
660 				curTeam = redTeam;
661 			else
662 				curTeam = blueTeam;
663 
664 			// Increase the appropriate iline
665 			assert( player->team <= 1 );
666 			ilines[ player->team ]++;
667 
668 
669 			// Update the flag status
670 			if ( this->player_blue_flag == player->entityNumber )
671 				scoreBoard->SetStateInt( "player_blue_flag", ilines[ player->team ] );
672 
673 			if ( player->team == 1 && this->player_red_flag == player->entityNumber )
674 				scoreBoard->SetStateInt( "player_red_flag", ilines[ player->team ] );
675 
676 
677 
678 			/* Player Name */
679 			scoreBoard->SetStateString( va( "player%i_%s", ilines[ player->team ], curTeam ), player->GetUserInfo()->GetString( "ui_name" ) );
680 
681 			if ( IsGametypeTeamBased() ) {
682 
683 				value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
684 				scoreBoard->SetStateInt( va( "player%i_%s_score", ilines[ player->team ], curTeam ), value );
685 
686 				/* Team score and score, blanked */
687 				scoreBoard->SetStateString( va( "player%i_%s_tscore", ilines[ player->team ], curTeam ), "" );
688 				//scoreBoard->SetStateString( va( "player%i_%s_score",  ilines[ player->team ], curTeam ), "" );
689 			}
690 
691 			/* Wins */
692 			value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ rankedPlayers[ i ]->entityNumber ].wins );
693 			scoreBoard->SetStateInt( va( "player%i_%s_wins", ilines[ player->team ], curTeam ), value );
694 
695 			/* Ping */
696 			scoreBoard->SetStateInt( va( "player%i_%s_ping", ilines[ player->team ], curTeam ), playerState[ rankedPlayers[ i ]->entityNumber ].ping );
697 		}
698 	}
699 
700 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
701 
702 		ent = gameLocal.entities[ i ];
703 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
704 			continue;
705 		}
706 
707 		if ( gameState != WARMUP ) {
708 			// check he's not covered by ranks already
709 			for ( j = 0; j < numRankedPlayers; j++ ) {
710 				if ( ent == rankedPlayers[ j ] ) {
711 					break;
712 				}
713 			}
714 
715 			if ( j != numRankedPlayers ) {
716 				continue;
717 			}
718 
719 		}
720 		player = static_cast< idPlayer * >( ent );
721 
722 		if ( player->spectating )
723 			continue;
724 
725 		if ( player->team == 0 )
726 			curTeam = redTeam;
727 		else
728 			curTeam = blueTeam;
729 
730 		ilines[ player->team ]++;
731 
732 
733 
734 
735 
736 		if ( !playerState[ i ].ingame ) {
737 
738 			/* "New Player" on player's name location */
739 			scoreBoard->SetStateString( va( "player%i_%s", ilines[ player->team ], curTeam ), common->GetLanguageDict()->GetString( "#str_04244" ) );
740 
741 			/* "Connecting" on player's score location */
742 			scoreBoard->SetStateString( va( "player%i_%s_score", ilines[ player->team ], curTeam ), common->GetLanguageDict()->GetString( "#str_04245" ) );
743 
744 
745 		} else {
746 
747 			/* Player's name in player's name location */
748 			if ( !player->spectating )
749 				scoreBoard->SetStateString( va( "player%i_%s", ilines[ player->team ], curTeam ), gameLocal.userInfo[ i ].GetString( "ui_name" ) );
750 
751 			if ( gameState == WARMUP ) {
752 
753 				if ( player->spectating ) {
754 
755 					/* "Spectating" on player's score location */
756 					scoreBoard->SetStateString( va( "player%i_%s_score", ilines[ player->team ], curTeam ), common->GetLanguageDict()->GetString( "#str_04246" ) );
757 
758 				} else {
759 
760 					/* Display "ready" in player's score location if they're ready.  Display nothing if not.  No room for 'not ready'.  */
761 					scoreBoard->SetStateString( va( "player%i_%s_score", ilines[ player->team ], curTeam ), player->IsReady() ? common->GetLanguageDict()->GetString( "#str_04247" ) : "" );
762 
763 				}
764 			}
765 		}
766 
767 	}
768 
769 	// Clear remaining slots
770 	for ( i = 0; i < 2; i++ )
771 	{
772 		if ( i )
773 			curTeam = blueTeam;
774 		else
775 			curTeam = redTeam;
776 
777 		for ( j = ilines[ i ]+1; j <= 8; j++ )
778 		{
779 			scoreBoard->SetStateString( va( "player%i_%s", j, curTeam ), "" );
780 			scoreBoard->SetStateString( va( "player%i_%s_score", j, curTeam ), "" );
781 			scoreBoard->SetStateString( va( "player%i_%s_wins", j, curTeam ), "" );
782 			scoreBoard->SetStateString( va( "player%i_%s_ping", j, curTeam ), "" );
783 			scoreBoard->SetStateInt( "rank_self", 0 );
784 		}
785 	}
786 
787 
788 	// Don't display "CTF" -- if this scoreboard comes up, it should be apparent.
789 
790 	if ( gameLocal.gameType == GAME_CTF ) {
791 
792 		int captureLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
793 
794 		if ( captureLimit > MP_CTF_MAXPOINTS  )
795 			captureLimit = MP_CTF_MAXPOINTS;
796 
797 		int timeLimit    = gameLocal.serverInfo.GetInt( "si_timeLimit" );
798 
799 		/* Prints "Capture Limit: %i" at the bottom of the scoreboard, left */
800 		if ( captureLimit )
801 			scoreBoard->SetStateString( "gameinfo_red", va( common->GetLanguageDict()->GetString( "#str_11108" ), captureLimit) );
802 		else
803 			scoreBoard->SetStateString( "gameinfo_red", "" );
804 
805 		/* Prints "Time Limit: %i" at the bottom of the scoreboard, right */
806 		if ( timeLimit )
807 			scoreBoard->SetStateString( "gameinfo_blue", va( common->GetLanguageDict()->GetString( "#str_11109" ), timeLimit) );
808 		else
809 			scoreBoard->SetStateString( "gameinfo_blue", "" );
810 	}
811 
812 
813 
814 	// Set team scores
815 	scoreBoard->SetStateInt( "red_team_score", GetFlagPoints( 0 ) );
816 	scoreBoard->SetStateInt( "blue_team_score", GetFlagPoints( 1 ) );
817 
818 	// Handle flag status changed event
819 	scoreBoard->HandleNamedEvent( "BlueFlagStatusChange" );
820 	scoreBoard->HandleNamedEvent( "RedFlagStatusChange" );
821 
822 	scoreBoard->Redraw( gameLocal.time );
823 
824 
825 
826 
827 }
828 #endif
829 
830 /*
831 ================
832 idMultiplayerGame::GameTime
833 ================
834 */
835 const char *idMultiplayerGame::GameTime() {
836 	static char buff[16];
837 	int m, s, t, ms;
838 
839 	if ( gameState == COUNTDOWN ) {
840 		ms = warmupEndTime - gameLocal.realClientTime;
841 		s = ms / 1000 + 1;
842 		if ( ms <= 0 ) {
843 			strcpy( buff, "WMP --" );
844 		} else {
845 			sprintf( buff, "WMP %i", s );
846 		}
847 	} else {
848 		int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" );
849 		if ( timeLimit ) {
850 			ms = ( timeLimit * 60000 ) - ( gameLocal.time - matchStartedTime );
851 		} else {
852 			ms = gameLocal.time - matchStartedTime;
853 		}
854 		if ( ms < 0 ) {
855 			ms = 0;
856 		}
857 
858 		s = ms / 1000;
859 		m = s / 60;
860 		s -= m * 60;
861 		t = s / 10;
862 		s -= t * 10;
863 
864 		sprintf( buff, "%i:%i%i", m, t, s );
865 	}
866 	return &buff[0];
867 }
868 
869 /*
870 ================
871 idMultiplayerGame::NumActualClients
872 ================
873 */
874 int idMultiplayerGame::NumActualClients( bool countSpectators, int *teamcounts ) {
875 	idPlayer *p;
876 	int c = 0;
877 
878 	if ( teamcounts ) {
879 		teamcounts[ 0 ] = teamcounts[ 1 ] = 0;
880 	}
881 	for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
882 		idEntity *ent = gameLocal.entities[ i ];
883 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
884 			continue;
885 		}
886 		p = static_cast< idPlayer * >( ent );
887 		if ( countSpectators || CanPlay( p ) ) {
888 			c++;
889 		}
890 		if ( teamcounts && CanPlay( p ) ) {
891 			teamcounts[ p->team ]++;
892 		}
893 	}
894 	return c;
895 }
896 
897 /*
898 ================
899 idMultiplayerGame::EnoughClientsToPlay
900 ================
901 */
902 bool idMultiplayerGame::EnoughClientsToPlay() {
903 	int team[ 2 ];
904 	int clients = NumActualClients( false, &team[ 0 ] );
905 	if ( IsGametypeTeamBased() ) { /* CTF */
906 		return clients >= 2 && team[ 0 ] && team[ 1 ];
907 	} else {
908 		return clients >= 2;
909 	}
910 }
911 
912 /*
913 ================
914 idMultiplayerGame::AllPlayersReady
915 ================
916 */
917 bool idMultiplayerGame::AllPlayersReady() {
918 	int			i;
919 	idEntity	*ent;
920 	idPlayer	*p;
921 	int			team[ 2 ];
922 
923 	if ( NumActualClients( false, &team[ 0 ] ) <= 1 ) {
924 		return false;
925 	}
926 
927 	if ( IsGametypeTeamBased() ) { /* CTF */
928 		if ( !team[ 0 ] || !team[ 1 ] ) {
929 			return false;
930 		}
931 	}
932 
933 	if ( !gameLocal.serverInfo.GetBool( "si_warmup" ) ) {
934 		return true;
935 	}
936 
937 	for( i = 0; i < gameLocal.numClients; i++ ) {
938 		if ( gameLocal.gameType == GAME_TOURNEY && i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] ) {
939 			continue;
940 		}
941 		ent = gameLocal.entities[ i ];
942 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
943 			continue;
944 		}
945 		p = static_cast< idPlayer * >( ent );
946 		if ( CanPlay( p ) && !p->IsReady() ) {
947 			return false;
948 		}
949 		team[ p->team ]++;
950 	}
951 
952 	return true;
953 }
954 
955 /*
956 ================
957 idMultiplayerGame::FragLimitHit
958 return the winning player (team player)
959 if there is no FragLeader(), the game is tied and we return NULL
960 ================
961 */
962 idPlayer *idMultiplayerGame::FragLimitHit() {
963 	int i;
964 	int fragLimit		= gameLocal.serverInfo.GetInt( "si_fragLimit" );
965 	idPlayer *leader;
966 
967 #ifdef CTF
968 	if ( IsGametypeFlagBased() ) /* CTF */
969 		return NULL;
970 #endif
971 
972 	leader = FragLeader();
973 	if ( !leader ) {
974 		return NULL;
975 	}
976 
977 	if ( fragLimit <= 0 ) {
978 		fragLimit = MP_PLAYER_MAXFRAGS;
979 	}
980 
981 	if ( gameLocal.gameType == GAME_LASTMAN ) {
982 		// we have a leader, check if any other players have frags left
983 		assert( !static_cast< idPlayer * >( leader )->lastManOver );
984 		for( i = 0 ; i < gameLocal.numClients ; i++ ) {
985 			idEntity *ent = gameLocal.entities[ i ];
986 			if ( !ent || !ent->IsType( idPlayer::Type ) ) {
987 				continue;
988 			}
989 			if ( !CanPlay( static_cast< idPlayer * >( ent ) ) ) {
990 				continue;
991 			}
992 			if ( ent == leader ) {
993 				continue;
994 			}
995 			if ( playerState[ ent->entityNumber ].fragCount > 0 ) {
996 				return NULL;
997 			}
998 		}
999 		// there is a leader, his score may even be negative, but no one else has frags left or is !lastManOver
1000 		return leader;
1001 	} else if ( IsGametypeTeamBased() ) { /* CTF */
1002 		if ( playerState[ leader->entityNumber ].teamFragCount >= fragLimit ) {
1003 			return leader;
1004 		}
1005 	} else {
1006 		if ( playerState[ leader->entityNumber ].fragCount >= fragLimit ) {
1007 			return leader;
1008 		}
1009 	}
1010 
1011 	return NULL;
1012 }
1013 
1014 /*
1015 ================
1016 idMultiplayerGame::TimeLimitHit
1017 ================
1018 */
1019 bool idMultiplayerGame::TimeLimitHit() {
1020 	int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" );
1021 	if ( timeLimit ) {
1022 		if ( gameLocal.time >= matchStartedTime + timeLimit * 60000 ) {
1023 			return true;
1024 		}
1025 	}
1026 	return false;
1027 }
1028 
1029 #ifdef CTF
1030 
1031 /*
1032 ================
1033 idMultiplayerGame::WinningTeam
1034 return winning team
1035 -1 if tied or no players
1036 ================
1037 */
1038 int idMultiplayerGame::WinningTeam( void ) {
1039 	if ( teamPoints[0] > teamPoints[1] )
1040 		return 0;
1041 	if ( teamPoints[0] < teamPoints[1] )
1042 		return 1;
1043 	return -1;
1044 }
1045 
1046 /*
1047 ================
1048 idMultiplayerGame::PointLimitHit
1049 ================
1050 */
1051 bool idMultiplayerGame::PointLimitHit( void ) {
1052 	int pointLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
1053 
1054 	// default to MP_CTF_MAXPOINTS if needed
1055 	if ( pointLimit > MP_CTF_MAXPOINTS )
1056 		pointLimit = MP_CTF_MAXPOINTS;
1057 	else if ( pointLimit <= 0 )
1058 		pointLimit = MP_CTF_MAXPOINTS;
1059 
1060 	if ( teamPoints[0] == teamPoints[1] )
1061 		return false;
1062 
1063 	if ( teamPoints[0] >= pointLimit ||
1064 		 teamPoints[1] >= pointLimit )
1065 		 return true;
1066 
1067 	return false;
1068 }
1069 #endif
1070 
1071 /*
1072 ================
1073 idMultiplayerGame::FragLeader
1074 return the current winner ( or a player from the winning team )
1075 NULL if even
1076 ================
1077 */
1078 idPlayer *idMultiplayerGame::FragLeader( void ) {
1079 	int i;
1080 	int frags[ MAX_CLIENTS ];
1081 	idPlayer *leader = NULL;
1082 	idEntity *ent;
1083 	idPlayer *p;
1084 	int high = -9999;
1085 	int count = 0;
1086 	bool teamLead[ 2 ] = { false, false };
1087 
1088 	for ( i = 0 ; i < gameLocal.numClients ; i++ ) {
1089 		ent = gameLocal.entities[ i ];
1090 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1091 			continue;
1092 		}
1093 		if ( !CanPlay( static_cast< idPlayer * >( ent ) ) ) {
1094 			continue;
1095 		}
1096 		if ( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] ) {
1097 			continue;
1098 		}
1099 		if ( static_cast< idPlayer * >( ent )->lastManOver ) {
1100 			continue;
1101 		}
1102 
1103 		int fragc = ( IsGametypeTeamBased() ) ? playerState[i].teamFragCount : playerState[i].fragCount; /* CTF */
1104 		if ( fragc > high ) {
1105 			high = fragc;
1106 		}
1107 
1108 		frags[ i ] = fragc;
1109 	}
1110 
1111 	for ( i = 0; i < gameLocal.numClients; i++ ) {
1112 		ent = gameLocal.entities[ i ];
1113 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1114 			continue;
1115 		}
1116 		p = static_cast< idPlayer * >( ent );
1117 		p->SetLeader( false );
1118 
1119 		if ( !CanPlay( p ) ) {
1120 			continue;
1121 		}
1122 		if ( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] ) {
1123 			continue;
1124 		}
1125 		if ( p->lastManOver ) {
1126 			continue;
1127 		}
1128 		if ( p->spectating ) {
1129 			continue;
1130 		}
1131 
1132 		if ( frags[ i ] >= high ) {
1133 			leader = p;
1134 			count++;
1135 			p->SetLeader( true );
1136 			if ( IsGametypeTeamBased() ) { /* CTF */
1137 				teamLead[ p->team ] = true;
1138 			}
1139 		}
1140 	}
1141 
1142 	if ( !IsGametypeTeamBased() ) { /* CTF */
1143 		// more than one player at the highest frags
1144 		if ( count > 1 ) {
1145 			return NULL;
1146 		} else {
1147 			return leader;
1148 		}
1149 	} else {
1150 		if ( teamLead[ 0 ] && teamLead[ 1 ] ) {
1151 			// even game in team play
1152 			return NULL;
1153 		}
1154 		return leader;
1155 	}
1156 }
1157 
1158 /*
1159 ================
1160 idGameLocal::UpdateWinsLosses
1161 ================
1162 */
1163 void idMultiplayerGame::UpdateWinsLosses( idPlayer *winner ) {
1164 	if ( winner ) {
1165 		// run back through and update win/loss count
1166 		for( int i = 0; i < gameLocal.numClients; i++ ) {
1167 			idEntity *ent = gameLocal.entities[ i ];
1168 			if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1169 				continue;
1170 			}
1171 			idPlayer *player = static_cast<idPlayer *>(ent);
1172 			if ( IsGametypeTeamBased() ) { /* CTF */
1173 				if ( player == winner || ( player != winner && player->team == winner->team ) ) {
1174 					playerState[ i ].wins++;
1175 					PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1176 				} else {
1177 					PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1178 				}
1179 			} else if ( gameLocal.gameType == GAME_LASTMAN ) {
1180 				if ( player == winner ) {
1181 					playerState[ i ].wins++;
1182 					PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1183 				} else if ( !player->wantSpectate ) {
1184 					PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1185 				}
1186 			} else if ( gameLocal.gameType == GAME_TOURNEY ) {
1187 				if ( player == winner ) {
1188 					playerState[ i ].wins++;
1189 					PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1190 				} else if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
1191 					PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1192 				}
1193 			} else {
1194 				if ( player == winner ) {
1195 					playerState[i].wins++;
1196 					PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1197 				} else if ( !player->wantSpectate ) {
1198 					PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1199 				}
1200 			}
1201 		}
1202 	}
1203 #ifdef CTF
1204 	else if ( IsGametypeFlagBased() ) { /* CTF */
1205 		int winteam = WinningTeam();
1206 
1207 		if ( winteam != -1 )	// TODO : print a message telling it why the hell the game ended with no winning team?
1208 		for( int i = 0; i < gameLocal.numClients; i++ ) {
1209 			idEntity *ent = gameLocal.entities[ i ];
1210 			if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1211 				continue;
1212 			}
1213 			idPlayer *player = static_cast<idPlayer *>(ent);
1214 
1215 			if ( player->team == winteam ) {
1216 				PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1217 			} else {
1218 				PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1219 			}
1220 		}
1221 	}
1222 #endif
1223 
1224 	if ( winner ) {
1225 		lastWinner = winner->entityNumber;
1226 	} else {
1227 		lastWinner = -1;
1228 	}
1229 }
1230 
1231 #ifdef CTF
1232 /*
1233 ================
1234 idMultiplayerGame::TeamScoreCTF
1235 ================
1236 */
1237 void idMultiplayerGame::TeamScoreCTF( int team, int delta ) {
1238 	if ( team < 0 || team > 1 )
1239 		return;
1240 
1241 	teamPoints[team] += delta;
1242 
1243 	if ( gameState == GAMEON || gameState == SUDDENDEATH )
1244 	PrintMessageEvent( -1, MSG_SCOREUPDATE, teamPoints[0], teamPoints[1] );
1245 }
1246 
1247 /*
1248 ================
1249 idMultiplayerGame::PlayerScoreCTF
1250 ================
1251 */
1252 void idMultiplayerGame::PlayerScoreCTF( int playerIdx, int delta ) {
1253 	if ( playerIdx < 0 || playerIdx >= MAX_CLIENTS )
1254 		return;
1255 
1256 	playerState[ playerIdx ].fragCount += delta;
1257 }
1258 
1259 /*
1260 ================
1261 idMultiplayerGame::GetFlagCarrier
1262 ================
1263 */
1264 int	idMultiplayerGame::GetFlagCarrier( int team ) {
1265 	int iFlagCarrier = -1;
1266 
1267 	for ( int i = 0; i < gameLocal.numClients; i++ ) {
1268 		idEntity * ent = gameLocal.entities[ i ];
1269 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1270 			continue;
1271 		}
1272 
1273 		idPlayer * player = static_cast<idPlayer *>( ent );
1274 		if ( player->team != team )
1275 			continue;
1276 
1277 		if ( player->carryingFlag ) {
1278 			if ( iFlagCarrier != -1 )
1279 				gameLocal.Warning( "BUG: more than one flag carrier on %s team", team == 0 ? "red" : "blue" );
1280 			iFlagCarrier = i;
1281 		}
1282 	}
1283 
1284 	return iFlagCarrier;
1285 }
1286 
1287 
1288 
1289 #endif
1290 
1291 /*
1292 ================
1293 idMultiplayerGame::TeamScore
1294 ================
1295 */
1296 void idMultiplayerGame::TeamScore( int entityNumber, int team, int delta ) {
1297 	playerState[ entityNumber ].fragCount += delta;
1298 	for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
1299 		idEntity *ent = gameLocal.entities[ i ];
1300 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1301 			continue;
1302 		}
1303 		idPlayer *player = static_cast<idPlayer *>(ent);
1304 		if ( player->team == team ) {
1305 			playerState[ player->entityNumber ].teamFragCount += delta;
1306 		}
1307 	}
1308 }
1309 
1310 /*
1311 ================
1312 idMultiplayerGame::PlayerDeath
1313 ================
1314 */
1315 void idMultiplayerGame::PlayerDeath( idPlayer *dead, idPlayer *killer, bool telefrag ) {
1316 
1317 	// don't do PrintMessageEvent and shit
1318 	assert( !gameLocal.isClient );
1319 
1320 	if ( killer ) {
1321 		if ( gameLocal.gameType == GAME_LASTMAN ) {
1322 			playerState[ dead->entityNumber ].fragCount--;
1323 
1324 		} else if ( IsGametypeTeamBased() ) { /* CTF */
1325 			if ( killer == dead || killer->team == dead->team ) {
1326 				// suicide or teamkill
1327 				TeamScore( killer->entityNumber, killer->team, -1 );
1328 			} else {
1329 				TeamScore( killer->entityNumber, killer->team, +1 );
1330 			}
1331 		} else {
1332 			playerState[ killer->entityNumber ].fragCount += ( killer == dead ) ? -1 : 1;
1333 		}
1334 	}
1335 
1336 	if ( killer && killer == dead ) {
1337 		PrintMessageEvent( -1, MSG_SUICIDE, dead->entityNumber );
1338 	} else if ( killer ) {
1339 		if ( telefrag ) {
1340 			PrintMessageEvent( -1, MSG_TELEFRAGGED, dead->entityNumber, killer->entityNumber );
1341 		} else if ( IsGametypeTeamBased() && dead->team == killer->team ) { /* CTF */
1342 			PrintMessageEvent( -1, MSG_KILLEDTEAM, dead->entityNumber, killer->entityNumber );
1343 		} else {
1344 			PrintMessageEvent( -1, MSG_KILLED, dead->entityNumber, killer->entityNumber );
1345 		}
1346 	} else {
1347 		PrintMessageEvent( -1, MSG_DIED, dead->entityNumber );
1348 		playerState[ dead->entityNumber ].fragCount--;
1349 	}
1350 }
1351 
1352 /*
1353 ================
1354 idMultiplayerGame::PlayerStats
1355 ================
1356 */
1357 void idMultiplayerGame::PlayerStats( int clientNum, char *data, const int len ) {
1358 
1359 	idEntity *ent;
1360 	int team;
1361 
1362 	*data = 0;
1363 
1364 	// make sure we don't exceed the client list
1365 	if ( clientNum < 0 || clientNum > gameLocal.numClients ) {
1366 		return;
1367 	}
1368 
1369 	// find which team this player is on
1370 	ent = gameLocal.entities[ clientNum ];
1371 	if ( ent && ent->IsType( idPlayer::Type ) ) {
1372 		team = static_cast< idPlayer * >(ent)->team;
1373 	} else {
1374 		return;
1375 	}
1376 
1377 	idStr::snPrintf( data, len, "team=%d score=%d tks=%d", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount );
1378 
1379 	return;
1380 
1381 }
1382 
1383 /*
1384 ================
1385 idMultiplayerGame::PlayerVote
1386 ================
1387 */
1388 void idMultiplayerGame::PlayerVote( int clientNum, playerVote_t vote ) {
1389 	playerState[ clientNum ].vote = vote;
1390 }
1391 
1392 /*
1393 ================
1394 idMultiplayerGame::DumpTourneyLine
1395 ================
1396 */
1397 void idMultiplayerGame::DumpTourneyLine( void ) {
1398 	int i;
1399 	for ( i = 0; i < gameLocal.numClients; i++ ) {
1400 		if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
1401 			common->Printf( "client %d: rank %d\n", i, static_cast< idPlayer * >( gameLocal.entities[ i ] )->tourneyRank );
1402 		}
1403 	}
1404 }
1405 
1406 /*
1407 ================
1408 idMultiplayerGame::NewState
1409 ================
1410 */
1411 void idMultiplayerGame::NewState( gameState_t news, idPlayer *player ) {
1412 	idBitMsg	outMsg;
1413 	byte		msgBuf[MAX_GAME_MESSAGE_SIZE];
1414 	int			i;
1415 
1416 	assert( news != gameState );
1417 	assert( !gameLocal.isClient );
1418 	gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ news ] );
1419 	switch( news ) {
1420 		case GAMEON: {
1421 			gameLocal.LocalMapRestart();
1422 			outMsg.Init( msgBuf, sizeof( msgBuf ) );
1423 			outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART );
1424 			outMsg.WriteBits( 0, 1 );
1425 			networkSystem->ServerSendReliableMessage( -1, outMsg );
1426 
1427 #ifdef CTF
1428 			teamPoints[0] = 0;
1429 			teamPoints[1] = 0;
1430 
1431 			ClearHUDStatus();
1432 #endif
1433 
1434 			PlayGlobalSound( -1, SND_FIGHT );
1435 			matchStartedTime = gameLocal.time;
1436 			fragLimitTimeout = 0;
1437 			for( i = 0; i < gameLocal.numClients; i++ ) {
1438 				idEntity *ent = gameLocal.entities[ i ];
1439 				if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1440 					continue;
1441 				}
1442 				idPlayer *p = static_cast<idPlayer *>( ent );
1443 				p->SetLeader( false ); // don't carry the flag from previous games
1444 				if ( gameLocal.gameType == GAME_TOURNEY && currentTourneyPlayer[ 0 ] != i && currentTourneyPlayer[ 1 ] != i ) {
1445 					p->ServerSpectate( true );
1446 					p->tourneyRank++;
1447 				} else {
1448 					int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
1449 					int startingCount = ( gameLocal.gameType == GAME_LASTMAN ) ? fragLimit : 0;
1450 					playerState[ i ].fragCount = startingCount;
1451 					playerState[ i ].teamFragCount = startingCount;
1452 					if ( !static_cast<idPlayer *>(ent)->wantSpectate ) {
1453 						static_cast<idPlayer *>(ent)->ServerSpectate( false );
1454 						if ( gameLocal.gameType == GAME_TOURNEY ) {
1455 							p->tourneyRank = 0;
1456 						}
1457 					}
1458 				}
1459 				if ( CanPlay( p ) ) {
1460 					p->lastManPresent = true;
1461 				} else {
1462 					p->lastManPresent = false;
1463 				}
1464 			}
1465 			cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
1466 			switchThrottle[ 1 ] = 0;	// passby the throttle
1467 			startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
1468 			break;
1469 		}
1470 		case GAMEREVIEW: {
1471 #ifdef CTF
1472 			SetFlagMsg( false );
1473 #endif
1474 			nextState = INACTIVE;	// used to abort a game. cancel out any upcoming state change
1475 			// set all players not ready and spectating
1476 			for( i = 0; i < gameLocal.numClients; i++ ) {
1477 				idEntity *ent = gameLocal.entities[ i ];
1478 				if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1479 					continue;
1480 				}
1481 				static_cast< idPlayer *>( ent )->forcedReady = false;
1482 				static_cast<idPlayer *>(ent)->ServerSpectate( true );
1483 			}
1484 			UpdateWinsLosses( player );
1485 #ifdef CTF
1486 			SetFlagMsg( true );
1487 #endif
1488 			break;
1489 		}
1490 		case SUDDENDEATH: {
1491 			PrintMessageEvent( -1, MSG_SUDDENDEATH );
1492 			PlayGlobalSound( -1, SND_SUDDENDEATH );
1493 			break;
1494 		}
1495 		case COUNTDOWN: {
1496 			idBitMsg	outMsg;
1497 			byte		msgBuf[ 128 ];
1498 
1499 			warmupEndTime = gameLocal.time + 1000*cvarSystem->GetCVarInteger( "g_countDown" );
1500 
1501 			outMsg.Init( msgBuf, sizeof( msgBuf ) );
1502 			outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME );
1503 			outMsg.WriteInt( warmupEndTime );
1504 			networkSystem->ServerSendReliableMessage( -1, outMsg );
1505 
1506 			break;
1507 		}
1508 #ifdef CTF
1509 		case WARMUP: {
1510 			teamPoints[0] = 0;
1511 			teamPoints[1] = 0;
1512 
1513 			if ( IsGametypeFlagBased() ) {
1514 				// reset player scores to zero, only required for CTF
1515 				for( i = 0; i < gameLocal.numClients; i++ ) {
1516 					idEntity *ent = gameLocal.entities[ i ];
1517 					if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1518 						continue;
1519 					}
1520 					playerState[ i ].fragCount = 0;
1521 				}
1522 			}
1523 		}
1524 #endif
1525 		default:
1526 			break;
1527 	}
1528 
1529 	gameState = news;
1530 }
1531 
1532 /*
1533 ================
1534 idMultiplayerGame::FillTourneySlots
1535 NOTE: called each frame during warmup to keep the tourney slots filled
1536 ================
1537 */
1538 void idMultiplayerGame::FillTourneySlots( ) {
1539 	int i, j, rankmax, rankmaxindex;
1540 	idEntity *ent;
1541 	idPlayer *p;
1542 
1543 	// fill up the slots based on tourney ranks
1544 	for ( i = 0; i < 2; i++ ) {
1545 		if ( currentTourneyPlayer[ i ] != -1 ) {
1546 			continue;
1547 		}
1548 		rankmax = -1;
1549 		rankmaxindex = -1;
1550 		for ( j = 0; j < gameLocal.numClients; j++ ) {
1551 			ent = gameLocal.entities[ j ];
1552 			if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1553 				continue;
1554 			}
1555 			if ( currentTourneyPlayer[ 0 ] == j || currentTourneyPlayer[ 1 ] == j ) {
1556 				continue;
1557 			}
1558 			p = static_cast< idPlayer * >( ent );
1559 			if ( p->wantSpectate ) {
1560 				continue;
1561 			}
1562 			if ( p->tourneyRank >= rankmax ) {
1563 				// when ranks are equal, use time in game
1564 				if ( p->tourneyRank == rankmax ) {
1565 					assert( rankmaxindex >= 0 );
1566 					if ( p->spawnedTime > static_cast< idPlayer * >( gameLocal.entities[ rankmaxindex ] )->spawnedTime ) {
1567 						continue;
1568 					}
1569 				}
1570 				rankmax = static_cast< idPlayer * >( ent )->tourneyRank;
1571 				rankmaxindex = j;
1572 			}
1573 		}
1574 		currentTourneyPlayer[ i ] = rankmaxindex; // may be -1 if we found nothing
1575 	}
1576 }
1577 
1578 /*
1579 ================
1580 idMultiplayerGame::UpdateTourneyLine
1581 we manipulate tourneyRank on player entities for internal ranking. it's easier to deal with.
1582 but we need a real wait list to be synced down to clients for GUI
1583 ignore current players, ignore wantSpectate
1584 ================
1585 */
1586 void idMultiplayerGame::UpdateTourneyLine( void ) {
1587 	int i, j, imax, max, globalmax = -1;
1588 	idPlayer *p;
1589 
1590 	assert( !gameLocal.isClient );
1591 	if ( gameLocal.gameType != GAME_TOURNEY ) {
1592 		return;
1593 	}
1594 
1595 	for ( j = 1; j <= gameLocal.numClients; j++ ) {
1596 		max = -1; imax = -1;
1597 		for ( i = 0; i < gameLocal.numClients; i++ ) {
1598 			if ( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i ) {
1599 				continue;
1600 			}
1601 			p = static_cast< idPlayer * >( gameLocal.entities[ i ] );
1602 			if ( !p || p->wantSpectate ) {
1603 				continue;
1604 			}
1605 			if ( p->tourneyRank > max && ( globalmax == -1 || p->tourneyRank < globalmax ) ) {
1606 				imax = i;
1607 				max = p->tourneyRank;
1608 			}
1609 		}
1610 		if ( imax == -1 ) {
1611 			break;
1612 		}
1613 
1614 		idBitMsg outMsg;
1615 		byte msgBuf[1024];
1616 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
1617 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_TOURNEYLINE );
1618 		outMsg.WriteByte( j );
1619 		networkSystem->ServerSendReliableMessage( imax, outMsg );
1620 
1621 		globalmax = max;
1622 	}
1623 }
1624 
1625 /*
1626 ================
1627 idMultiplayerGame::CycleTourneyPlayers
1628 ================
1629 */
1630 void idMultiplayerGame::CycleTourneyPlayers( ) {
1631 	int i;
1632 	idEntity *ent;
1633 	idPlayer *player;
1634 
1635 	currentTourneyPlayer[ 0 ] = -1;
1636 	currentTourneyPlayer[ 1 ] = -1;
1637 	// if any, winner from last round will play again
1638 	if ( lastWinner != -1 ) {
1639 		idEntity *ent = gameLocal.entities[ lastWinner ];
1640 		if ( ent && ent->IsType( idPlayer::Type ) ) {
1641 			currentTourneyPlayer[ 0 ] = lastWinner;
1642 		}
1643 	}
1644 	FillTourneySlots( );
1645 	// force selected players in/out of the game and update the ranks
1646 	for ( i = 0 ; i < gameLocal.numClients ; i++ ) {
1647 		if ( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i ) {
1648 			player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
1649 			player->ServerSpectate( false );
1650 		} else {
1651 			ent = gameLocal.entities[ i ];
1652 			if ( ent && ent->IsType( idPlayer::Type ) ) {
1653 				player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
1654 				player->ServerSpectate( true );
1655 			}
1656 		}
1657 	}
1658 	UpdateTourneyLine();
1659 }
1660 
1661 /*
1662 ================
1663 idMultiplayerGame::ExecuteVote
1664 the votes are checked for validity/relevance before they are started
1665 we assume that they are still legit when reaching here
1666 ================
1667 */
1668 void idMultiplayerGame::ExecuteVote( void ) {
1669 	bool needRestart;
1670 	switch ( vote ) {
1671 		case VOTE_RESTART:
1672 			gameLocal.MapRestart();
1673 			break;
1674 		case VOTE_TIMELIMIT:
1675 			si_timeLimit.SetInteger( atoi( voteValue ) );
1676 #ifdef _D3XP
1677 			needRestart = gameLocal.NeedRestart();
1678 			cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
1679 			if ( needRestart ) {
1680 				cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
1681 			}
1682 #endif
1683 			break;
1684 		case VOTE_FRAGLIMIT:
1685 			si_fragLimit.SetInteger( atoi( voteValue ) );
1686 #ifdef _D3XP
1687 			needRestart = gameLocal.NeedRestart();
1688 			cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
1689 			if ( needRestart ) {
1690 				cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
1691 			}
1692 #endif
1693 			break;
1694 		case VOTE_GAMETYPE:
1695 			si_gameType.SetString( voteValue );
1696 			gameLocal.MapRestart();
1697 			break;
1698 		case VOTE_KICK:
1699 			cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %s", voteValue.c_str() ) );
1700 			break;
1701 		case VOTE_MAP:
1702 			si_map.SetString( voteValue );
1703 			gameLocal.MapRestart();
1704 			break;
1705 		case VOTE_SPECTATORS:
1706 			si_spectators.SetBool( !si_spectators.GetBool() );
1707 #ifdef _D3XP
1708 			needRestart = gameLocal.NeedRestart();
1709 			cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
1710 			if ( needRestart ) {
1711 				cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
1712 			}
1713 #endif
1714 			break;
1715 		case VOTE_NEXTMAP:
1716 			cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverNextMap\n" );
1717 			break;
1718 	}
1719 }
1720 
1721 /*
1722 ================
1723 idMultiplayerGame::CheckVote
1724 ================
1725 */
1726 void idMultiplayerGame::CheckVote( void ) {
1727 	int numVoters, i;
1728 
1729 	if ( vote == VOTE_NONE ) {
1730 		return;
1731 	}
1732 
1733 	if ( voteExecTime ) {
1734 		if ( gameLocal.time > voteExecTime ) {
1735 			voteExecTime = 0;
1736 			ClientUpdateVote( VOTE_RESET, 0, 0 );
1737 			ExecuteVote();
1738 			vote = VOTE_NONE;
1739 		}
1740 		return;
1741 	}
1742 
1743 	// count voting players
1744 	numVoters = 0;
1745 	for ( i = 0; i < gameLocal.numClients; i++ ) {
1746 		idEntity *ent = gameLocal.entities[ i ];
1747 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1748 			continue;
1749 		}
1750 		if ( playerState[ i ].vote != PLAYER_VOTE_NONE ) {
1751 			numVoters++;
1752 		}
1753 	}
1754 	if ( !numVoters ) {
1755 		// abort
1756 		vote = VOTE_NONE;
1757 		ClientUpdateVote( VOTE_ABORTED, yesVotes, noVotes );
1758 		return;
1759 	}
1760 	if ( yesVotes / numVoters > 0.5f ) {
1761 		ClientUpdateVote( VOTE_PASSED, yesVotes, noVotes );
1762 		voteExecTime = gameLocal.time + 2000;
1763 		return;
1764 	}
1765 	if ( gameLocal.time > voteTimeOut || noVotes / numVoters >= 0.5f ) {
1766 		ClientUpdateVote( VOTE_FAILED, yesVotes, noVotes );
1767 		vote = VOTE_NONE;
1768 		return;
1769 	}
1770 }
1771 
1772 /*
1773 ================
1774 idMultiplayerGame::Warmup
1775 ================
1776 */
1777 bool idMultiplayerGame::Warmup() {
1778 	return ( gameState == WARMUP );
1779 }
1780 
1781 /*
1782 ================
1783 idMultiplayerGame::Run
1784 ================
1785 */
1786 void idMultiplayerGame::Run() {
1787 	int i, timeLeft;
1788 	idPlayer *player;
1789 	int gameReviewPause;
1790 
1791 	assert( gameLocal.isMultiplayer );
1792 	assert( !gameLocal.isClient );
1793 
1794 	pureReady = true;
1795 
1796 	if ( gameState == INACTIVE ) {
1797 		lastGameType = gameLocal.gameType;
1798 		NewState( WARMUP );
1799 	}
1800 
1801 	CheckVote();
1802 
1803 	CheckRespawns();
1804 
1805 	if ( nextState != INACTIVE && gameLocal.time > nextStateSwitch ) {
1806 		NewState( nextState );
1807 		nextState = INACTIVE;
1808 	}
1809 
1810 	// don't update the ping every frame to save bandwidth
1811 	if ( gameLocal.time > pingUpdateTime ) {
1812 		for ( i = 0; i < gameLocal.numClients; i++ ) {
1813 			playerState[i].ping = networkSystem->ServerGetClientPing( i );
1814 		}
1815 		pingUpdateTime = gameLocal.time + 1000;
1816 	}
1817 
1818 	warmupText = "";
1819 
1820 	switch( gameState ) {
1821 		case GAMEREVIEW: {
1822 			if ( nextState == INACTIVE ) {
1823 				gameReviewPause = cvarSystem->GetCVarInteger( "g_gameReviewPause" );
1824 				nextState = NEXTGAME;
1825 				nextStateSwitch = gameLocal.time + 1000 * gameReviewPause;
1826 			}
1827 			break;
1828 		}
1829 		case NEXTGAME: {
1830 			if ( nextState == INACTIVE ) {
1831 				// game rotation, new map, gametype etc.
1832 				if ( gameLocal.NextMap() ) {
1833 					cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n" );
1834 					return;
1835 				}
1836 #ifdef CTF
1837 				// make sure flags are returned
1838 				if ( IsGametypeFlagBased() ) {
1839 					idItemTeam * flag;
1840 					flag = GetTeamFlag( 0 );
1841 					if ( flag ) {
1842 						flag->Return();
1843 					}
1844 					flag = GetTeamFlag( 1 );
1845 					if ( flag ) {
1846 						flag->Return();
1847 					}
1848 				}
1849 #endif
1850 				NewState( WARMUP );
1851 				if ( gameLocal.gameType == GAME_TOURNEY ) {
1852 					CycleTourneyPlayers();
1853 				}
1854 				// put everyone back in from endgame spectate
1855 				for ( i = 0; i < gameLocal.numClients; i++ ) {
1856 					idEntity *ent = gameLocal.entities[ i ];
1857 					if ( ent && ent->IsType( idPlayer::Type ) ) {
1858 						if ( !static_cast< idPlayer * >( ent )->wantSpectate ) {
1859 							CheckRespawns( static_cast<idPlayer *>( ent ) );
1860 						}
1861 					}
1862 				}
1863 			}
1864 			break;
1865 		}
1866 		case WARMUP: {
1867 			if ( AllPlayersReady() ) {
1868 				NewState( COUNTDOWN );
1869 				nextState = GAMEON;
1870 				nextStateSwitch = gameLocal.time + 1000 * cvarSystem->GetCVarInteger( "g_countDown" );
1871 			}
1872 			warmupText = "Warming up.. waiting for players to get ready";
1873 			one = two = three = false;
1874 			break;
1875 		}
1876 		case COUNTDOWN: {
1877 			timeLeft = ( nextStateSwitch - gameLocal.time ) / 1000 + 1;
1878 			if ( timeLeft == 3 && !three ) {
1879 				PlayGlobalSound( -1, SND_THREE );
1880 				three = true;
1881 			} else if ( timeLeft == 2 && !two ) {
1882 				PlayGlobalSound( -1, SND_TWO );
1883 				two = true;
1884 			} else if ( timeLeft == 1 && !one ) {
1885 				PlayGlobalSound( -1, SND_ONE );
1886 				one = true;
1887 			}
1888 			warmupText = va( "Match starts in %i", timeLeft );
1889 			break;
1890 		}
1891 		case GAMEON: {
1892 #ifdef CTF
1893 			if ( IsGametypeFlagBased() ) { /* CTF */
1894 				// totally different logic branch for CTF
1895 				if ( PointLimitHit() ) {
1896 					int team = WinningTeam();
1897 					assert( team != -1 );
1898 
1899 					NewState( GAMEREVIEW, NULL );
1900 					PrintMessageEvent( -1, MSG_POINTLIMIT, team );
1901 				} else if ( TimeLimitHit() ) {
1902 					int team = WinningTeam();
1903 					if ( EnoughClientsToPlay() && team == -1 ) {
1904 						NewState( SUDDENDEATH );
1905 					} else {
1906 						NewState( GAMEREVIEW, NULL  );
1907 						PrintMessageEvent( -1, MSG_TIMELIMIT );
1908 					}
1909 				}
1910 				break;
1911 			}
1912 #endif
1913 
1914 			player = FragLimitHit();
1915 			if ( player ) {
1916 				// delay between detecting frag limit and ending game. let the death anims play
1917 				if ( !fragLimitTimeout ) {
1918 					common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber );
1919 					fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
1920 				}
1921 				if ( gameLocal.time > fragLimitTimeout ) {
1922 					NewState( GAMEREVIEW, player );
1923 					PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber );
1924 				}
1925 			} else {
1926 				if ( fragLimitTimeout ) {
1927 					// frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY
1928 					// enter sudden death, the next frag leader will win
1929 					SuddenRespawn();
1930 					PrintMessageEvent( -1, MSG_HOLYSHIT );
1931 					fragLimitTimeout = 0;
1932 					NewState( SUDDENDEATH );
1933 				} else if ( TimeLimitHit() ) {
1934 					player = FragLeader();
1935 					if ( !player ) {
1936 						NewState( SUDDENDEATH );
1937 					} else {
1938 						NewState( GAMEREVIEW, player );
1939 						PrintMessageEvent( -1, MSG_TIMELIMIT );
1940 					}
1941 				}
1942 			}
1943 			break;
1944 		}
1945 		case SUDDENDEATH: {
1946 #ifdef CTF
1947 			if ( IsGametypeFlagBased() ) { /* CTF */
1948 				int team = WinningTeam();
1949 				if ( team != -1 ) {
1950 					// TODO : implement pointLimitTimeout
1951 					NewState( GAMEREVIEW, NULL );
1952 					PrintMessageEvent( -1, MSG_POINTLIMIT, team );
1953 				}
1954 				break;
1955 			}
1956 #endif
1957 
1958 			player = FragLeader();
1959 			if ( player ) {
1960 				if ( !fragLimitTimeout ) {
1961 					common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber );
1962 					fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
1963 				}
1964 				if ( gameLocal.time > fragLimitTimeout ) {
1965 					NewState( GAMEREVIEW, player );
1966 					PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber );
1967 				}
1968 			} else if ( fragLimitTimeout ) {
1969 				SuddenRespawn();
1970 				PrintMessageEvent( -1, MSG_HOLYSHIT );
1971 				fragLimitTimeout = 0;
1972 			}
1973 			break;
1974 		}
1975 	}
1976 }
1977 
1978 /*
1979 ================
1980 idMultiplayerGame::UpdateMainGui
1981 ================
1982 */
1983 void idMultiplayerGame::UpdateMainGui( void ) {
1984 	int i;
1985 	mainGui->SetStateInt( "readyon", gameState == WARMUP ? 1 : 0 );
1986 	mainGui->SetStateInt( "readyoff", gameState != WARMUP ? 1 : 0 );
1987 	idStr strReady = cvarSystem->GetCVarString( "ui_ready" );
1988 	if ( strReady.Icmp( "ready") == 0 ){
1989 		strReady = common->GetLanguageDict()->GetString( "#str_04248" );
1990 	} else {
1991 		strReady = common->GetLanguageDict()->GetString( "#str_04247" );
1992 	}
1993 	mainGui->SetStateString( "ui_ready", strReady );
1994 	mainGui->SetStateInt( "teamon", IsGametypeTeamBased() ? 1 : 0 ); /* CTF */
1995 	mainGui->SetStateInt( "teamoff", (!IsGametypeTeamBased()) ? 1 : 0 ); /* CTF */
1996 	if ( IsGametypeTeamBased() ) {
1997 		idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum );
1998 		if ( p ) {
1999 			mainGui->SetStateInt( "team", p->team );
2000 		}
2001 		else {
2002 			mainGui->SetStateInt( "team", 0 );
2003 		}
2004 	}
2005 	// setup vote
2006 	mainGui->SetStateInt( "voteon", ( vote != VOTE_NONE && !voted ) ? 1 : 0 );
2007 	mainGui->SetStateInt( "voteoff", ( vote != VOTE_NONE && !voted ) ? 0 : 1 );
2008 	// last man hack
2009 	mainGui->SetStateInt( "isLastMan", gameLocal.gameType == GAME_LASTMAN ? 1 : 0 );
2010 	// send the current serverinfo values
2011 	for ( i = 0; i < gameLocal.serverInfo.GetNumKeyVals(); i++ ) {
2012 		const idKeyValue *keyval = gameLocal.serverInfo.GetKeyVal( i );
2013 		mainGui->SetStateString( keyval->GetKey(), keyval->GetValue() );
2014 	}
2015 	mainGui->StateChanged( gameLocal.time );
2016 	mainGui->SetStateString( "driver_prompt", "0" );
2017 }
2018 
2019 /*
2020 ================
2021 idMultiplayerGame::StartMenu
2022 ================
2023 */
2024 idUserInterface* idMultiplayerGame::StartMenu( void ) {
2025 
2026 	if ( mainGui == NULL ) {
2027 		return NULL;
2028 	}
2029 
2030 	int i, j;
2031 	if ( currentMenu ) {
2032 		currentMenu = 0;
2033 		cvarSystem->SetCVarBool( "ui_chat", false );
2034 	} else {
2035 		if ( nextMenu >= 2 ) {
2036 			currentMenu = nextMenu;
2037 		} else {
2038 			// for default and explicit
2039 			currentMenu = 1;
2040 		}
2041 		cvarSystem->SetCVarBool( "ui_chat", true );
2042 	}
2043 	nextMenu = 0;
2044 	gameLocal.sessionCommand = "";	// in case we used "game_startMenu" to trigger the menu
2045 	if ( currentMenu == 1 ) {
2046 		UpdateMainGui();
2047 
2048 		// UpdateMainGui sets most things, but it doesn't set these because
2049 		// it'd be pointless and/or harmful to set them every frame (for various reasons)
2050 		// Currenty the gui doesn't update properly if they change anyway, so we'll leave it like this.
2051 
2052 		// setup callvote
2053 		if ( vote == VOTE_NONE ) {
2054 			bool callvote_ok = false;
2055 			for ( i = 0; i < VOTE_COUNT; i++ ) {
2056 				// flag on means vote is denied, so default value 0 means all votes and -1 disables
2057 				mainGui->SetStateInt( va( "vote%d", i ), g_voteFlags.GetInteger() & ( 1 << i ) ? 0 : 1 );
2058 				if ( !( g_voteFlags.GetInteger() & ( 1 << i ) ) ) {
2059 					callvote_ok = true;
2060 				}
2061 			}
2062 			mainGui->SetStateInt( "callvote", callvote_ok );
2063 		} else {
2064 			mainGui->SetStateInt( "callvote", 2 );
2065 		}
2066 
2067 		// player kick data
2068 		idStr kickList;
2069 		j = 0;
2070 		for ( i = 0; i < gameLocal.numClients; i++ ) {
2071 			if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
2072 				if ( kickList.Length() ) {
2073 					kickList += ";";
2074 				}
2075 				kickList += va( "\"%d - %s\"", i, gameLocal.userInfo[ i ].GetString( "ui_name" ) );
2076 				kickVoteMap[ j ] = i;
2077 				j++;
2078 			}
2079 		}
2080 		mainGui->SetStateString( "kickChoices", kickList );
2081 
2082 #ifdef CTF
2083 		const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" );
2084 		const char *map	= gameLocal.serverInfo.GetString( "si_map" );			// what if server changes this strings while user in UI?
2085 		int num = declManager->GetNumDecls( DECL_MAPDEF );
2086 
2087 		for ( i = 0; i < num; i++ ) {
2088 			const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_MAPDEF, i ) );
2089 
2090 			if ( mapDef && idStr::Icmp( mapDef->GetName(), map ) == 0 && mapDef->dict.GetBool( gametype ) ) {
2091 				int k = 0;
2092 
2093 				idStr gametypeList;
2094 
2095 				for ( j = 0; si_gameTypeArgs[ j ]; j++ ) {
2096 					if ( mapDef->dict.GetBool( si_gameTypeArgs[ j ] ) ) {
2097 						if ( gametypeList.Length() ) {
2098 							gametypeList += ";";
2099 						}
2100 						gametypeList += va( "%s", si_gameTypeArgs[ j ] );
2101 						gameTypeVoteMap[ k ] = si_gameTypeArgs[ j ];
2102 						k++;
2103 					}
2104 				}
2105 
2106 				mainGui->SetStateString( "gametypeChoices", gametypeList );
2107 
2108 				break;
2109 			}
2110 		}
2111 #endif
2112 
2113 		mainGui->SetStateString( "chattext", "" );
2114 		mainGui->Activate( true, gameLocal.time );
2115 		return mainGui;
2116 	} else if ( currentMenu == 2 ) {
2117 		// the setup is done in MessageMode
2118 		msgmodeGui->Activate( true, gameLocal.time );
2119 		cvarSystem->SetCVarBool( "ui_chat", true );
2120 		return msgmodeGui;
2121 	}
2122 	return NULL;
2123 }
2124 
2125 /*
2126 ================
2127 idMultiplayerGame::DisableMenu
2128 ================
2129 */
2130 void idMultiplayerGame::DisableMenu( void ) {
2131 	gameLocal.sessionCommand = "";	// in case we used "game_startMenu" to trigger the menu
2132 	if ( currentMenu == 1 ) {
2133 		mainGui->Activate( false, gameLocal.time );
2134 	} else if ( currentMenu == 2 ) {
2135 		msgmodeGui->Activate( false, gameLocal.time );
2136 	}
2137 	currentMenu = 0;
2138 	nextMenu = 0;
2139 	cvarSystem->SetCVarBool( "ui_chat", false );
2140 }
2141 
2142 /*
2143 ================
2144 idMultiplayerGame::SetMapShot
2145 ================
2146 */
2147 void idMultiplayerGame::SetMapShot( void ) {
2148 	char screenshot[ MAX_STRING_CHARS ];
2149 	int mapNum = mapList->GetSelection( NULL, 0 );
2150 	const idDict *dict = NULL;
2151 	if ( mapNum >= 0 ) {
2152 		dict = fileSystem->GetMapDecl( mapNum );
2153 	}
2154 	fileSystem->FindMapScreenshot( dict ? dict->GetString( "path" ) : "", screenshot, MAX_STRING_CHARS );
2155 	mainGui->SetStateString( "current_levelshot", screenshot );
2156 }
2157 
2158 /*
2159 ================
2160 idMultiplayerGame::HandleGuiCommands
2161 ================
2162 */
2163 const char* idMultiplayerGame::HandleGuiCommands( const char *_menuCommand ) {
2164 	idUserInterface	*currentGui;
2165 	const char		*voteValue;
2166 	int				vote_clientNum;
2167 	int				icmd;
2168 	idCmdArgs		args;
2169 
2170 	if ( !_menuCommand[ 0 ] ) {
2171 		common->Printf( "idMultiplayerGame::HandleGuiCommands: empty command\n" );
2172 		return "continue";
2173 	}
2174 	assert( currentMenu );
2175 	if ( currentMenu == 1 ) {
2176 		currentGui = mainGui;
2177 	} else {
2178 		currentGui = msgmodeGui;
2179 	}
2180 
2181 	args.TokenizeString( _menuCommand, false );
2182 
2183 	for( icmd = 0; icmd < args.Argc(); ) {
2184 		const char *cmd = args.Argv( icmd++ );
2185 
2186 		if ( !idStr::Icmp( cmd,	";"	) )	{
2187 			continue;
2188 		} else if (	!idStr::Icmp( cmd, "video" ) ) {
2189 			idStr vcmd;
2190 			if ( args.Argc() - icmd	>= 1 ) {
2191 				vcmd = args.Argv( icmd++ );
2192 			}
2193 
2194 			int	oldSpec	= cvarSystem->GetCVarInteger( "com_machineSpec"	);
2195 
2196 			if ( idStr::Icmp( vcmd,	"low" )	== 0 ) {
2197 				cvarSystem->SetCVarInteger(	"com_machineSpec", 0 );
2198 			} else if (	idStr::Icmp( vcmd, "medium"	) == 0 ) {
2199 				cvarSystem->SetCVarInteger(	"com_machineSpec", 1 );
2200 			} else	if ( idStr::Icmp( vcmd,	"high" ) ==	0 )	{
2201 				cvarSystem->SetCVarInteger(	"com_machineSpec", 2 );
2202 			} else	if ( idStr::Icmp( vcmd,	"ultra"	) == 0 ) {
2203 				cvarSystem->SetCVarInteger(	"com_machineSpec", 3 );
2204 			} else if (	idStr::Icmp( vcmd, "recommended" ) == 0	) {
2205 				cmdSystem->BufferCommandText( CMD_EXEC_NOW,	"setMachineSpec\n" );
2206 			}
2207 
2208 			if ( oldSpec !=	cvarSystem->GetCVarInteger(	"com_machineSpec" )	) {
2209 				currentGui->SetStateInt( "com_machineSpec",	cvarSystem->GetCVarInteger(	"com_machineSpec" )	);
2210 				currentGui->StateChanged( gameLocal.realClientTime );
2211 				cmdSystem->BufferCommandText( CMD_EXEC_NOW,	"execMachineSpec\n"	);
2212 			}
2213 
2214 			if ( idStr::Icmp( vcmd,	"restart" )	 ==	0) {
2215 				cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" );
2216 			}
2217 
2218 			continue;
2219 		} else if (	!idStr::Icmp( cmd, "play" )	) {
2220 			if ( args.Argc() - icmd	>= 1 ) {
2221 				idStr snd =	args.Argv( icmd++ );
2222 				int	channel	= 1;
2223 				if ( snd.Length() == 1 ) {
2224 					channel	= atoi(	snd	);
2225 					snd	= args.Argv( icmd++	);
2226 				}
2227 				gameSoundWorld->PlayShaderDirectly(	snd, channel );
2228 			}
2229 			continue;
2230 		} else if (	!idStr::Icmp( cmd, "mpSkin"	) )	{
2231 			idStr skin;
2232 			if ( args.Argc() - icmd	>= 1 ) {
2233 				skin = args.Argv( icmd++ );
2234 				cvarSystem->SetCVarString( "ui_skin", skin );
2235 			}
2236 			SetMenuSkin();
2237 			continue;
2238 		} else if (	!idStr::Icmp( cmd, "quit" )	) {
2239 			cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n"	);
2240 			return NULL;
2241 		} else if (	!idStr::Icmp( cmd, "disconnect"	) )	{
2242 			cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
2243 			return NULL;
2244 		} else if (	!idStr::Icmp( cmd, "close" ) ) {
2245 			DisableMenu( );
2246 			return NULL;
2247 		} else if (	!idStr::Icmp( cmd, "spectate" )	) {
2248 			ToggleSpectate();
2249 			DisableMenu( );
2250 			return NULL;
2251 		} else if (	!idStr::Icmp( cmd, "chatmessage" ) ) {
2252 			int	mode = currentGui->State().GetInt( "messagemode" );
2253 			if ( mode )	{
2254 				cmdSystem->BufferCommandText( CMD_EXEC_NOW,	va(	"sayTeam \"%s\"", currentGui->State().GetString( "chattext"	) )	);
2255 			} else {
2256 				cmdSystem->BufferCommandText( CMD_EXEC_NOW,	va(	"say \"%s\"", currentGui->State().GetString( "chattext"	) )	);
2257 			}
2258 			currentGui->SetStateString(	"chattext",	"" );
2259 			if ( currentMenu ==	1 )	{
2260 				return "continue";
2261 			} else {
2262 				DisableMenu();
2263 				return NULL;
2264 			}
2265 		} else if (	!idStr::Icmp( cmd, "readytoggle" ) ) {
2266 			ToggleReady( );
2267 			DisableMenu( );
2268 			return NULL;
2269 		} else if (	!idStr::Icmp( cmd, "teamtoggle"	) )	{
2270 			ToggleTeam(	);
2271 			DisableMenu( );
2272 			return NULL;
2273 		} else if (	!idStr::Icmp( cmd, "callVote" )	) {
2274 			vote_flags_t voteIndex = (vote_flags_t)mainGui->State().GetInt(	"voteIndex"	);
2275 			if ( voteIndex == VOTE_MAP ) {
2276 				int mapNum = mapList->GetSelection( NULL, 0 );
2277 				if ( mapNum >= 0 ) {
2278 					const idDict *dict = fileSystem->GetMapDecl( mapNum );
2279 					if ( dict ) {
2280 						ClientCallVote( VOTE_MAP, dict->GetString( "path" ) );
2281 					}
2282 				}
2283 			} else {
2284 				voteValue =	mainGui->State().GetString(	"str_voteValue"	);
2285 				if ( voteIndex == VOTE_KICK	) {
2286 					vote_clientNum = kickVoteMap[ atoi(	voteValue )	];
2287 					ClientCallVote(	voteIndex, va( "%d", vote_clientNum	) );
2288 #ifdef CTF
2289 				} else if ( voteIndex == VOTE_GAMETYPE ) {
2290 					// send the actual gametype index, not an index in the choice list
2291 					int i;
2292 					for ( i = 0; si_gameTypeArgs[i]; i++ ) {
2293 						if ( !idStr::Icmp( gameTypeVoteMap[ atoi( voteValue ) ], si_gameTypeArgs[i] ) ) {
2294 							ClientCallVote( voteIndex, va( "%d", i ) );
2295 							break;
2296 						}
2297 					}
2298 #endif
2299 				} else {
2300 					ClientCallVote(	voteIndex, voteValue );
2301 				}
2302 			}
2303 			DisableMenu();
2304 			return NULL;
2305 		} else if (	!idStr::Icmp( cmd, "voteyes" ) ) {
2306 			CastVote( gameLocal.localClientNum,	true );
2307 			DisableMenu();
2308 			return NULL;
2309 		} else if (	!idStr::Icmp( cmd, "voteno"	) )	{
2310 			CastVote( gameLocal.localClientNum,	false );
2311 			DisableMenu();
2312 			return NULL;
2313 		} else if ( !idStr::Icmp( cmd, "bind" ) ) {
2314 			if ( args.Argc() - icmd >= 2 ) {
2315 				idStr key = args.Argv( icmd++ );
2316 				idStr bind = args.Argv( icmd++ );
2317 				cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "bindunbindtwo \"%s\" \"%s\"", key.c_str(), bind.c_str() ) );
2318 				mainGui->SetKeyBindingNames();
2319 			}
2320 			continue;
2321 		} else if ( !idStr::Icmp( cmd, "clearbind" ) ) {
2322 			if ( args.Argc() - icmd >= 1 ) {
2323 				idStr bind = args.Argv( icmd++ );
2324 				cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "unbind \"%s\"", bind.c_str() ) );
2325 				mainGui->SetKeyBindingNames();
2326 			}
2327 			continue;
2328 		} else if (	!idStr::Icmp( cmd, "MAPScan" ) ) {
2329 			const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" );
2330 			if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) {
2331 				gametype = "Deathmatch";
2332 			}
2333 
2334 			int i, num;
2335 			idStr si_map = gameLocal.serverInfo.GetString("si_map");
2336 			const idDict *dict;
2337 
2338 			mapList->Clear();
2339 			mapList->SetSelection( -1 );
2340 			num = fileSystem->GetNumMaps();
2341 			for ( i = 0; i < num; i++ ) {
2342 				dict = fileSystem->GetMapDecl( i );
2343 				if ( dict ) {
2344 					// any MP gametype supported
2345 					bool isMP = false;
2346 					int igt = GAME_SP + 1;
2347 					while ( si_gameTypeArgs[ igt ] ) {
2348 						if ( dict->GetBool( si_gameTypeArgs[ igt ] ) ) {
2349 							isMP = true;
2350 							break;
2351 						}
2352 						igt++;
2353 					}
2354 					if ( isMP ) {
2355 						const char *mapName = dict->GetString( "name" );
2356 						if ( mapName[0] == '\0' ) {
2357 							mapName = dict->GetString( "path" );
2358 						}
2359 						mapName = common->GetLanguageDict()->GetString( mapName );
2360 						mapList->Add( i, mapName );
2361 						if ( !si_map.Icmp( dict->GetString( "path" ) ) ) {
2362 							mapList->SetSelection( mapList->Num() - 1 );
2363 						}
2364 					}
2365 				}
2366 			}
2367 			// set the current level shot
2368 			SetMapShot(	);
2369 			return "continue";
2370 		} else if (	!idStr::Icmp( cmd, "click_maplist" ) ) {
2371 			SetMapShot(	);
2372 			return "continue";
2373 		} else if ( strstr( cmd, "sound" ) == cmd ) {
2374 			// pass that back to the core, will know what to do with it
2375 			return _menuCommand;
2376 		}
2377 		common->Printf(	"idMultiplayerGame::HandleGuiCommands: '%s'	unknown\n",	cmd	);
2378 
2379 	}
2380 	return "continue";
2381 }
2382 
2383 /*
2384 ================
2385 idMultiplayerGame::Draw
2386 ================
2387 */
2388 bool idMultiplayerGame::Draw( int clientNum ) {
2389 	idPlayer *player, *viewPlayer;
2390 
2391 	// clear the render entities for any players that don't need
2392 	// icons and which might not be thinking because they weren't in
2393 	// the last snapshot.
2394 	for ( int i = 0; i < gameLocal.numClients; i++ ) {
2395 		player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
2396 		if ( player && !player->NeedsIcon() ) {
2397 			player->HidePlayerIcons();
2398 		}
2399 	}
2400 
2401 	player = viewPlayer = static_cast<idPlayer *>( gameLocal.entities[ clientNum ] );
2402 
2403 	if ( player == NULL ) {
2404 		return false;
2405 	}
2406 
2407 	if ( player->spectating ) {
2408 		viewPlayer = static_cast<idPlayer *>( gameLocal.entities[ player->spectator ] );
2409 		if ( viewPlayer == NULL ) {
2410 			return false;
2411 		}
2412 	}
2413 
2414 	UpdatePlayerRanks();
2415 	UpdateHud( viewPlayer, player->hud );
2416 	// use the hud of the local player
2417 	viewPlayer->playerView.RenderPlayerView( player->hud );
2418 
2419 	if ( currentMenu ) {
2420 #if 0
2421 		// uncomment this if you want to track when players are in a menu
2422 		if ( !bCurrentMenuMsg ) {
2423 			idBitMsg	outMsg;
2424 			byte		msgBuf[ 128 ];
2425 
2426 			outMsg.Init( msgBuf, sizeof( msgBuf ) );
2427 			outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU );
2428 			outMsg.WriteBits( 1, 1 );
2429 			networkSystem->ClientSendReliableMessage( outMsg );
2430 
2431 			bCurrentMenuMsg = true;
2432 		}
2433 #endif
2434 		if ( player->wantSpectate ) {
2435 			mainGui->SetStateString( "spectext", common->GetLanguageDict()->GetString( "#str_04249" ) );
2436 		} else {
2437 			mainGui->SetStateString( "spectext", common->GetLanguageDict()->GetString( "#str_04250" ) );
2438 		}
2439 		DrawChat();
2440 		if ( currentMenu == 1 ) {
2441 			UpdateMainGui();
2442 			mainGui->Redraw( gameLocal.time );
2443 		} else {
2444 			msgmodeGui->Redraw( gameLocal.time );
2445 		}
2446 	} else {
2447 #if 0
2448 		// uncomment this if you want to track when players are in a menu
2449 		if ( bCurrentMenuMsg ) {
2450 			idBitMsg	outMsg;
2451 			byte		msgBuf[ 128 ];
2452 
2453 			outMsg.Init( msgBuf, sizeof( msgBuf ) );
2454 			outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU );
2455 			outMsg.WriteBits( 0, 1 );
2456 			networkSystem->ClientSendReliableMessage( outMsg );
2457 
2458 			bCurrentMenuMsg = false;
2459 		}
2460 #endif
2461 		if ( player->spectating ) {
2462 			idStr spectatetext[ 2 ];
2463 			int ispecline = 0;
2464 			if ( gameLocal.gameType == GAME_TOURNEY ) {
2465 				if ( !player->wantSpectate ) {
2466 					spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_04246" );
2467 					switch ( player->tourneyLine ) {
2468 						case 0:
2469 							spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07003" );
2470 							break;
2471 						case 1:
2472 							spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07004" );
2473 							break;
2474 						case 2:
2475 							spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07005" );
2476 							break;
2477 						default:
2478 							spectatetext[ 0 ] += va( common->GetLanguageDict()->GetString( "#str_07006" ), player->tourneyLine );
2479 							break;
2480 					}
2481 					ispecline++;
2482 				}
2483 			} else if ( gameLocal.gameType == GAME_LASTMAN ) {
2484 				if ( !player->wantSpectate ) {
2485 					spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_07007" );
2486 					ispecline++;
2487 				}
2488 			}
2489 			if ( player->spectator != player->entityNumber ) {
2490 				spectatetext[ ispecline ] = va( common->GetLanguageDict()->GetString( "#str_07008" ), viewPlayer->GetUserInfo()->GetString( "ui_name" ) );
2491 			} else if ( !ispecline ) {
2492 				spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_04246" );
2493 			}
2494 			spectateGui->SetStateString( "spectatetext0", spectatetext[0].c_str() );
2495 			spectateGui->SetStateString( "spectatetext1", spectatetext[1].c_str() );
2496 			if ( vote != VOTE_NONE ) {
2497 				spectateGui->SetStateString( "vote", va( "%s (y: %d n: %d)", voteString.c_str(), (int)yesVotes, (int)noVotes ) );
2498 			} else {
2499 				spectateGui->SetStateString( "vote", "" );
2500 			}
2501 			spectateGui->Redraw( gameLocal.time );
2502 		}
2503 		DrawChat();
2504 		DrawScoreBoard( player );
2505 	}
2506 
2507 	return true;
2508 }
2509 
2510 /*
2511 ================
2512 idMultiplayerGame::UpdateHud
2513 ================
2514 */
2515 void idMultiplayerGame::UpdateHud( idPlayer *player, idUserInterface *hud ) {
2516 	int i;
2517 
2518 	if ( !hud ) {
2519 		return;
2520 	}
2521 
2522 	hud->SetStateBool( "warmup", Warmup() );
2523 
2524 	if ( gameState == WARMUP ) {
2525 		if ( player->IsReady() ) {
2526 			hud->SetStateString( "warmuptext", common->GetLanguageDict()->GetString( "#str_04251" ) );
2527 		} else {
2528 			hud->SetStateString( "warmuptext", common->GetLanguageDict()->GetString( "#str_07002" ) );
2529 		}
2530 	}
2531 
2532 	hud->SetStateString( "timer", ( Warmup() ) ? common->GetLanguageDict()->GetString( "#str_04251" ) : ( gameState == SUDDENDEATH ) ? common->GetLanguageDict()->GetString( "#str_04252" ) : GameTime() );
2533 	if ( vote != VOTE_NONE ) {
2534 		hud->SetStateString( "vote", va( "%s (y: %d n: %d)", voteString.c_str(), (int)yesVotes, (int)noVotes ) );
2535 	} else {
2536 		hud->SetStateString( "vote", "" );
2537 	}
2538 
2539 	hud->SetStateInt( "rank_self", 0 );
2540 	if ( gameState == GAMEON ) {
2541 		for ( i = 0; i < numRankedPlayers; i++ ) {
2542 			if (  IsGametypeTeamBased() ) { /* CTF */
2543 				hud->SetStateInt( va( "player%i_score", i+1 ), playerState[ rankedPlayers[ i ]->entityNumber ].teamFragCount );
2544 			} else {
2545 				hud->SetStateInt( va( "player%i_score", i+1 ), playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
2546 			}
2547 			hud->SetStateInt( va( "rank%i", i+1 ), 1 );
2548 			UpdateRankColor( hud, "rank%i_color%i", i+1, rankedPlayers[ i ]->colorBar );
2549 			if ( rankedPlayers[ i ] == player ) {
2550 				hud->SetStateInt( "rank_self", i+1 );
2551 			}
2552 		}
2553 	}
2554 #ifdef _D3XP
2555 	for ( i = ( gameState == GAMEON ? numRankedPlayers : 0 ) ; i < MAX_CLIENTS; i++ ) {
2556 #else
2557 	for ( i = ( gameState == GAMEON ? numRankedPlayers : 0 ) ; i < 5; i++ ) {
2558 #endif
2559 		hud->SetStateString( va( "player%i", i+1 ), "" );
2560 		hud->SetStateString( va( "player%i_score", i+1 ), "" );
2561 		hud->SetStateInt( va( "rank%i", i+1 ), 0 );
2562 	}
2563 
2564 #ifdef CTF
2565 	if ( IsGametypeFlagBased() )
2566 		hud->SetStateInt( "self_team", player->team );
2567 	else
2568 		hud->SetStateInt( "self_team", -1 ); /* Disable */
2569 #endif
2570 
2571 }
2572 
2573 /*
2574 ================
2575 idMultiplayerGame::DrawScoreBoard
2576 ================
2577 */
2578 void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) {
2579 	if ( player->scoreBoardOpen || gameState == GAMEREVIEW ) {
2580 		if ( !playerState[ player->entityNumber ].scoreBoardUp ) {
2581 			scoreBoard->Activate( true, gameLocal.time );
2582 			playerState[ player->entityNumber ].scoreBoardUp = true;
2583 		}
2584 
2585 #ifdef CTF
2586 		if ( IsGametypeFlagBased() )
2587 			UpdateCTFScoreboard( scoreBoard, player );
2588 		else
2589 #endif
2590 		UpdateScoreboard( scoreBoard, player );
2591 
2592 	} else {
2593 		if ( playerState[ player->entityNumber ].scoreBoardUp ) {
2594 			scoreBoard->Activate( false, gameLocal.time );
2595 			playerState[ player->entityNumber ].scoreBoardUp = false;
2596 		}
2597 	}
2598 }
2599 
2600 /*
2601 ===============
2602 idMultiplayerGame::ClearChatData
2603 ===============
2604 */
2605 void idMultiplayerGame::ClearChatData() {
2606 	chatHistoryIndex	= 0;
2607 	chatHistorySize		= 0;
2608 	chatDataUpdated		= true;
2609 }
2610 
2611 /*
2612 ===============
2613 idMultiplayerGame::AddChatLine
2614 ===============
2615 */
2616 void idMultiplayerGame::AddChatLine( const char *fmt, ... ) {
2617 	idStr temp;
2618 	va_list argptr;
2619 
2620 	va_start( argptr, fmt );
2621 	vsprintf( temp, fmt, argptr );
2622 	va_end( argptr );
2623 
2624 	gameLocal.Printf( "%s\n", temp.c_str() );
2625 
2626 	chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].line = temp;
2627 	chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].fade = 6;
2628 
2629 	chatHistoryIndex++;
2630 	if ( chatHistorySize < NUM_CHAT_NOTIFY ) {
2631 		chatHistorySize++;
2632 	}
2633 	chatDataUpdated = true;
2634 	lastChatLineTime = gameLocal.time;
2635 }
2636 
2637 /*
2638 ===============
2639 idMultiplayerGame::DrawChat
2640 ===============
2641 */
2642 void idMultiplayerGame::DrawChat() {
2643 	int i, j;
2644 	if ( guiChat ) {
2645 		if ( gameLocal.time - lastChatLineTime > CHAT_FADE_TIME ) {
2646 			if ( chatHistorySize > 0 ) {
2647 				for ( i = chatHistoryIndex - chatHistorySize; i < chatHistoryIndex; i++ ) {
2648 					chatHistory[ i % NUM_CHAT_NOTIFY ].fade--;
2649 					if ( chatHistory[ i % NUM_CHAT_NOTIFY ].fade < 0 ) {
2650 						chatHistorySize--; // this assumes the removals are always at the beginning
2651 					}
2652 				}
2653 				chatDataUpdated = true;
2654 			}
2655 			lastChatLineTime = gameLocal.time;
2656 		}
2657 		if ( chatDataUpdated ) {
2658 			j = 0;
2659 			i = chatHistoryIndex - chatHistorySize;
2660 			while ( i < chatHistoryIndex ) {
2661 				guiChat->SetStateString( va( "chat%i", j ), chatHistory[ i % NUM_CHAT_NOTIFY ].line );
2662 				// don't set alpha above 4, the gui only knows that
2663 				guiChat->SetStateInt( va( "alpha%i", j ), Min( 4, (int)chatHistory[ i % NUM_CHAT_NOTIFY ].fade ) );
2664 				j++; i++;
2665 			}
2666 			while ( j < NUM_CHAT_NOTIFY ) {
2667 				guiChat->SetStateString( va( "chat%i", j ), "" );
2668 				j++;
2669 			}
2670 			guiChat->Activate( true, gameLocal.time );
2671 			chatDataUpdated = false;
2672 		}
2673 		guiChat->Redraw( gameLocal.time );
2674 	}
2675 }
2676 
2677 #ifdef _D3XP
2678 //D3XP: Adding one to frag count to allow for the negative flag in numbers greater than 255
2679 const int ASYNC_PLAYER_FRAG_BITS = -(idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS )+1);	// player can have negative frags
2680 #else
2681 const int ASYNC_PLAYER_FRAG_BITS = -idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS );	// player can have negative frags
2682 #endif
2683 const int ASYNC_PLAYER_WINS_BITS = idMath::BitsForInteger( MP_PLAYER_MAXWINS );
2684 const int ASYNC_PLAYER_PING_BITS = idMath::BitsForInteger( MP_PLAYER_MAXPING );
2685 
2686 /*
2687 ================
2688 idMultiplayerGame::WriteToSnapshot
2689 ================
2690 */
2691 void idMultiplayerGame::WriteToSnapshot( idBitMsgDelta &msg ) const {
2692 	int i;
2693 	int value;
2694 
2695 	msg.WriteByte( gameState );
2696 	msg.WriteShort( currentTourneyPlayer[ 0 ] );
2697 	msg.WriteShort( currentTourneyPlayer[ 1 ] );
2698 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
2699 		// clamp all values to min/max possible value that we can send over
2700 		value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount );
2701 		msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS );
2702 		value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount );
2703 		msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS );
2704 		value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins );
2705 		msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS );
2706 		value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping );
2707 		msg.WriteBits( value, ASYNC_PLAYER_PING_BITS );
2708 		msg.WriteBits( playerState[i].ingame, 1 );
2709 	}
2710 
2711 #ifdef CTF
2712 	msg.WriteShort( teamPoints[0] );
2713 	msg.WriteShort( teamPoints[1] );
2714 	msg.WriteShort( player_red_flag );
2715 	msg.WriteShort( player_blue_flag );
2716 #endif
2717 }
2718 
2719 /*
2720 ================
2721 idMultiplayerGame::ReadFromSnapshot
2722 ================
2723 */
2724 void idMultiplayerGame::ReadFromSnapshot( const idBitMsgDelta &msg ) {
2725 	int i;
2726 	gameState_t newState;
2727 
2728 	newState = (idMultiplayerGame::gameState_t)msg.ReadByte();
2729 	if ( newState != gameState ) {
2730 		gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ newState ] );
2731 		gameState = newState;
2732 		// these could be gathered in a BGNewState() kind of thing, as we have to do them in NewState as well
2733 		if ( gameState == GAMEON ) {
2734 			matchStartedTime = gameLocal.time;
2735 			cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
2736 			switchThrottle[ 1 ] = 0;	// passby the throttle
2737 			startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
2738 		}
2739 	}
2740 	currentTourneyPlayer[ 0 ] = msg.ReadShort();
2741 	currentTourneyPlayer[ 1 ] = msg.ReadShort();
2742 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
2743 		playerState[i].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS );
2744 		playerState[i].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS );
2745 		playerState[i].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS );
2746 		playerState[i].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS );
2747 		playerState[i].ingame = msg.ReadBits( 1 ) != 0;
2748 	}
2749 
2750 #ifdef CTF
2751 	teamPoints[0] = msg.ReadShort();
2752 	teamPoints[1] = msg.ReadShort();
2753 
2754 	player_red_flag = msg.ReadShort();
2755 	player_blue_flag = msg.ReadShort();
2756 
2757 #endif
2758 
2759 }
2760 
2761 /*
2762 ================
2763 idMultiplayerGame::PlayGlobalSound
2764 ================
2765 */
2766 void idMultiplayerGame::PlayGlobalSound( int to, snd_evt_t evt, const char *shader ) {
2767 	const idSoundShader *shaderDecl;
2768 
2769 	if ( to == -1 || to == gameLocal.localClientNum ) {
2770 		if ( shader ) {
2771 			if ( gameSoundWorld ) {
2772 				gameSoundWorld->PlayShaderDirectly( shader );
2773 			}
2774 		} else {
2775 			if ( gameSoundWorld ) {
2776 				gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ evt ] );
2777 			}
2778 		}
2779 	}
2780 
2781 	if ( !gameLocal.isClient ) {
2782 		idBitMsg outMsg;
2783 		byte msgBuf[1024];
2784 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
2785 
2786 		if ( shader ) {
2787 			shaderDecl = declManager->FindSound( shader );
2788 			if ( !shaderDecl ) {
2789 				return;
2790 			}
2791 			outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SOUND_INDEX );
2792 			outMsg.WriteInt( gameLocal.ServerRemapDecl( to, DECL_SOUND, shaderDecl->Index() ) );
2793 		} else {
2794 			outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SOUND_EVENT );
2795 			outMsg.WriteByte( evt );
2796 		}
2797 
2798 		networkSystem->ServerSendReliableMessage( to, outMsg );
2799 	}
2800 }
2801 
2802 #ifdef CTF
2803 /*
2804 ================
2805 idMultiplayerGame::PlayTeamSound
2806 ================
2807 */
2808 void idMultiplayerGame::PlayTeamSound( int toTeam, snd_evt_t evt, const char *shader ) {
2809 	for( int i = 0; i < gameLocal.numClients; i++ ) {
2810 		idEntity *ent = gameLocal.entities[ i ];
2811 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
2812 			continue;
2813 		}
2814 		idPlayer * player = static_cast<idPlayer*>(ent);
2815 		if ( player->team != toTeam )
2816 			continue;
2817 		PlayGlobalSound( i, evt, shader );
2818 	}
2819 }
2820 #endif
2821 
2822 /*
2823 ================
2824 idMultiplayerGame::PrintMessageEvent
2825 ================
2826 */
2827 void idMultiplayerGame::PrintMessageEvent( int to, msg_evt_t evt, int parm1, int parm2 ) {
2828 	switch ( evt ) {
2829 		case MSG_SUICIDE:
2830 			assert( parm1 >= 0 );
2831 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04293" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2832 			break;
2833 		case MSG_KILLED:
2834 			assert( parm1 >= 0 && parm2 >= 0 );
2835 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04292" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
2836 			break;
2837 		case MSG_KILLEDTEAM:
2838 			assert( parm1 >= 0 && parm2 >= 0 );
2839 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04291" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
2840 			break;
2841 		case MSG_TELEFRAGGED:
2842 			assert( parm1 >= 0 && parm2 >= 0 );
2843 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04290" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
2844 			break;
2845 		case MSG_DIED:
2846 			assert( parm1 >= 0 );
2847 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04289" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2848 			break;
2849 		case MSG_VOTE:
2850 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04288" ) );
2851 			break;
2852 		case MSG_SUDDENDEATH:
2853 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04287" ) );
2854 			break;
2855 		case MSG_FORCEREADY:
2856 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04286" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2857 			if ( gameLocal.entities[ parm1 ] && gameLocal.entities[ parm1 ]->IsType( idPlayer::Type ) ) {
2858 				static_cast< idPlayer * >( gameLocal.entities[ parm1 ] )->forcedReady = true;
2859 			}
2860 			break;
2861 		case MSG_JOINEDSPEC:
2862 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04285" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2863 			break;
2864 		case MSG_TIMELIMIT:
2865 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04284" ) );
2866 			break;
2867 		case MSG_FRAGLIMIT:
2868 			if ( gameLocal.gameType == GAME_LASTMAN ) {
2869 				AddChatLine( common->GetLanguageDict()->GetString( "#str_04283" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2870 			} else if ( IsGametypeTeamBased() ) { /* CTF */
2871 				AddChatLine( common->GetLanguageDict()->GetString( "#str_04282" ), gameLocal.userInfo[ parm1 ].GetString( "ui_team" ) );
2872 			} else {
2873 				AddChatLine( common->GetLanguageDict()->GetString( "#str_04281" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2874 			}
2875 			break;
2876 		case MSG_JOINTEAM:
2877 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04280" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), parm2 ? common->GetLanguageDict()->GetString( "#str_02500" ) : common->GetLanguageDict()->GetString( "#str_02499" ) );
2878 			break;
2879 		case MSG_HOLYSHIT:
2880 			AddChatLine( common->GetLanguageDict()->GetString( "#str_06732" ) );
2881 			break;
2882 #ifdef CTF
2883 		case MSG_POINTLIMIT:
2884 			AddChatLine( common->GetLanguageDict()->GetString( "#str_11100" ), parm1 ? common->GetLanguageDict()->GetString( "#str_11110" ) : common->GetLanguageDict()->GetString( "#str_11111"  ) );
2885 			break;
2886 
2887 		case MSG_FLAGTAKEN :
2888 			if ( gameLocal.GetLocalPlayer() == NULL )
2889 				break;
2890 
2891 			if ( parm2 < 0 || parm2 >= MAX_CLIENTS )
2892 				break;
2893 
2894 			if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2895 				AddChatLine( common->GetLanguageDict()->GetString( "#str_11101" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );	// your team
2896 			} else {
2897 				AddChatLine( common->GetLanguageDict()->GetString( "#str_11102" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );	// enemy
2898 			}
2899 			break;
2900 
2901 		case MSG_FLAGDROP :
2902 			if ( gameLocal.GetLocalPlayer() == NULL )
2903 				break;
2904 
2905 			if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2906 				AddChatLine( common->GetLanguageDict()->GetString( "#str_11103" ) );	// your team
2907 			} else {
2908 				AddChatLine( common->GetLanguageDict()->GetString( "#str_11104" ) );	// enemy
2909 			}
2910 			break;
2911 
2912 		case MSG_FLAGRETURN :
2913 			if ( gameLocal.GetLocalPlayer() == NULL )
2914 				break;
2915 
2916 			if ( parm2 >= 0 && parm2 < MAX_CLIENTS ) {
2917 				if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2918 					AddChatLine( common->GetLanguageDict()->GetString( "#str_11120" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );	// your team
2919 				} else {
2920 					AddChatLine( common->GetLanguageDict()->GetString( "#str_11121" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );	// enemy
2921 				}
2922 			} else {
2923 				AddChatLine( common->GetLanguageDict()->GetString( "#str_11105" ), parm1 ? common->GetLanguageDict()->GetString( "#str_11110" ) : common->GetLanguageDict()->GetString( "#str_11111" ) );
2924 			}
2925 			break;
2926 
2927 		case MSG_FLAGCAPTURE :
2928 			if ( gameLocal.GetLocalPlayer() == NULL )
2929 				break;
2930 
2931 			if ( parm2 < 0 || parm2 >= MAX_CLIENTS )
2932 				break;
2933 
2934 			if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2935 				AddChatLine( common->GetLanguageDict()->GetString( "#str_11122" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );	// your team
2936 			} else {
2937 				AddChatLine( common->GetLanguageDict()->GetString( "#str_11123" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );	// enemy
2938 			}
2939 
2940 //			AddChatLine( common->GetLanguageDict()->GetString( "#str_11106" ), parm1 ? common->GetLanguageDict()->GetString( "#str_11110" ) : common->GetLanguageDict()->GetString( "#str_11111" ) );
2941 			break;
2942 
2943 		case MSG_SCOREUPDATE:
2944 			AddChatLine( common->GetLanguageDict()->GetString( "#str_11107" ), parm1, parm2 );
2945 			break;
2946 #endif
2947 		default:
2948 			gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt );
2949 			return;
2950 	}
2951 	if ( !gameLocal.isClient ) {
2952 		idBitMsg outMsg;
2953 		byte msgBuf[1024];
2954 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
2955 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DB );
2956 		outMsg.WriteByte( evt );
2957 		outMsg.WriteByte( parm1 );
2958 		outMsg.WriteByte( parm2 );
2959 		networkSystem->ServerSendReliableMessage( to, outMsg );
2960 	}
2961 }
2962 
2963 /*
2964 ================
2965 idMultiplayerGame::SuddenRespawns
2966 solely for LMN if an end game ( fragLimitTimeout ) was entered and aborted before expiration
2967 LMN players which still have lives left need to be respawned without being marked lastManOver
2968 ================
2969 */
2970 void idMultiplayerGame::SuddenRespawn( void ) {
2971 	int i;
2972 
2973 	if ( gameLocal.gameType != GAME_LASTMAN ) {
2974 		return;
2975 	}
2976 
2977 	for ( i = 0; i < gameLocal.numClients; i++ ) {
2978 		if ( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
2979 			continue;
2980 		}
2981 		if ( !CanPlay( static_cast< idPlayer * >( gameLocal.entities[ i ] ) ) ) {
2982 			continue;
2983 		}
2984 		if ( static_cast< idPlayer * >( gameLocal.entities[ i ] )->lastManOver ) {
2985 			continue;
2986 		}
2987 		static_cast< idPlayer * >( gameLocal.entities[ i ] )->lastManPlayAgain = true;
2988 	}
2989 }
2990 
2991 /*
2992 ================
2993 idMultiplayerGame::CheckSpawns
2994 ================
2995 */
2996 void idMultiplayerGame::CheckRespawns( idPlayer *spectator ) {
2997 	for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
2998 		idEntity *ent = gameLocal.entities[ i ];
2999 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
3000 			continue;
3001 		}
3002 		idPlayer *p = static_cast<idPlayer *>(ent);
3003 		// once we hit sudden death, nobody respawns till game has ended
3004 		if ( WantRespawn( p ) || p == spectator ) {
3005 			if ( gameState == SUDDENDEATH && gameLocal.gameType != GAME_LASTMAN ) {
3006 				// respawn rules while sudden death are different
3007 				// sudden death may trigger while a player is dead, so there are still cases where we need to respawn
3008 				// don't do any respawns while we are in end game delay though
3009 				if ( !fragLimitTimeout ) {
3010 					if ( IsGametypeTeamBased() || p->IsLeader() ) {                     /* CTF */
3011 #ifdef _DEBUG
3012 						if ( gameLocal.gameType == GAME_TOURNEY ) {
3013 							assert( p->entityNumber == currentTourneyPlayer[ 0 ] || p->entityNumber == currentTourneyPlayer[ 1 ] );
3014 						}
3015 #endif
3016 						p->ServerSpectate( false );
3017 					} else if ( !p->IsLeader() ) {
3018 						// sudden death is rolling, this player is not a leader, have him spectate
3019 						p->ServerSpectate( true );
3020 						CheckAbortGame();
3021 					}
3022 				}
3023 			} else {
3024 				if ( gameLocal.gameType == GAME_DM ||		// CTF : 3wave sboily, was DM really included before?
3025 					 IsGametypeTeamBased() )
3026 				{
3027 					if ( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON ) {
3028 						p->ServerSpectate( false );
3029 					}
3030 				} else if ( gameLocal.gameType == GAME_TOURNEY ) {
3031 					if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
3032 						if ( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON ) {
3033 							p->ServerSpectate( false );
3034 						}
3035 					} else if ( gameState == WARMUP ) {
3036 						// make sure empty tourney slots get filled first
3037 						FillTourneySlots( );
3038 						if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
3039 							p->ServerSpectate( false );
3040 						}
3041 					}
3042 				} else if ( gameLocal.gameType == GAME_LASTMAN ) {
3043 					if ( gameState == WARMUP || gameState == COUNTDOWN ) {
3044 						p->ServerSpectate( false );
3045 					} else if ( gameState == GAMEON || gameState == SUDDENDEATH ) {
3046 						if ( gameState == GAMEON && playerState[ i ].fragCount > 0 && p->lastManPresent ) {
3047 							assert( !p->lastManOver );
3048 							p->ServerSpectate( false );
3049 						} else if ( p->lastManPlayAgain && p->lastManPresent ) {
3050 							assert( gameState == SUDDENDEATH );
3051 							p->ServerSpectate( false );
3052 						} else {
3053 							// if a fragLimitTimeout was engaged, do NOT mark lastManOver as that could mean
3054 							// everyone ends up spectator and game is stalled with no end
3055 							// if the frag limit delay is engaged and cancels out before expiring, LMN players are
3056 							// respawned to play the tie again ( through SuddenRespawn and lastManPlayAgain )
3057 							if ( !fragLimitTimeout && !p->lastManOver ) {
3058 								common->DPrintf( "client %d has lost all last man lives\n", i );
3059 								// end of the game for this guy, send him to spectators
3060 								p->lastManOver = true;
3061 								// clients don't have access to lastManOver
3062 								// so set the fragCount to something silly ( used in scoreboard and player ranking )
3063 								playerState[ i ].fragCount = LASTMAN_NOLIVES;
3064 								p->ServerSpectate( true );
3065 
3066 								//Check for a situation where the last two player dies at the same time and don't
3067 								//try to respawn manually...This was causing all players to go into spectate mode
3068 								//and the server got stuck
3069 								{
3070 									int j;
3071 									for ( j = 0; j < gameLocal.numClients; j++ ) {
3072 										if ( !gameLocal.entities[ j ] ) {
3073 											continue;
3074 										}
3075 										if ( !CanPlay( static_cast< idPlayer * >( gameLocal.entities[ j ] ) ) ) {
3076 											continue;
3077 										}
3078 										if ( !static_cast< idPlayer * >( gameLocal.entities[ j ] )->lastManOver ) {
3079 											break;
3080 										}
3081 									}
3082 									if( j == gameLocal.numClients) {
3083 										//Everyone is dead so don't allow this player to spectate
3084 										//so the match will end
3085 										p->ServerSpectate( false );
3086 									}
3087 								}
3088 							}
3089 						}
3090 					}
3091 				}
3092 			}
3093 		} else if ( p->wantSpectate && !p->spectating ) {
3094 			playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets
3095 			p->ServerSpectate( true );
3096 			UpdateTourneyLine();
3097 			CheckAbortGame();
3098 		}
3099 	}
3100 }
3101 
3102 /*
3103 ================
3104 idMultiplayerGame::ForceReady
3105 ================
3106 */
3107 void idMultiplayerGame::ForceReady( ) {
3108 
3109 	for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
3110 		idEntity *ent = gameLocal.entities[ i ];
3111 		if ( !ent || !ent->IsType( idPlayer::Type ) ) {
3112 			continue;
3113 		}
3114 		idPlayer *p = static_cast<idPlayer *>( ent );
3115 		if ( !p->IsReady() ) {
3116 			PrintMessageEvent( -1, MSG_FORCEREADY, i );
3117 			p->forcedReady = true;
3118 		}
3119 	}
3120 }
3121 
3122 /*
3123 ================
3124 idMultiplayerGame::ForceReady_f
3125 ================
3126 */
3127 void idMultiplayerGame::ForceReady_f( const idCmdArgs &args ) {
3128 	if ( !gameLocal.isMultiplayer || gameLocal.isClient ) {
3129 		common->Printf( "forceReady: multiplayer server only\n" );
3130 		return;
3131 	}
3132 	gameLocal.mpGame.ForceReady();
3133 }
3134 
3135 /*
3136 ================
3137 idMultiplayerGame::DropWeapon
3138 ================
3139 */
3140 void idMultiplayerGame::DropWeapon( int clientNum ) {
3141 	assert( !gameLocal.isClient );
3142 	idEntity *ent = gameLocal.entities[ clientNum ];
3143 	if ( !ent || !ent->IsType( idPlayer::Type ) ) {
3144 		return;
3145 	}
3146 	static_cast< idPlayer* >( ent )->DropWeapon( false );
3147 }
3148 
3149 /*
3150 ================
3151 idMultiplayerGame::DropWeapon_f
3152 ================
3153 */
3154 void idMultiplayerGame::DropWeapon_f( const idCmdArgs &args ) {
3155 	if ( !gameLocal.isMultiplayer ) {
3156 		common->Printf( "clientDropWeapon: only valid in multiplayer\n" );
3157 		return;
3158 	}
3159 	idBitMsg	outMsg;
3160 	byte		msgBuf[128];
3161 	outMsg.Init( msgBuf, sizeof( msgBuf ) );
3162 	outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DROPWEAPON );
3163 	networkSystem->ClientSendReliableMessage( outMsg );
3164 }
3165 
3166 /*
3167 ================
3168 idMultiplayerGame::MessageMode_f
3169 ================
3170 */
3171 void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) {
3172 	gameLocal.mpGame.MessageMode( args );
3173 }
3174 
3175 /*
3176 ================
3177 idMultiplayerGame::MessageMode
3178 ================
3179 */
3180 void idMultiplayerGame::MessageMode( const idCmdArgs &args ) {
3181 	const char *mode;
3182 	int imode;
3183 
3184 	if ( !gameLocal.isMultiplayer ) {
3185 		common->Printf( "clientMessageMode: only valid in multiplayer\n" );
3186 		return;
3187 	}
3188 	if ( !mainGui ) {
3189 		common->Printf( "no local client\n" );
3190 		return;
3191 	}
3192 	mode = args.Argv( 1 );
3193 	if ( !mode[ 0 ] ) {
3194 		imode = 0;
3195 	} else {
3196 		imode = atoi( mode );
3197 	}
3198 	msgmodeGui->SetStateString( "messagemode", imode ? "1" : "0" );
3199 	msgmodeGui->SetStateString( "chattext", "" );
3200 	nextMenu = 2;
3201 	// let the session know that we want our ingame main menu opened
3202 	gameLocal.sessionCommand = "game_startmenu";
3203 }
3204 
3205 /*
3206 ================
3207 idMultiplayerGame::Vote_f
3208 FIXME: voting from console
3209 ================
3210 */
3211 void idMultiplayerGame::Vote_f( const idCmdArgs &args ) { }
3212 
3213 /*
3214 ================
3215 idMultiplayerGame::CallVote_f
3216 FIXME: voting from console
3217 ================
3218 */
3219 void idMultiplayerGame::CallVote_f( const idCmdArgs &args ) { }
3220 
3221 /*
3222 ================
3223 idMultiplayerGame::ServerStartVote
3224 ================
3225 */
3226 void idMultiplayerGame::ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *value ) {
3227 	int i;
3228 
3229 	assert( vote == VOTE_NONE );
3230 
3231 	// setup
3232 	yesVotes = 1;
3233 	noVotes = 0;
3234 	vote = voteIndex;
3235 	voteValue = value;
3236 	voteTimeOut = gameLocal.time + 20000;
3237 	// mark players allowed to vote - only current ingame players, players joining during vote will be ignored
3238 	for ( i = 0; i < gameLocal.numClients; i++ ) {
3239 		if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
3240 			playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT;
3241 		} else {
3242 			playerState[i].vote = PLAYER_VOTE_NONE;
3243 		}
3244 	}
3245 }
3246 
3247 /*
3248 ================
3249 idMultiplayerGame::ClientStartVote
3250 ================
3251 */
3252 void idMultiplayerGame::ClientStartVote( int clientNum, const char *_voteString ) {
3253 	idBitMsg	outMsg;
3254 	byte		msgBuf[ MAX_GAME_MESSAGE_SIZE ];
3255 
3256 	if ( !gameLocal.isClient ) {
3257 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
3258 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTVOTE );
3259 		outMsg.WriteByte( clientNum );
3260 		outMsg.WriteString( _voteString );
3261 		networkSystem->ServerSendReliableMessage( -1, outMsg );
3262 	}
3263 
3264 	voteString = _voteString;
3265 	AddChatLine( va( common->GetLanguageDict()->GetString( "#str_04279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) );
3266 	gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE ] );
3267 	if ( clientNum == gameLocal.localClientNum ) {
3268 		voted = true;
3269 	} else {
3270 		voted = false;
3271 	}
3272 	if ( gameLocal.isClient ) {
3273 		// the the vote value to something so the vote line is displayed
3274 		vote = VOTE_RESTART;
3275 		yesVotes = 1;
3276 		noVotes = 0;
3277 	}
3278 }
3279 
3280 /*
3281 ================
3282 idMultiplayerGame::ClientUpdateVote
3283 ================
3284 */
3285 void idMultiplayerGame::ClientUpdateVote( vote_result_t status, int yesCount, int noCount ) {
3286 	idBitMsg	outMsg;
3287 	byte		msgBuf[ MAX_GAME_MESSAGE_SIZE ];
3288 
3289 	if ( !gameLocal.isClient ) {
3290 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
3291 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_UPDATEVOTE );
3292 		outMsg.WriteByte( status );
3293 		outMsg.WriteByte( yesCount );
3294 		outMsg.WriteByte( noCount );
3295 		networkSystem->ServerSendReliableMessage( -1, outMsg );
3296 	}
3297 
3298 	if ( vote == VOTE_NONE ) {
3299 		// clients coming in late don't get the vote start and are not allowed to vote
3300 		return;
3301 	}
3302 
3303 	switch ( status ) {
3304 		case VOTE_FAILED:
3305 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04278" ) );
3306 			gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE_FAILED ] );
3307 			if ( gameLocal.isClient ) {
3308 				vote = VOTE_NONE;
3309 			}
3310 			break;
3311 		case VOTE_PASSED:
3312 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04277" ) );
3313 			gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE_PASSED ] );
3314 			break;
3315 		case VOTE_RESET:
3316 			if ( gameLocal.isClient ) {
3317 				vote = VOTE_NONE;
3318 			}
3319 			break;
3320 		case VOTE_ABORTED:
3321 			AddChatLine( common->GetLanguageDict()->GetString( "#str_04276" ) );
3322 			if ( gameLocal.isClient ) {
3323 				vote = VOTE_NONE;
3324 			}
3325 			break;
3326 		default:
3327 			break;
3328 	}
3329 	if ( gameLocal.isClient ) {
3330 		yesVotes = yesCount;
3331 		noVotes = noCount;
3332 	}
3333 }
3334 
3335 /*
3336 ================
3337 idMultiplayerGame::ClientCallVote
3338 ================
3339 */
3340 void idMultiplayerGame::ClientCallVote( vote_flags_t voteIndex, const char *voteValue ) {
3341 	idBitMsg	outMsg;
3342 	byte		msgBuf[ MAX_GAME_MESSAGE_SIZE ];
3343 
3344 	// send
3345 	outMsg.Init( msgBuf, sizeof( msgBuf ) );
3346 	outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLVOTE );
3347 	outMsg.WriteByte( voteIndex );
3348 	outMsg.WriteString( voteValue );
3349 	networkSystem->ClientSendReliableMessage( outMsg );
3350 }
3351 
3352 /*
3353 ================
3354 idMultiplayerGame::CastVote
3355 ================
3356 */
3357 void idMultiplayerGame::CastVote( int clientNum, bool castVote ) {
3358 	idBitMsg	outMsg;
3359 	byte		msgBuf[ 128 ];
3360 
3361 	if ( clientNum == gameLocal.localClientNum ) {
3362 		voted = true;
3363 	}
3364 
3365 	if ( gameLocal.isClient ) {
3366 		outMsg.Init( msgBuf, sizeof( msgBuf ) );
3367 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CASTVOTE );
3368 		outMsg.WriteByte( castVote );
3369 		networkSystem->ClientSendReliableMessage( outMsg );
3370 		return;
3371 	}
3372 
3373 	// sanity
3374 	if ( vote == VOTE_NONE ) {
3375 		gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04275" ) );
3376 		common->DPrintf( "client %d: cast vote while no vote in progress\n", clientNum );
3377 		return;
3378 	}
3379 	if ( playerState[ clientNum ].vote != PLAYER_VOTE_WAIT ) {
3380 		gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04274" ) );
3381 		common->DPrintf( "client %d: cast vote - vote %d != PLAYER_VOTE_WAIT\n", clientNum, playerState[ clientNum ].vote );
3382 		return;
3383 	}
3384 
3385 	if ( castVote ) {
3386 		playerState[ clientNum ].vote = PLAYER_VOTE_YES;
3387 		yesVotes++;
3388 	} else {
3389 		playerState[ clientNum ].vote = PLAYER_VOTE_NO;
3390 		noVotes++;
3391 	}
3392 
3393 	ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes );
3394 }
3395 
3396 /*
3397 ================
3398 idMultiplayerGame::ServerCallVote
3399 ================
3400 */
3401 void idMultiplayerGame::ServerCallVote( int clientNum, const idBitMsg &msg ) {
3402 	vote_flags_t	voteIndex;
3403 	int				vote_timeLimit, vote_fragLimit, vote_clientNum, vote_gameTypeIndex; //, vote_kickIndex;
3404 	char			value[ MAX_STRING_CHARS ];
3405 
3406 	assert( clientNum != -1 );
3407 	assert( !gameLocal.isClient );
3408 
3409 	voteIndex = (vote_flags_t)msg.ReadByte( );
3410 	msg.ReadString( value, sizeof( value ) );
3411 
3412 	// sanity checks - setup the vote
3413 	if ( vote != VOTE_NONE ) {
3414 		gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04273" ) );
3415 		common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum );
3416 		return;
3417 	}
3418 	switch ( voteIndex ) {
3419 		case VOTE_RESTART:
3420 			ServerStartVote( clientNum, voteIndex, "" );
3421 			ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04271" ) );
3422 			break;
3423 		case VOTE_NEXTMAP:
3424 			ServerStartVote( clientNum, voteIndex, "" );
3425 			ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04272" ) );
3426 			break;
3427 		case VOTE_TIMELIMIT:
3428 			vote_timeLimit = strtol( value, NULL, 10 );
3429 			if ( vote_timeLimit == gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) {
3430 				gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04270" ) );
3431 				common->DPrintf( "client %d: already at the voted Time Limit\n", clientNum );
3432 				return;
3433 			}
3434 			if ( vote_timeLimit < si_timeLimit.GetMinValue() || vote_timeLimit > si_timeLimit.GetMaxValue() ) {
3435 				gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04269" ) );
3436 				common->DPrintf( "client %d: timelimit value out of range for vote: %s\n", clientNum, value );
3437 				return;
3438 			}
3439 			ServerStartVote( clientNum, voteIndex, value );
3440 			ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04268" ), vote_timeLimit ) );
3441 			break;
3442 		case VOTE_FRAGLIMIT:
3443 			vote_fragLimit = strtol( value, NULL, 10 );
3444 			if ( vote_fragLimit == gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) {
3445 				gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04267" ) );
3446 				common->DPrintf( "client %d: already at the voted Frag Limit\n", clientNum );
3447 				return;
3448 			}
3449 			if ( vote_fragLimit < si_fragLimit.GetMinValue() || vote_fragLimit > si_fragLimit.GetMaxValue() ) {
3450 				gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04266" ) );
3451 				common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value );
3452 				return;
3453 			}
3454 			ServerStartVote( clientNum, voteIndex, value );
3455 			ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04303" ), gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04264" ) : common->GetLanguageDict()->GetString( "#str_04265" ), vote_fragLimit ) );
3456 			break;
3457 		case VOTE_GAMETYPE:
3458 			vote_gameTypeIndex = strtol( value, NULL, 10 );
3459 #ifdef CTF
3460 			assert( vote_gameTypeIndex > 0 && vote_gameTypeIndex < GAME_COUNT );
3461 			strcpy( value, si_gameTypeArgs[ vote_gameTypeIndex ] );
3462 #endif
3463 
3464 /*#ifdef CTF
3465 			assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex <= 4 );
3466 #else
3467 			assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex <= 3 );
3468 #endif
3469 			switch ( vote_gameTypeIndex ) {
3470 				case 0:
3471 					strcpy( value, "Deathmatch" );
3472 					break;
3473 				case 1:
3474 					strcpy( value, "Tourney" );
3475 					break;
3476 				case 2:
3477 					strcpy( value, "Team DM" );
3478 					break;
3479 				case 3:
3480 					strcpy( value, "Last Man" );
3481 					break;
3482 #ifdef CTF
3483 				case 4:
3484 					strcpy( value, "CTF" );
3485 					break;
3486 #endif
3487 			}*/
3488 			if ( !idStr::Icmp( value, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) {
3489 				gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04259" ) );
3490 				common->DPrintf( "client %d: already at the voted Game Type\n", clientNum );
3491 				return;
3492 			}
3493 			ServerStartVote( clientNum, voteIndex, value );
3494 			ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04258" ), value ) );
3495 			break;
3496 		case VOTE_KICK:
3497 			vote_clientNum = strtol( value, NULL, 10 );
3498 			if ( vote_clientNum == gameLocal.localClientNum ) {
3499 				gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04257" ) );
3500 				common->DPrintf( "client %d: called kick for the server host\n", clientNum );
3501 				return;
3502 			}
3503 			ServerStartVote( clientNum, voteIndex, va( "%d", vote_clientNum ) );
3504 			ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04302" ), vote_clientNum, gameLocal.userInfo[ vote_clientNum ].GetString( "ui_name" ) ) );
3505 			break;
3506 		case VOTE_MAP: {
3507 			if ( idStr::FindText( gameLocal.serverInfo.GetString( "si_map" ), value ) != -1 ) {
3508 				gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04295" ), value ) );
3509 				common->DPrintf( "client %d: already running the voted map: %s\n", clientNum, value );
3510 				return;
3511 			}
3512 			int				num = fileSystem->GetNumMaps();
3513 			int				i;
3514 			const idDict	*dict;
3515 			bool			haveMap = false;
3516 			for ( i = 0; i < num; i++ ) {
3517 				dict = fileSystem->GetMapDecl( i );
3518 				if ( dict && !idStr::Icmp( dict->GetString( "path" ), value ) ) {
3519 					haveMap = true;
3520 					break;
3521 				}
3522 			}
3523 			if ( !haveMap ) {
3524 				gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04296" ), value ) );
3525 				common->Printf( "client %d: map not found: %s\n", clientNum, value );
3526 				return;
3527 			}
3528 			ServerStartVote( clientNum, voteIndex, value );
3529 			ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04256" ), common->GetLanguageDict()->GetString( dict ? dict->GetString( "name" ) : value ) ) );
3530 			break;
3531 		}
3532 		case VOTE_SPECTATORS:
3533 			if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
3534 				ServerStartVote( clientNum, voteIndex, "" );
3535 				ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04255" ) );
3536 			} else {
3537 				ServerStartVote( clientNum, voteIndex, "" );
3538 				ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04254" ) );
3539 			}
3540 			break;
3541 		default:
3542 			gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04297" ), (int)voteIndex ) );
3543 			common->DPrintf( "client %d: unknown vote index %d\n", clientNum, voteIndex );
3544 	}
3545 }
3546 
3547 /*
3548 ================
3549 idMultiplayerGame::DisconnectClient
3550 ================
3551 */
3552 void idMultiplayerGame::DisconnectClient( int clientNum ) {
3553 	if ( lastWinner == clientNum ) {
3554 		lastWinner = -1;
3555 	}
3556 	UpdatePlayerRanks();
3557 	CheckAbortGame();
3558 }
3559 
3560 /*
3561 ================
3562 idMultiplayerGame::CheckAbortGame
3563 ================
3564 */
3565 void idMultiplayerGame::CheckAbortGame( void ) {
3566 	int i;
3567 	if ( gameLocal.gameType == GAME_TOURNEY && gameState == WARMUP ) {
3568 		// if a tourney player joined spectators, let someone else have his spot
3569 		for ( i = 0; i < 2; i++ ) {
3570 			if ( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || static_cast< idPlayer * >( gameLocal.entities[ currentTourneyPlayer[ i ] ] )->spectating ) {
3571 				currentTourneyPlayer[ i ] = -1;
3572 			}
3573 		}
3574 	}
3575 	// only checks for aborts -> game review below
3576 	if ( gameState != COUNTDOWN && gameState != GAMEON && gameState != SUDDENDEATH ) {
3577 		return;
3578 	}
3579 	switch ( gameLocal.gameType ) {
3580 		case GAME_TOURNEY:
3581 			for ( i = 0; i < 2; i++ ) {
3582 				if ( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || static_cast< idPlayer * >( gameLocal.entities[ currentTourneyPlayer[ i ] ] )->spectating ) {
3583 					NewState( GAMEREVIEW );
3584 					return;
3585 				}
3586 			}
3587 			break;
3588 		default:
3589 			if ( !EnoughClientsToPlay() ) {
3590 				NewState( GAMEREVIEW );
3591 			}
3592 			break;
3593 	}
3594 }
3595 
3596 /*
3597 ================
3598 idMultiplayerGame::WantKilled
3599 ================
3600 */
3601 void idMultiplayerGame::WantKilled( int clientNum ) {
3602 	idEntity *ent = gameLocal.entities[ clientNum ];
3603 	if ( ent && ent->IsType( idPlayer::Type ) ) {
3604 		static_cast<idPlayer *>( ent )->Kill( false, false );
3605 	}
3606 }
3607 
3608 /*
3609 ================
3610 idMultiplayerGame::MapRestart
3611 ================
3612 */
3613 void idMultiplayerGame::MapRestart( void ) {
3614 	int clientNum;
3615 
3616 	assert( !gameLocal.isClient );
3617 	if ( gameState != WARMUP ) {
3618 		NewState( WARMUP );
3619 		nextState = INACTIVE;
3620 		nextStateSwitch = 0;
3621 	}
3622 
3623 #ifdef CTF
3624 	teamPoints[0] = 0;
3625 	teamPoints[1] = 0;
3626 
3627 	ClearHUDStatus();
3628 #endif
3629 
3630 #ifdef CTF
3631 	// still balance teams in CTF
3632 	if ( g_balanceTDM.GetBool() && lastGameType != GAME_TDM && lastGameType != GAME_CTF && gameLocal.mpGame.IsGametypeTeamBased() ) {
3633 #else
3634 	if ( g_balanceTDM.GetBool() && lastGameType != GAME_TDM && gameLocal.gameType == GAME_TDM ) {
3635 #endif
3636 		for ( clientNum = 0; clientNum < gameLocal.numClients; clientNum++ ) {
3637 			if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::Type ) ) {
3638 				if ( static_cast< idPlayer* >( gameLocal.entities[ clientNum ] )->BalanceTDM() ) {
3639 					// core is in charge of syncing down userinfo changes
3640 					// it will also call back game through SetUserInfo with the current info for update
3641 					cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) );
3642 				}
3643 			}
3644 		}
3645 	}
3646 	lastGameType = gameLocal.gameType;
3647 }
3648 
3649 /*
3650 ================
3651 idMultiplayerGame::SwitchToTeam
3652 ================
3653 */
3654 void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) {
3655 	idEntity *ent;
3656 	int i;
3657 
3658 	assert( IsGametypeTeamBased() ); /* CTF */
3659 	assert( oldteam != newteam );
3660 	assert( !gameLocal.isClient );
3661 
3662 	if ( !gameLocal.isClient && newteam >= 0 && IsInGame( clientNum ) ) {
3663 		PrintMessageEvent( -1, MSG_JOINTEAM, clientNum, newteam );
3664 	}
3665 	// assign the right teamFragCount
3666 	for( i = 0; i < gameLocal.numClients; i++ ) {
3667 		if ( i == clientNum ) {
3668 			continue;
3669 		}
3670 		ent = gameLocal.entities[ i ];
3671 		if ( ent && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >(ent)->team == newteam ) {
3672 			playerState[ clientNum ].teamFragCount = playerState[ i ].teamFragCount;
3673 			break;
3674 		}
3675 	}
3676 	if ( i == gameLocal.numClients ) {
3677 		// alone on this team
3678 		playerState[ clientNum ].teamFragCount = 0;
3679 
3680 	}
3681 #ifdef CTF
3682 	if ( ( gameState == GAMEON || ( IsGametypeFlagBased() && gameState == SUDDENDEATH ) ) && oldteam != -1 ) {
3683 #else
3684 	if ( gameState == GAMEON && oldteam != -1 ) {
3685 #endif
3686 		// when changing teams during game, kill and respawn
3687 		idPlayer *p = static_cast<idPlayer *>( gameLocal.entities[ clientNum ] );
3688 		if ( p->IsInTeleport() ) {
3689 			p->ServerSendEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false, -1 );
3690 			p->SetPrivateCameraView( NULL );
3691 		}
3692 		p->Kill( true, true );
3693 #ifdef CTF
3694 		if ( IsGametypeFlagBased() )
3695 			p->DropFlag();
3696 #endif
3697 		CheckAbortGame();
3698 	}
3699 #ifdef CTF
3700 	else if ( IsGametypeFlagBased() && oldteam != -1 ) {
3701 		idPlayer *p = static_cast<idPlayer *>( gameLocal.entities[ clientNum ] );
3702 		p->DropFlag();
3703 	}
3704 #endif
3705 }
3706 
3707 /*
3708 ================
3709 idMultiplayerGame::ProcessChatMessage
3710 ================
3711 */
3712 void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ) {
3713 	idBitMsg	outMsg;
3714 	byte		msgBuf[ 256 ];
3715 	const char *prefix = NULL;
3716 	int			send_to; // 0 - all, 1 - specs, 2 - team
3717 	int			i;
3718 	idEntity	*ent;
3719 	idPlayer	*p;
3720 	idStr		prefixed_name;
3721 
3722 	assert( !gameLocal.isClient );
3723 
3724 	if ( clientNum >= 0 ) {
3725 		p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
3726 		if ( !( p && p->IsType( idPlayer::Type ) ) ) {
3727 			return;
3728 		}
3729 
3730 		if ( p->spectating ) {
3731 			prefix = "spectating";
3732 			if ( team || ( !g_spectatorChat.GetBool() && ( gameState == GAMEON || gameState == SUDDENDEATH ) ) ) {
3733 				// to specs
3734 				send_to = 1;
3735 			} else {
3736 				// to all
3737 				send_to = 0;
3738 			}
3739 		} else if ( team ) {
3740 			prefix = "team";
3741 			// to team
3742 			send_to = 2;
3743 		} else {
3744 			// to all
3745 			send_to = 0;
3746 		}
3747 	} else {
3748 		p = NULL;
3749 		send_to = 0;
3750 	}
3751 	// put the message together
3752 	outMsg.Init( msgBuf, sizeof( msgBuf ) );
3753 	outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT );
3754 	if ( prefix ) {
3755 		prefixed_name = va( "(%s) %s", prefix, name );
3756 	} else {
3757 		prefixed_name = name;
3758 	}
3759 	outMsg.WriteString( prefixed_name );
3760 	outMsg.WriteString( text, -1, false );
3761 	if ( !send_to ) {
3762 		AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
3763 		networkSystem->ServerSendReliableMessage( -1, outMsg );
3764 		if ( sound ) {
3765 			PlayGlobalSound( -1, SND_COUNT, sound );
3766 		}
3767 	} else {
3768 		for ( i = 0; i < gameLocal.numClients; i++ ) {
3769 			ent = gameLocal.entities[ i ];
3770 			if ( !ent || !ent->IsType( idPlayer::Type ) ) {
3771 				continue;
3772 			}
3773 			if ( send_to == 1 && static_cast< idPlayer * >( ent )->spectating ) {
3774 				if ( sound ) {
3775 					PlayGlobalSound( i, SND_COUNT, sound );
3776 				}
3777 				if ( i == gameLocal.localClientNum ) {
3778 					AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
3779 				} else {
3780 					networkSystem->ServerSendReliableMessage( i, outMsg );
3781 				}
3782 			} else if ( send_to == 2 && static_cast< idPlayer * >( ent )->team == p->team ) {
3783 				if ( sound ) {
3784 					PlayGlobalSound( i, SND_COUNT, sound );
3785 				}
3786 				if ( i == gameLocal.localClientNum ) {
3787 					AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
3788 				} else {
3789 					networkSystem->ServerSendReliableMessage( i, outMsg );
3790 				}
3791 			}
3792 		}
3793 	}
3794 }
3795 
3796 /*
3797 ================
3798 idMultiplayerGame::Precache
3799 ================
3800 */
3801 void idMultiplayerGame::Precache( void ) {
3802 	int			i;
3803 	idFile		*f;
3804 
3805 	if ( !gameLocal.isMultiplayer ) {
3806 		return;
3807 	}
3808 	gameLocal.FindEntityDefDict( "player_doommarine", false );;
3809 
3810 	// skins
3811 	idStr str = cvarSystem->GetCVarString( "mod_validSkins" );
3812 	idStr skin;
3813 	while ( str.Length() ) {
3814 		int n = str.Find( ";" );
3815 		if ( n >= 0 ) {
3816 			skin = str.Left( n );
3817 			str = str.Right( str.Length() - n - 1 );
3818 		} else {
3819 			skin = str;
3820 			str = "";
3821 		}
3822 		declManager->FindSkin( skin, false );
3823 	}
3824 
3825 	for ( i = 0; ui_skinArgs[ i ]; i++ ) {
3826 		declManager->FindSkin( ui_skinArgs[ i ], false );
3827 	}
3828 	// MP game sounds
3829 	for ( i = 0; i < SND_COUNT; i++ ) {
3830 		f = fileSystem->OpenFileRead( GlobalSoundStrings[ i ] );
3831 		fileSystem->CloseFile( f );
3832 	}
3833 	// MP guis. just make sure we hit all of them
3834 	i = 0;
3835 	while ( MPGuis[ i ] ) {
3836 		uiManager->FindGui( MPGuis[ i ], true );
3837 		i++;
3838 	}
3839 }
3840 
3841 /*
3842 ================
3843 idMultiplayerGame::ToggleSpectate
3844 ================
3845 */
3846 void idMultiplayerGame::ToggleSpectate( void ) {
3847 	bool spectating;
3848 	assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
3849 
3850 	spectating = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) == 0 );
3851 	if ( spectating ) {
3852 		// always allow toggling to play
3853 		cvarSystem->SetCVarString( "ui_spectate", "Play" );
3854 	} else {
3855 		// only allow toggling to spectate if spectators are enabled.
3856 		if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
3857 			cvarSystem->SetCVarString( "ui_spectate", "Spectate" );
3858 		} else {
3859 			gameLocal.mpGame.AddChatLine( common->GetLanguageDict()->GetString( "#str_06747" ) );
3860 		}
3861 	}
3862 }
3863 
3864 /*
3865 ================
3866 idMultiplayerGame::ToggleReady
3867 ================
3868 */
3869 void idMultiplayerGame::ToggleReady( void ) {
3870 	bool ready;
3871 	assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
3872 
3873 	ready = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_ready" ), "Ready" ) == 0 );
3874 	if ( ready ) {
3875 		cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
3876 	} else {
3877 		cvarSystem->SetCVarString( "ui_ready", "Ready" );
3878 	}
3879 }
3880 
3881 /*
3882 ================
3883 idMultiplayerGame::ToggleTeam
3884 ================
3885 */
3886 void idMultiplayerGame::ToggleTeam( void ) {
3887 	bool team;
3888 	assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
3889 
3890 	team = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_team" ), "Red" ) == 0 );
3891 	if ( team ) {
3892 		cvarSystem->SetCVarString( "ui_team", "Blue" );
3893 	} else {
3894 		cvarSystem->SetCVarString( "ui_team", "Red" );
3895 	}
3896 }
3897 
3898 /*
3899 ================
3900 idMultiplayerGame::ToggleUserInfo
3901 ================
3902 */
3903 void idMultiplayerGame::ThrottleUserInfo( void ) {
3904 	int i;
3905 
3906 	assert( gameLocal.localClientNum >= 0 );
3907 
3908 	i = 0;
3909 	while ( ThrottleVars[ i ] ) {
3910 		if ( idStr::Icmp( gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ),
3911 			cvarSystem->GetCVarString( ThrottleVars[ i ] ) ) ) {
3912 			if ( gameLocal.realClientTime < switchThrottle[ i ] ) {
3913 				AddChatLine( common->GetLanguageDict()->GetString( "#str_04299" ), common->GetLanguageDict()->GetString( ThrottleVarsInEnglish[ i ] ), ( switchThrottle[ i ] - gameLocal.time ) / 1000 + 1 );
3914 				cvarSystem->SetCVarString( ThrottleVars[ i ], gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ) );
3915 			} else {
3916 				switchThrottle[ i ] = gameLocal.time + ThrottleDelay[ i ] * 1000;
3917 			}
3918 		}
3919 		i++;
3920 	}
3921 }
3922 
3923 /*
3924 ================
3925 idMultiplayerGame::CanPlay
3926 ================
3927 */
3928 bool idMultiplayerGame::CanPlay( idPlayer *p ) {
3929 	return !p->wantSpectate && playerState[ p->entityNumber ].ingame;
3930 }
3931 
3932 /*
3933 ================
3934 idMultiplayerGame::EnterGame
3935 ================
3936 */
3937 void idMultiplayerGame::EnterGame( int clientNum ) {
3938 	assert( !gameLocal.isClient );
3939 
3940 	if ( !playerState[ clientNum ].ingame ) {
3941 		playerState[ clientNum ].ingame = true;
3942 		if ( gameLocal.isMultiplayer ) {
3943 			// can't use PrintMessageEvent as clients don't know the nickname yet
3944 			gameLocal.ServerSendChatMessage( -1, common->GetLanguageDict()->GetString( "#str_02047" ), va( common->GetLanguageDict()->GetString( "#str_07177" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) );
3945 		}
3946 	}
3947 }
3948 
3949 /*
3950 ================
3951 idMultiplayerGame::WantRespawn
3952 ================
3953 */
3954 bool idMultiplayerGame::WantRespawn( idPlayer *p ) {
3955 	return p->forceRespawn && !p->wantSpectate && playerState[ p->entityNumber ].ingame;
3956 }
3957 
3958 /*
3959 ================
3960 idMultiplayerGame::VoiceChat
3961 ================
3962 */
3963 void idMultiplayerGame::VoiceChat_f( const idCmdArgs &args ) {
3964 	gameLocal.mpGame.VoiceChat( args, false );
3965 }
3966 
3967 /*
3968 ================
3969 idMultiplayerGame::VoiceChatTeam
3970 ================
3971 */
3972 void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs &args ) {
3973 	gameLocal.mpGame.VoiceChat( args, true );
3974 }
3975 
3976 /*
3977 ================
3978 idMultiplayerGame::VoiceChat
3979 ================
3980 */
3981 void idMultiplayerGame::VoiceChat( const idCmdArgs &args, bool team ) {
3982 	idBitMsg			outMsg;
3983 	byte				msgBuf[128];
3984 	const char			*voc;
3985 	const idDict		*spawnArgs;
3986 	const idKeyValue	*keyval;
3987 	int					index;
3988 
3989 	if ( !gameLocal.isMultiplayer ) {
3990 		common->Printf( "clientVoiceChat: only valid in multiplayer\n" );
3991 		return;
3992 	}
3993 	if ( args.Argc() != 2 ) {
3994 		common->Printf( "clientVoiceChat: bad args\n" );
3995 		return;
3996 	}
3997 	// throttle
3998 	if ( gameLocal.realClientTime < voiceChatThrottle ) {
3999 		return;
4000 	}
4001 
4002 	voc = args.Argv( 1 );
4003 	spawnArgs = gameLocal.FindEntityDefDict( "player_doommarine", false );
4004 	keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL );
4005 	index = 0;
4006 	while ( keyval ) {
4007 		if ( !keyval->GetValue().Icmp( voc ) ) {
4008 			break;
4009 		}
4010 		keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval );
4011 		index++;
4012 	}
4013 	if ( !keyval ) {
4014 		common->Printf( "Voice command not found: %s\n", voc );
4015 		return;
4016 	}
4017 	voiceChatThrottle = gameLocal.realClientTime + 1000;
4018 
4019 	outMsg.Init( msgBuf, sizeof( msgBuf ) );
4020 	outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VCHAT );
4021 	outMsg.WriteInt( index );
4022 	outMsg.WriteBits( team ? 1 : 0, 1 );
4023 	networkSystem->ClientSendReliableMessage( outMsg );
4024 }
4025 
4026 /*
4027 ================
4028 idMultiplayerGame::ProcessVoiceChat
4029 ================
4030 */
4031 void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index ) {
4032 	const idDict		*spawnArgs;
4033 	const idKeyValue	*keyval;
4034 	idStr				name;
4035 	idStr				snd_key;
4036 	idStr				text_key;
4037 	idPlayer			*p;
4038 
4039 	p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
4040 	if ( !( p && p->IsType( idPlayer::Type ) ) ) {
4041 		return;
4042 	}
4043 
4044 	if ( p->spectating ) {
4045 		return;
4046 	}
4047 
4048 	// lookup the sound def
4049 	spawnArgs = gameLocal.FindEntityDefDict( "player_doommarine", false );
4050 	keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL );
4051 	while ( index > 0 && keyval ) {
4052 		keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval );
4053 		index--;
4054 	}
4055 	if ( !keyval ) {
4056 		common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index );
4057 		return;
4058 	}
4059 	snd_key = keyval->GetKey();
4060 	name = gameLocal.userInfo[ clientNum ].GetString( "ui_name" );
4061 	sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() );
4062 	if ( team || gameState == COUNTDOWN || gameState == GAMEREVIEW ) {
4063 		ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) );
4064 	} else {
4065 		p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL );
4066 		ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL );
4067 	}
4068 }
4069 
4070 /*
4071 ================
4072 idMultiplayerGame::ServerWriteInitialReliableMessages
4073 ================
4074 */
4075 void idMultiplayerGame::ServerWriteInitialReliableMessages( int clientNum ) {
4076 	idBitMsg	outMsg;
4077 	byte		msgBuf[ MAX_GAME_MESSAGE_SIZE ];
4078 	int			i;
4079 	idEntity	*ent;
4080 
4081 	outMsg.Init( msgBuf, sizeof( msgBuf ) );
4082 	outMsg.BeginWriting();
4083 	outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTSTATE );
4084 	// send the game state and start time
4085 	outMsg.WriteByte( gameState );
4086 	outMsg.WriteInt( matchStartedTime );
4087 	outMsg.WriteShort( startFragLimit );
4088 	// send the powerup states and the spectate states
4089 	for( i = 0; i < gameLocal.numClients; i++ ) {
4090 		ent = gameLocal.entities[ i ];
4091 		if ( i != clientNum && ent && ent->IsType( idPlayer::Type ) ) {
4092 			outMsg.WriteShort( i );
4093 			outMsg.WriteShort( static_cast< idPlayer * >( ent )->inventory.powerups );
4094 			outMsg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 );
4095 		}
4096 	}
4097 	outMsg.WriteShort( MAX_CLIENTS );
4098 	networkSystem->ServerSendReliableMessage( clientNum, outMsg );
4099 
4100 	// we send SI in connectResponse messages, but it may have been modified already
4101 	outMsg.BeginWriting( );
4102 	outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO );
4103 	outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL );
4104 	networkSystem->ServerSendReliableMessage( clientNum, outMsg );
4105 
4106 	// warmup time
4107 	if ( gameState == COUNTDOWN ) {
4108 		outMsg.BeginWriting();
4109 		outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME );
4110 		outMsg.WriteInt( warmupEndTime );
4111 		networkSystem->ServerSendReliableMessage( clientNum, outMsg );
4112 	}
4113 }
4114 
4115 /*
4116 ================
4117 idMultiplayerGame::ClientReadStartState
4118 ================
4119 */
4120 void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) {
4121 	int i, client, powerup;
4122 
4123 	// read the state in preparation for reading snapshot updates
4124 	gameState = (idMultiplayerGame::gameState_t)msg.ReadByte();
4125 	matchStartedTime = msg.ReadInt( );
4126 	startFragLimit = msg.ReadShort( );
4127 	while ( ( client = msg.ReadShort() ) != MAX_CLIENTS ) {
4128 		assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::Type ) );
4129 		powerup = msg.ReadShort();
4130 		for ( i = 0; i < MAX_POWERUPS; i++ ) {
4131 			if ( powerup & ( 1 << i ) ) {
4132 				static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0 );
4133 			}
4134 		}
4135 		bool spectate = ( msg.ReadBits( 1 ) != 0 );
4136 		static_cast< idPlayer * >( gameLocal.entities[ client ] )->Spectate( spectate );
4137 	}
4138 }
4139 
4140 /*
4141 ================
4142 idMultiplayerGame::ClientReadWarmupTime
4143 ================
4144 */
4145 void idMultiplayerGame::ClientReadWarmupTime( const idBitMsg &msg ) {
4146 	warmupEndTime = msg.ReadInt();
4147 }
4148 
4149 /*
4150 #ifdef CTF
4151 
4152 	Threewave note:
4153 	The below IsGametype...() functions were implemented for CTF,
4154 	but we did not #ifdef CTF them, because doing so would clutter
4155 	the codebase substantially.  Please consider them part of the merged
4156 	CTF code.
4157 */
4158 
4159 /*
4160 ================
4161 idMultiplayerGame::IsGametypeTeamBased
4162 ================
4163 */
4164 bool idMultiplayerGame::IsGametypeTeamBased( void ) /* CTF */
4165 {
4166 	switch ( gameLocal.gameType )
4167 	{
4168 	case GAME_SP:
4169 	case GAME_DM:
4170 	case GAME_TOURNEY:
4171 	case GAME_LASTMAN:
4172 		return false;
4173 #ifdef CTF
4174 	case GAME_CTF:
4175 #endif
4176 	case GAME_TDM:
4177 		return true;
4178 
4179 	default:
4180 		assert( !"Add support for your new gametype here." );
4181 	}
4182 
4183 	return false;
4184 }
4185 
4186 /*
4187 ================
4188 idMultiplayerGame::IsGametypeFlagBased
4189 ================
4190 */
4191 bool idMultiplayerGame::IsGametypeFlagBased( void )  {
4192 	switch ( gameLocal.gameType )
4193 	{
4194 	case GAME_SP:
4195 	case GAME_DM:
4196 	case GAME_TOURNEY:
4197 	case GAME_LASTMAN:
4198 	case GAME_TDM:
4199 		return false;
4200 
4201 #ifdef CTF
4202 	case GAME_CTF:
4203 		return true;
4204 #endif
4205 
4206 	default:
4207 		assert( !"Add support for your new gametype here." );
4208 	}
4209 
4210 	return false;
4211 
4212 }
4213 #ifdef CTF
4214 
4215 /*
4216 ================
4217 idMultiplayerGame::GetTeamFlag
4218 ================
4219 */
4220 idItemTeam * idMultiplayerGame::GetTeamFlag( int team ) {
4221 	assert( team == 0 || team == 1 );
4222 
4223 	if ( !IsGametypeFlagBased() || ( team != 0 && team != 1 ) ) /* CTF */
4224 		return NULL;
4225 
4226 	// TODO : just call on map start
4227 	FindTeamFlags();
4228 
4229 	return teamFlags[team];
4230 }
4231 
4232 /*
4233 ================
4234 idMultiplayerGame::GetTeamFlag
4235 ================
4236 */
4237 void idMultiplayerGame::FindTeamFlags( void ) {
4238 	const char * flagDefs[2] =
4239 	{
4240 		"team_CTF_redflag",
4241 		"team_CTF_blueflag"
4242 	};
4243 
4244 	for ( int i = 0; i < 2; i++)
4245 	{
4246 		idEntity * entity = gameLocal.FindEntityUsingDef( NULL, flagDefs[i] );
4247 		do
4248 		{
4249 			if ( entity == NULL )
4250 				return;
4251 
4252 			idItemTeam * flag = static_cast<idItemTeam *>(entity);
4253 
4254 			if ( flag->team == i )
4255 			{
4256 				teamFlags[i] = flag;
4257 				break;
4258 			}
4259 
4260 			entity = gameLocal.FindEntityUsingDef( entity, flagDefs[i] );
4261 		} while( entity );
4262 	}
4263 }
4264 
4265 /*
4266 ================
4267 idMultiplayerGame::GetFlagStatus
4268 ================
4269 */
4270 flagStatus_t idMultiplayerGame::GetFlagStatus( int team ) {
4271 	//assert( IsGametypeFlagBased() );
4272 
4273 	idItemTeam *teamFlag = GetTeamFlag( team );
4274 	//assert( teamFlag != NULL );
4275 
4276 	if ( teamFlag != NULL ) {
4277 		if ( teamFlag->carried == false && teamFlag->dropped == false )
4278 			return FLAGSTATUS_INBASE;
4279 
4280 		if ( teamFlag->carried == true )
4281 			return FLAGSTATUS_TAKEN;
4282 
4283 		if ( teamFlag->carried == false && teamFlag->dropped == true )
4284 			return FLAGSTATUS_STRAY;
4285 	}
4286 
4287 	//assert( !"Invalid flag state." );
4288 	return FLAGSTATUS_NONE;
4289 }
4290 
4291 /*
4292 ================
4293 idMultiplayerGame::SetFlagMsgs
4294 ================
4295 */
4296 void idMultiplayerGame::SetFlagMsg( bool b ) {
4297 	flagMsgOn = b;
4298 }
4299 
4300 /*
4301 ================
4302 idMultiplayerGame::IsFlagMsgOn
4303 ================
4304 */
4305 bool idMultiplayerGame::IsFlagMsgOn( void ) {
4306 	return ( GetGameState() == WARMUP || GetGameState() == GAMEON || GetGameState() == SUDDENDEATH ) && flagMsgOn;
4307 }
4308 
4309 
4310 /*
4311 ================
4312 idMultiplayerGame::SetBestGametype
4313 ================
4314 */
4315 void idMultiplayerGame::SetBestGametype( const char * map ) {
4316 	const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" );
4317 	//	const char *map	= gameLocal.serverInfo.GetString( "si_map" );
4318 	int num = declManager->GetNumDecls( DECL_MAPDEF );
4319 	int i, j;
4320 
4321 	for ( i = 0; i < num; i++ ) {
4322 		const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_MAPDEF, i ) );
4323 
4324 		if ( mapDef && idStr::Icmp( mapDef->GetName(), map ) == 0 ) {
4325 			if ( mapDef->dict.GetBool( gametype ) ) {
4326 				// dont change gametype
4327 				return;
4328 			}
4329 
4330 			for ( j = 1; si_gameTypeArgs[ j ]; j++ ) {
4331 				if ( mapDef->dict.GetBool( si_gameTypeArgs[ j ] ) ) {
4332 					si_gameType.SetString( si_gameTypeArgs[ j ] );
4333 					return;
4334 				}
4335 			}
4336 
4337 			// error out, no valid gametype
4338 			return;
4339 		}
4340 	}
4341 }
4342 
4343 /*
4344 ================
4345 idMultiplayerGame::ReloadScoreboard
4346 ================
4347 */
4348 void idMultiplayerGame::ReloadScoreboard() {
4349 	// CTF uses its own scoreboard
4350 	if ( IsGametypeFlagBased() )
4351 		scoreBoard = uiManager->FindGui( "guis/ctfscoreboard.gui", true, false, true );
4352 	else
4353 		scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true );
4354 
4355 	Precache();
4356 }
4357 
4358 
4359 #endif
4360 
4361 #ifdef _D3XP
4362 idStr idMultiplayerGame::GetBestGametype( const char* map, const char* gametype ) {
4363 
4364 	int num = declManager->GetNumDecls( DECL_MAPDEF );
4365 	int i, j;
4366 
4367 	for ( i = 0; i < num; i++ ) {
4368 		const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_MAPDEF, i ) );
4369 
4370 		if ( mapDef && idStr::Icmp( mapDef->GetName(), map ) == 0 ) {
4371 			if ( mapDef->dict.GetBool( gametype ) ) {
4372 				// dont change gametype
4373 				return gametype;
4374 			}
4375 
4376 			for ( j = 1; si_gameTypeArgs[ j ]; j++ ) {
4377 				if ( mapDef->dict.GetBool( si_gameTypeArgs[ j ] ) ) {
4378 					return si_gameTypeArgs[ j ];
4379 				}
4380 			}
4381 
4382 			// error out, no valid gametype
4383 			return "deathmatch";
4384 		}
4385 	}
4386 
4387 	//For testing a new map let it play any gametpye
4388 	return gametype;
4389 }
4390 #endif
4391