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