2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
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.
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.
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/>.
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.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 #include "Game_local.h"
34 // could be a problem if players manage to go down sudden deaths till this .. oh well
35 #define LASTMAN_NOLIVES -20
37 idCVar g_spectatorChat( "g_spectatorChat", "0", CVAR_GAME | CVAR_ARCHIVE | CVAR_BOOL, "let spectators talk to everyone during game" );
39 // global sounds transmitted by index - 0 .. SND_COUNT
40 // sounds in this list get precached on MP start
41 const char *idMultiplayerGame::GlobalSoundStrings[] = {
42 "sound/feedback/voc_youwin.wav",
43 "sound/feedback/voc_youlose.wav",
44 "sound/feedback/fight.wav",
45 "sound/feedback/vote_now.wav",
46 "sound/feedback/vote_passed.wav",
47 "sound/feedback/vote_failed.wav",
48 "sound/feedback/three.wav",
49 "sound/feedback/two.wav",
50 "sound/feedback/one.wav",
51 "sound/feedback/sudden_death.wav",
53 "sound/ctf/flag_capped_yours.wav",
54 "sound/ctf/flag_capped_theirs.wav",
55 "sound/ctf/flag_return.wav",
56 "sound/ctf/flag_taken_yours.wav",
57 "sound/ctf/flag_taken_theirs.wav",
58 "sound/ctf/flag_dropped_yours.wav",
59 "sound/ctf/flag_dropped_theirs.wav"
64 const char *idMultiplayerGame::GameStateStrings[] = {
74 const char *idMultiplayerGame::MPGuis[] = {
82 const char *idMultiplayerGame::ThrottleVars[] = {
89 const char *idMultiplayerGame::ThrottleVarsInEnglish[] = {
96 const int idMultiplayerGame::ThrottleDelay[] = {
104 idMultiplayerGame::idMultiplayerGame
107 idMultiplayerGame::idMultiplayerGame() {
114 lastGameType = GAME_SP;
125 player_blue_flag = -1;
126 player_red_flag = -1;
134 idMultiplayerGame::Shutdown
137 void idMultiplayerGame::Shutdown( void ) {
143 idMultiplayerGame::SetMenuSkin
146 void idMultiplayerGame::SetMenuSkin( void ) {
148 idStr str = cvarSystem->GetCVarString( "mod_validSkins" );
149 idStr uiSkin = cvarSystem->GetCVarString( "ui_skin" );
153 while ( str.Length() ) {
154 int n = str.Find( ";" );
156 skin = str.Left( n );
157 str = str.Right( str.Length() - n - 1 );
162 if ( skin.Icmp( uiSkin ) == 0 ) {
168 for ( int i = 0; i < count; i++ ) {
169 mainGui->SetStateInt( va( "skin%i", i+1 ), 0 );
171 mainGui->SetStateInt( va( "skin%i", skinId ), 1 );
176 idMultiplayerGame::Reset
179 void idMultiplayerGame::Reset() {
181 assert( !scoreBoard && !spectateGui && !guiChat && !mainGui && !mapList );
184 // CTF uses its own scoreboard
185 if ( IsGametypeFlagBased() )
186 scoreBoard = uiManager->FindGui( "guis/ctfscoreboard.gui", true, false, true );
189 scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true );
191 spectateGui = uiManager->FindGui( "guis/spectate.gui", true, false, true );
192 guiChat = uiManager->FindGui( "guis/chat.gui", true, false, true );
193 mainGui = uiManager->FindGui( "guis/mpmain.gui", true, false, true );
194 mapList = uiManager->AllocListGUI( );
195 mapList->Config( mainGui, "mapList" );
196 // set this GUI so that our Draw function is still called when it becomes the active/fullscreen GUI
197 mainGui->SetStateBool( "gameDraw", true );
198 mainGui->SetKeyBindingNames();
199 mainGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) );
201 msgmodeGui = uiManager->FindGui( "guis/mpmsgmode.gui", true, false, true );
202 msgmodeGui->SetStateBool( "gameDraw", true );
210 idMultiplayerGame::ServerClientConnect
213 void idMultiplayerGame::ServerClientConnect( int clientNum ) {
214 memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
219 idMultiplayerGame::SpawnPlayer
222 void idMultiplayerGame::SpawnPlayer( int clientNum ) {
224 bool ingame = playerState[ clientNum ].ingame;
226 memset( &playerState[ clientNum ], 0, sizeof( playerState[ clientNum ] ) );
227 if ( !gameLocal.isClient ) {
228 idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
229 p->spawnedTime = gameLocal.time;
231 if ( IsGametypeTeamBased() ) { /* CTF */
232 SwitchToTeam( clientNum, -1, p->team );
235 if ( gameLocal.gameType == GAME_TOURNEY && gameState == GAMEON ) {
238 playerState[ clientNum ].ingame = ingame;
244 idMultiplayerGame::Clear
247 void idMultiplayerGame::Clear() {
250 gameState = INACTIVE;
251 nextState = INACTIVE;
257 matchStartedTime = 0;
258 currentTourneyPlayer[ 0 ] = -1;
259 currentTourneyPlayer[ 1 ] = -1;
260 one = two = three = false;
261 memset( &playerState, 0 , sizeof( playerState ) );
264 bCurrentMenuMsg = false;
273 uiManager->FreeListGUI( mapList );
276 fragLimitTimeout = 0;
277 memset( &switchThrottle, 0, sizeof( switchThrottle ) );
278 voiceChatThrottle = 0;
279 for ( i = 0; i < NUM_CHAT_NOTIFY; i++ ) {
280 chatHistory[ i ].line.Clear();
290 idMultiplayerGame::ClearGuis
293 void idMultiplayerGame::ClearGuis() {
296 for ( i = 0; i < MAX_CLIENTS; i++ ) {
297 scoreBoard->SetStateString( va( "player%i",i+1 ), "" );
298 scoreBoard->SetStateString( va( "player%i_score", i+1 ), "" );
299 scoreBoard->SetStateString( va( "player%i_tdm_tscore", i+1 ), "" );
300 scoreBoard->SetStateString( va( "player%i_tdm_score", i+1 ), "" );
301 scoreBoard->SetStateString( va( "player%i_wins", i+1 ), "" );
302 scoreBoard->SetStateString( va( "player%i_status", i+1 ), "" );
303 scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 );
304 scoreBoard->SetStateInt( "rank_self", 0 );
306 idPlayer *player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
307 if ( !player || !player->hud ) {
310 player->hud->SetStateString( va( "player%i",i+1 ), "" );
311 player->hud->SetStateString( va( "player%i_score", i+1 ), "" );
312 player->hud->SetStateString( va( "player%i_ready", i+1 ), "" );
313 scoreBoard->SetStateInt( va( "rank%i", i+1 ), 0 );
314 player->hud->SetStateInt( "rank_self", 0 );
326 idMultiplayerGame::ClearHUDStatus
329 void idMultiplayerGame::ClearHUDStatus( void ) {
332 for ( i = 0; i < MAX_CLIENTS; i++ ) {
334 idPlayer *player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
335 if ( !player || !player->hud ) {
339 player->hud->SetStateInt( "red_flagstatus", 0 );
340 player->hud->SetStateInt( "blue_flagstatus", 0 );
341 if ( IsGametypeFlagBased())
342 player->hud->SetStateInt( "self_team", player->team );
344 player->hud->SetStateInt( "self_team", -1 ); // Invisible.
351 idMultiplayerGame::GetFlagPoints
353 Gets number of captures in CTF game.
359 int idMultiplayerGame::GetFlagPoints( int team )
363 return teamPoints[ team ];
369 idMultiplayerGame::UpdatePlayerRanks
372 void idMultiplayerGame::UpdatePlayerRanks() {
374 idPlayer *players[MAX_CLIENTS];
378 memset( players, 0, sizeof( players ) );
379 numRankedPlayers = 0;
381 for ( i = 0; i < gameLocal.numClients; i++ ) {
382 ent = gameLocal.entities[ i ];
383 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
386 player = static_cast< idPlayer * >( ent );
387 if ( !CanPlay( player ) ) {
390 if ( gameLocal.gameType == GAME_TOURNEY ) {
391 if ( i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] ) {
395 if ( gameLocal.gameType == GAME_LASTMAN && playerState[ i ].fragCount == LASTMAN_NOLIVES ) {
398 for ( j = 0; j < numRankedPlayers; j++ ) {
401 if ( IsGametypeTeamBased() ) { /* CTF */
402 if ( player->team != players[ j ]->team ) {
403 if ( playerState[ i ].teamFragCount > playerState[ players[ j ]->entityNumber ].teamFragCount ) {
406 } else if ( playerState[ i ].teamFragCount == playerState[ players[ j ]->entityNumber ].teamFragCount && player->team < players[ j ]->team ) {
407 // at equal scores, sort by team number
410 } else if ( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount ) {
411 // in the same team, sort by frag count
415 insert = ( playerState[ i ].fragCount > playerState[ players[ j ]->entityNumber ].fragCount );
418 for ( k = numRankedPlayers; k > j; k-- ) {
419 players[ k ] = players[ k-1 ];
421 players[ j ] = player;
425 if ( j == numRankedPlayers ) {
426 players[ numRankedPlayers ] = player;
431 memcpy( rankedPlayers, players, sizeof( players ) );
437 idMultiplayerGame::UpdateRankColor
440 void idMultiplayerGame::UpdateRankColor( idUserInterface *gui, const char *mask, int i, const idVec3 &vec ) {
441 for ( int j = 1; j < 4; j++ ) {
442 gui->SetStateFloat( va( mask, i, j ), vec[ j - 1 ] );
448 idMultiplayerGame::UpdateScoreboard
451 void idMultiplayerGame::UpdateScoreboard( idUserInterface *scoreBoard, idPlayer *player ) {
463 scoreBoard->SetStateString( "scoretext", gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04242" ) : common->GetLanguageDict()->GetString( "#str_04243" ) );
465 iline = 0; // the display lines
466 if ( gameState != WARMUP ) {
467 for ( i = 0; i < numRankedPlayers; i++ ) {
470 scoreBoard->SetStateString( va( "player%i", iline ), rankedPlayers[ i ]->GetUserInfo()->GetString( "ui_name" ) );
471 if ( IsGametypeTeamBased() ) { /* CTF */
472 value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
473 scoreBoard->SetStateInt( va( "player%i_tdm_score", iline ), value );
474 value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].teamFragCount );
475 scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), va( "/ %i", value ) );
476 scoreBoard->SetStateString( va( "player%i_score", iline ), "" );
478 value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
479 scoreBoard->SetStateInt( va( "player%i_score", iline ), value );
480 scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
481 scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
484 value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ rankedPlayers[ i ]->entityNumber ].wins );
485 scoreBoard->SetStateInt( va( "player%i_wins", iline ), value );
486 scoreBoard->SetStateInt( va( "player%i_ping", iline ), playerState[ rankedPlayers[ i ]->entityNumber ].ping );
487 // set the color band
488 scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
489 UpdateRankColor( scoreBoard, "rank%i_color%i", iline, rankedPlayers[ i ]->colorBar );
490 if ( rankedPlayers[ i ] == player ) {
491 // highlight who we are
492 scoreBoard->SetStateInt( "rank_self", iline );
497 // if warmup, this draws everyone, otherwise it goes over spectators only
498 // when doing warmup we loop twice to draw ready/not ready first *then* spectators
499 // NOTE: in tourney, shows spectators according to their playing rank order?
500 for ( k = 0; k < ( gameState == WARMUP ? 2 : 1 ); k++ ) {
501 for ( i = 0; i < MAX_CLIENTS; i++ ) {
502 ent = gameLocal.entities[ i ];
503 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
506 if ( gameState != WARMUP ) {
507 // check he's not covered by ranks already
508 for ( j = 0; j < numRankedPlayers; j++ ) {
509 if ( ent == rankedPlayers[ j ] ) {
513 if ( j != numRankedPlayers ) {
517 p = static_cast< idPlayer * >( ent );
518 if ( gameState == WARMUP ) {
519 if ( k == 0 && p->spectating ) {
522 if ( k == 1 && !p->spectating ) {
528 if ( !playerState[ i ].ingame ) {
529 scoreBoard->SetStateString( va( "player%i", iline ), common->GetLanguageDict()->GetString( "#str_04244" ) );
530 scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04245" ) );
532 scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
534 scoreBoard->SetStateString( va( "player%i", iline ), gameLocal.userInfo[ i ].GetString( "ui_name" ) );
535 if ( gameState == WARMUP ) {
536 if ( p->spectating ) {
537 scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04246" ) );
539 scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
541 scoreBoard->SetStateString( va( "player%i_score", iline ), p->IsReady() ? common->GetLanguageDict()->GetString( "#str_04247" ) : common->GetLanguageDict()->GetString( "#str_04248" ) );
542 // set the color band
543 scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
544 UpdateRankColor( scoreBoard, "rank%i_color%i", iline, p->colorBar );
547 if ( gameLocal.gameType == GAME_LASTMAN && playerState[ i ].fragCount == LASTMAN_NOLIVES ) {
548 scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_06736" ) );
549 // set the color band
550 scoreBoard->SetStateInt( va( "rank%i", iline ), 1 );
551 UpdateRankColor( scoreBoard, "rank%i_color%i", iline, p->colorBar );
553 scoreBoard->SetStateString( va( "player%i_score", iline ), common->GetLanguageDict()->GetString( "#str_04246" ) );
555 scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
559 scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
560 scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
561 scoreBoard->SetStateString( va( "player%i_wins", iline ), "" );
562 scoreBoard->SetStateInt( va( "player%i_ping", iline ), playerState[ i ].ping );
563 if ( i == player->entityNumber ) {
564 // highlight who we are
565 scoreBoard->SetStateInt( "rank_self", iline );
570 // clear remaining lines (empty slots)
573 while ( iline < MAX_CLIENTS ) { //Max players is now 8
575 while ( iline < 5 ) {
577 scoreBoard->SetStateString( va( "player%i", iline ), "" );
578 scoreBoard->SetStateString( va( "player%i_score", iline ), "" );
579 scoreBoard->SetStateString( va( "player%i_tdm_tscore", iline ), "" );
580 scoreBoard->SetStateString( va( "player%i_tdm_score", iline ), "" );
581 scoreBoard->SetStateString( va( "player%i_wins", iline ), "" );
582 scoreBoard->SetStateString( va( "player%i_ping", iline ), "" );
583 scoreBoard->SetStateInt( va( "rank%i", iline ), 0 );
587 gameinfo = va( "%s: %s", common->GetLanguageDict()->GetString( "#str_02376" ), gameLocal.serverInfo.GetString( "si_gameType" ) );
588 if ( gameLocal.gameType == GAME_LASTMAN ) {
589 if ( gameState == GAMEON || gameState == SUDDENDEATH ) {
590 livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_04264" ), startFragLimit );
592 livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_04264" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
595 } else if ( gameLocal.gameType != GAME_CTF ) {
599 livesinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_01982" ), gameLocal.serverInfo.GetInt( "si_fragLimit" ) );
601 if ( gameLocal.serverInfo.GetInt( "si_timeLimit" ) > 0 ) {
602 timeinfo = va( "%s: %i", common->GetLanguageDict()->GetString( "#str_01983" ), gameLocal.serverInfo.GetInt( "si_timeLimit" ) );
604 timeinfo = va("%s", common->GetLanguageDict()->GetString( "#str_07209" ));
606 scoreBoard->SetStateString( "gameinfo", gameinfo );
607 scoreBoard->SetStateString( "livesinfo", livesinfo );
608 scoreBoard->SetStateString( "timeinfo", timeinfo );
610 scoreBoard->Redraw( gameLocal.time );
616 idMultiplayerGame::UpdateCTFScoreboard
619 void idMultiplayerGame::UpdateCTFScoreboard( idUserInterface *scoreBoard, idPlayer *player ) {
626 int ilines[2] = {0,0};
629 char redTeam[] = "red";
630 char blueTeam[] = "blue";
631 char *curTeam = NULL;
634 scoreBoard->SetStateString( "scoretext", gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04242" ) : common->GetLanguageDict()->GetString( "#str_04243" ) );
636 // Blank the flag carrier on the scoreboard. We update these in the loop below if necessary.
637 if ( this->player_blue_flag == -1 )
638 scoreBoard->SetStateInt( "player_blue_flag", 0 );
640 if ( this->player_red_flag == -1 )
641 scoreBoard->SetStateInt( "player_red_flag", 0 );
643 if ( gameState != WARMUP ) {
644 for ( i = 0; i < numRankedPlayers; i++ ) {
646 idPlayer *player = rankedPlayers[ i ];
649 if ( player->team == 0 )
654 // Increase the appropriate iline
655 assert( player->team <= 1 );
656 ilines[ player->team ]++;
659 // Update the flag status
660 if ( this->player_blue_flag == player->entityNumber )
661 scoreBoard->SetStateInt( "player_blue_flag", ilines[ player->team ] );
663 if ( player->team == 1 && this->player_red_flag == player->entityNumber )
664 scoreBoard->SetStateInt( "player_red_flag", ilines[ player->team ] );
669 scoreBoard->SetStateString( va( "player%i_%s", ilines[ player->team ], curTeam ), player->GetUserInfo()->GetString( "ui_name" ) );
671 if ( IsGametypeTeamBased() ) {
673 value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
674 scoreBoard->SetStateInt( va( "player%i_%s_score", ilines[ player->team ], curTeam ), value );
676 /* Team score and score, blanked */
677 scoreBoard->SetStateString( va( "player%i_%s_tscore", ilines[ player->team ], curTeam ), "" );
678 //scoreBoard->SetStateString( va( "player%i_%s_score", ilines[ player->team ], curTeam ), "" );
682 value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[ rankedPlayers[ i ]->entityNumber ].wins );
683 scoreBoard->SetStateInt( va( "player%i_%s_wins", ilines[ player->team ], curTeam ), value );
686 scoreBoard->SetStateInt( va( "player%i_%s_ping", ilines[ player->team ], curTeam ), playerState[ rankedPlayers[ i ]->entityNumber ].ping );
690 for ( i = 0; i < MAX_CLIENTS; i++ ) {
692 ent = gameLocal.entities[ i ];
693 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
697 if ( gameState != WARMUP ) {
698 // check he's not covered by ranks already
699 for ( j = 0; j < numRankedPlayers; j++ ) {
700 if ( ent == rankedPlayers[ j ] ) {
705 if ( j != numRankedPlayers ) {
710 player = static_cast< idPlayer * >( ent );
712 if ( player->spectating )
715 if ( player->team == 0 )
720 ilines[ player->team ]++;
726 if ( !playerState[ i ].ingame ) {
728 /* "New Player" on player's name location */
729 scoreBoard->SetStateString( va( "player%i_%s", ilines[ player->team ], curTeam ), common->GetLanguageDict()->GetString( "#str_04244" ) );
731 /* "Connecting" on player's score location */
732 scoreBoard->SetStateString( va( "player%i_%s_score", ilines[ player->team ], curTeam ), common->GetLanguageDict()->GetString( "#str_04245" ) );
737 /* Player's name in player's name location */
738 if ( !player->spectating )
739 scoreBoard->SetStateString( va( "player%i_%s", ilines[ player->team ], curTeam ), gameLocal.userInfo[ i ].GetString( "ui_name" ) );
741 if ( gameState == WARMUP ) {
743 if ( player->spectating ) {
745 /* "Spectating" on player's score location */
746 scoreBoard->SetStateString( va( "player%i_%s_score", ilines[ player->team ], curTeam ), common->GetLanguageDict()->GetString( "#str_04246" ) );
750 /* Display "ready" in player's score location if they're ready. Display nothing if not. No room for 'not ready'. */
751 scoreBoard->SetStateString( va( "player%i_%s_score", ilines[ player->team ], curTeam ), player->IsReady() ? common->GetLanguageDict()->GetString( "#str_04247" ) : "" );
759 // Clear remaining slots
760 for ( i = 0; i < 2; i++ )
767 for ( j = ilines[ i ]+1; j <= 8; j++ )
769 scoreBoard->SetStateString( va( "player%i_%s", j, curTeam ), "" );
770 scoreBoard->SetStateString( va( "player%i_%s_score", j, curTeam ), "" );
771 scoreBoard->SetStateString( va( "player%i_%s_wins", j, curTeam ), "" );
772 scoreBoard->SetStateString( va( "player%i_%s_ping", j, curTeam ), "" );
773 scoreBoard->SetStateInt( "rank_self", 0 );
778 // Don't display "CTF" -- if this scoreboard comes up, it should be apparent.
780 if ( gameLocal.gameType == GAME_CTF ) {
782 int captureLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
784 if ( captureLimit > MP_CTF_MAXPOINTS )
785 captureLimit = MP_CTF_MAXPOINTS;
787 int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" );
789 /* Prints "Capture Limit: %i" at the bottom of the scoreboard, left */
791 scoreBoard->SetStateString( "gameinfo_red", va( common->GetLanguageDict()->GetString( "#str_11108" ), captureLimit) );
793 scoreBoard->SetStateString( "gameinfo_red", "" );
795 /* Prints "Time Limit: %i" at the bottom of the scoreboard, right */
797 scoreBoard->SetStateString( "gameinfo_blue", va( common->GetLanguageDict()->GetString( "#str_11109" ), timeLimit) );
799 scoreBoard->SetStateString( "gameinfo_blue", "" );
805 scoreBoard->SetStateInt( "red_team_score", GetFlagPoints( 0 ) );
806 scoreBoard->SetStateInt( "blue_team_score", GetFlagPoints( 1 ) );
808 // Handle flag status changed event
809 scoreBoard->HandleNamedEvent( "BlueFlagStatusChange" );
810 scoreBoard->HandleNamedEvent( "RedFlagStatusChange" );
812 scoreBoard->Redraw( gameLocal.time );
822 idMultiplayerGame::GameTime
825 const char *idMultiplayerGame::GameTime() {
826 static char buff[16];
829 if ( gameState == COUNTDOWN ) {
830 ms = warmupEndTime - gameLocal.realClientTime;
833 strcpy( buff, "WMP --" );
835 sprintf( buff, "WMP %i", s );
838 int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" );
840 ms = ( timeLimit * 60000 ) - ( gameLocal.time - matchStartedTime );
842 ms = gameLocal.time - matchStartedTime;
854 sprintf( buff, "%i:%i%i", m, t, s );
861 idMultiplayerGame::NumActualClients
864 int idMultiplayerGame::NumActualClients( bool countSpectators, int *teamcounts ) {
869 teamcounts[ 0 ] = teamcounts[ 1 ] = 0;
871 for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
872 idEntity *ent = gameLocal.entities[ i ];
873 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
876 p = static_cast< idPlayer * >( ent );
877 if ( countSpectators || CanPlay( p ) ) {
880 if ( teamcounts && CanPlay( p ) ) {
881 teamcounts[ p->team ]++;
889 idMultiplayerGame::EnoughClientsToPlay
892 bool idMultiplayerGame::EnoughClientsToPlay() {
894 int clients = NumActualClients( false, &team[ 0 ] );
895 if ( IsGametypeTeamBased() ) { /* CTF */
896 return clients >= 2 && team[ 0 ] && team[ 1 ];
904 idMultiplayerGame::AllPlayersReady
907 bool idMultiplayerGame::AllPlayersReady() {
913 if ( NumActualClients( false, &team[ 0 ] ) <= 1 ) {
917 if ( IsGametypeTeamBased() ) { /* CTF */
918 if ( !team[ 0 ] || !team[ 1 ] ) {
923 if ( !gameLocal.serverInfo.GetBool( "si_warmup" ) ) {
927 for( i = 0; i < gameLocal.numClients; i++ ) {
928 if ( gameLocal.gameType == GAME_TOURNEY && i != currentTourneyPlayer[ 0 ] && i != currentTourneyPlayer[ 1 ] ) {
931 ent = gameLocal.entities[ i ];
932 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
935 p = static_cast< idPlayer * >( ent );
936 if ( CanPlay( p ) && !p->IsReady() ) {
947 idMultiplayerGame::FragLimitHit
948 return the winning player (team player)
949 if there is no FragLeader(), the game is tied and we return NULL
952 idPlayer *idMultiplayerGame::FragLimitHit() {
954 int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
958 if ( IsGametypeFlagBased() ) /* CTF */
962 leader = FragLeader();
967 if ( fragLimit <= 0 ) {
968 fragLimit = MP_PLAYER_MAXFRAGS;
971 if ( gameLocal.gameType == GAME_LASTMAN ) {
972 // we have a leader, check if any other players have frags left
973 assert( !static_cast< idPlayer * >( leader )->lastManOver );
974 for( i = 0 ; i < gameLocal.numClients ; i++ ) {
975 idEntity *ent = gameLocal.entities[ i ];
976 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
979 if ( !CanPlay( static_cast< idPlayer * >( ent ) ) ) {
982 if ( ent == leader ) {
985 if ( playerState[ ent->entityNumber ].fragCount > 0 ) {
989 // there is a leader, his score may even be negative, but no one else has frags left or is !lastManOver
991 } else if ( IsGametypeTeamBased() ) { /* CTF */
992 if ( playerState[ leader->entityNumber ].teamFragCount >= fragLimit ) {
996 if ( playerState[ leader->entityNumber ].fragCount >= fragLimit ) {
1006 idMultiplayerGame::TimeLimitHit
1009 bool idMultiplayerGame::TimeLimitHit() {
1010 int timeLimit = gameLocal.serverInfo.GetInt( "si_timeLimit" );
1012 if ( gameLocal.time >= matchStartedTime + timeLimit * 60000 ) {
1023 idMultiplayerGame::WinningTeam
1025 -1 if tied or no players
1028 int idMultiplayerGame::WinningTeam( void ) {
1029 if ( teamPoints[0] > teamPoints[1] )
1031 if ( teamPoints[0] < teamPoints[1] )
1038 idMultiplayerGame::PointLimitHit
1041 bool idMultiplayerGame::PointLimitHit( void ) {
1042 int pointLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
1044 // default to MP_CTF_MAXPOINTS if needed
1045 if ( pointLimit > MP_CTF_MAXPOINTS )
1046 pointLimit = MP_CTF_MAXPOINTS;
1047 else if ( pointLimit <= 0 )
1048 pointLimit = MP_CTF_MAXPOINTS;
1050 if ( teamPoints[0] == teamPoints[1] )
1053 if ( teamPoints[0] >= pointLimit ||
1054 teamPoints[1] >= pointLimit )
1063 idMultiplayerGame::FragLeader
1064 return the current winner ( or a player from the winning team )
1068 idPlayer *idMultiplayerGame::FragLeader( void ) {
1070 int frags[ MAX_CLIENTS ];
1071 idPlayer *leader = NULL;
1076 bool teamLead[ 2 ] = { false, false };
1078 for ( i = 0 ; i < gameLocal.numClients ; i++ ) {
1079 ent = gameLocal.entities[ i ];
1080 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1083 if ( !CanPlay( static_cast< idPlayer * >( ent ) ) ) {
1086 if ( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] ) {
1089 if ( static_cast< idPlayer * >( ent )->lastManOver ) {
1093 int fragc = ( IsGametypeTeamBased() ) ? playerState[i].teamFragCount : playerState[i].fragCount; /* CTF */
1094 if ( fragc > high ) {
1101 for ( i = 0; i < gameLocal.numClients; i++ ) {
1102 ent = gameLocal.entities[ i ];
1103 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1106 p = static_cast< idPlayer * >( ent );
1107 p->SetLeader( false );
1109 if ( !CanPlay( p ) ) {
1112 if ( gameLocal.gameType == GAME_TOURNEY && ent->entityNumber != currentTourneyPlayer[ 0 ] && ent->entityNumber != currentTourneyPlayer[ 1 ] ) {
1115 if ( p->lastManOver ) {
1118 if ( p->spectating ) {
1122 if ( frags[ i ] >= high ) {
1125 p->SetLeader( true );
1126 if ( IsGametypeTeamBased() ) { /* CTF */
1127 teamLead[ p->team ] = true;
1132 if ( !IsGametypeTeamBased() ) { /* CTF */
1133 // more than one player at the highest frags
1140 if ( teamLead[ 0 ] && teamLead[ 1 ] ) {
1141 // even game in team play
1150 idGameLocal::UpdateWinsLosses
1153 void idMultiplayerGame::UpdateWinsLosses( idPlayer *winner ) {
1155 // run back through and update win/loss count
1156 for( int i = 0; i < gameLocal.numClients; i++ ) {
1157 idEntity *ent = gameLocal.entities[ i ];
1158 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1161 idPlayer *player = static_cast<idPlayer *>(ent);
1162 if ( IsGametypeTeamBased() ) { /* CTF */
1163 if ( player == winner || ( player != winner && player->team == winner->team ) ) {
1164 playerState[ i ].wins++;
1165 PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1167 PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1169 } else if ( gameLocal.gameType == GAME_LASTMAN ) {
1170 if ( player == winner ) {
1171 playerState[ i ].wins++;
1172 PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1173 } else if ( !player->wantSpectate ) {
1174 PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1176 } else if ( gameLocal.gameType == GAME_TOURNEY ) {
1177 if ( player == winner ) {
1178 playerState[ i ].wins++;
1179 PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1180 } else if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
1181 PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1184 if ( player == winner ) {
1185 playerState[i].wins++;
1186 PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1187 } else if ( !player->wantSpectate ) {
1188 PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1194 else if ( IsGametypeFlagBased() ) { /* CTF */
1195 int winteam = WinningTeam();
1197 if ( winteam != -1 ) // TODO : print a message telling it why the hell the game ended with no winning team?
1198 for( int i = 0; i < gameLocal.numClients; i++ ) {
1199 idEntity *ent = gameLocal.entities[ i ];
1200 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1203 idPlayer *player = static_cast<idPlayer *>(ent);
1205 if ( player->team == winteam ) {
1206 PlayGlobalSound( player->entityNumber, SND_YOUWIN );
1208 PlayGlobalSound( player->entityNumber, SND_YOULOSE );
1215 lastWinner = winner->entityNumber;
1224 idMultiplayerGame::TeamScoreCTF
1227 void idMultiplayerGame::TeamScoreCTF( int team, int delta ) {
1228 if ( team < 0 || team > 1 )
1231 teamPoints[team] += delta;
1233 if ( gameState == GAMEON || gameState == SUDDENDEATH )
1234 PrintMessageEvent( -1, MSG_SCOREUPDATE, teamPoints[0], teamPoints[1] );
1239 idMultiplayerGame::PlayerScoreCTF
1242 void idMultiplayerGame::PlayerScoreCTF( int playerIdx, int delta ) {
1243 if ( playerIdx < 0 || playerIdx >= MAX_CLIENTS )
1246 playerState[ playerIdx ].fragCount += delta;
1251 idMultiplayerGame::GetFlagCarrier
1254 int idMultiplayerGame::GetFlagCarrier( int team ) {
1255 int iFlagCarrier = -1;
1257 for ( int i = 0; i < gameLocal.numClients; i++ ) {
1258 idEntity * ent = gameLocal.entities[ i ];
1259 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1263 idPlayer * player = static_cast<idPlayer *>( ent );
1264 if ( player->team != team )
1267 if ( player->carryingFlag ) {
1268 if ( iFlagCarrier != -1 )
1269 gameLocal.Warning( "BUG: more than one flag carrier on %s team", team == 0 ? "red" : "blue" );
1274 return iFlagCarrier;
1283 idMultiplayerGame::TeamScore
1286 void idMultiplayerGame::TeamScore( int entityNumber, int team, int delta ) {
1287 playerState[ entityNumber ].fragCount += delta;
1288 for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
1289 idEntity *ent = gameLocal.entities[ i ];
1290 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1293 idPlayer *player = static_cast<idPlayer *>(ent);
1294 if ( player->team == team ) {
1295 playerState[ player->entityNumber ].teamFragCount += delta;
1302 idMultiplayerGame::PlayerDeath
1305 void idMultiplayerGame::PlayerDeath( idPlayer *dead, idPlayer *killer, bool telefrag ) {
1307 // don't do PrintMessageEvent and shit
1308 assert( !gameLocal.isClient );
1311 if ( gameLocal.gameType == GAME_LASTMAN ) {
1312 playerState[ dead->entityNumber ].fragCount--;
1314 } else if ( IsGametypeTeamBased() ) { /* CTF */
1315 if ( killer == dead || killer->team == dead->team ) {
1316 // suicide or teamkill
1317 TeamScore( killer->entityNumber, killer->team, -1 );
1319 TeamScore( killer->entityNumber, killer->team, +1 );
1322 playerState[ killer->entityNumber ].fragCount += ( killer == dead ) ? -1 : 1;
1326 if ( killer && killer == dead ) {
1327 PrintMessageEvent( -1, MSG_SUICIDE, dead->entityNumber );
1328 } else if ( killer ) {
1330 PrintMessageEvent( -1, MSG_TELEFRAGGED, dead->entityNumber, killer->entityNumber );
1331 } else if ( IsGametypeTeamBased() && dead->team == killer->team ) { /* CTF */
1332 PrintMessageEvent( -1, MSG_KILLEDTEAM, dead->entityNumber, killer->entityNumber );
1334 PrintMessageEvent( -1, MSG_KILLED, dead->entityNumber, killer->entityNumber );
1337 PrintMessageEvent( -1, MSG_DIED, dead->entityNumber );
1338 playerState[ dead->entityNumber ].fragCount--;
1344 idMultiplayerGame::PlayerStats
1347 void idMultiplayerGame::PlayerStats( int clientNum, char *data, const int len ) {
1354 // make sure we don't exceed the client list
1355 if ( clientNum < 0 || clientNum > gameLocal.numClients ) {
1359 // find which team this player is on
1360 ent = gameLocal.entities[ clientNum ];
1361 if ( ent && ent->IsType( idPlayer::Type ) ) {
1362 team = static_cast< idPlayer * >(ent)->team;
1367 idStr::snPrintf( data, len, "team=%d score=%ld tks=%ld", team, playerState[ clientNum ].fragCount, playerState[ clientNum ].teamFragCount );
1375 idMultiplayerGame::PlayerVote
1378 void idMultiplayerGame::PlayerVote( int clientNum, playerVote_t vote ) {
1379 playerState[ clientNum ].vote = vote;
1384 idMultiplayerGame::DumpTourneyLine
1387 void idMultiplayerGame::DumpTourneyLine( void ) {
1389 for ( i = 0; i < gameLocal.numClients; i++ ) {
1390 if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
1391 common->Printf( "client %d: rank %d\n", i, static_cast< idPlayer * >( gameLocal.entities[ i ] )->tourneyRank );
1398 idMultiplayerGame::NewState
1401 void idMultiplayerGame::NewState( gameState_t news, idPlayer *player ) {
1403 byte msgBuf[MAX_GAME_MESSAGE_SIZE];
1406 assert( news != gameState );
1407 assert( !gameLocal.isClient );
1408 gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ news ] );
1411 gameLocal.LocalMapRestart();
1412 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1413 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_RESTART );
1414 outMsg.WriteBits( 0, 1 );
1415 networkSystem->ServerSendReliableMessage( -1, outMsg );
1424 PlayGlobalSound( -1, SND_FIGHT );
1425 matchStartedTime = gameLocal.time;
1426 fragLimitTimeout = 0;
1427 for( i = 0; i < gameLocal.numClients; i++ ) {
1428 idEntity *ent = gameLocal.entities[ i ];
1429 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1432 idPlayer *p = static_cast<idPlayer *>( ent );
1433 p->SetLeader( false ); // don't carry the flag from previous games
1434 if ( gameLocal.gameType == GAME_TOURNEY && currentTourneyPlayer[ 0 ] != i && currentTourneyPlayer[ 1 ] != i ) {
1435 p->ServerSpectate( true );
1438 int fragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
1439 int startingCount = ( gameLocal.gameType == GAME_LASTMAN ) ? fragLimit : 0;
1440 playerState[ i ].fragCount = startingCount;
1441 playerState[ i ].teamFragCount = startingCount;
1442 if ( !static_cast<idPlayer *>(ent)->wantSpectate ) {
1443 static_cast<idPlayer *>(ent)->ServerSpectate( false );
1444 if ( gameLocal.gameType == GAME_TOURNEY ) {
1449 if ( CanPlay( p ) ) {
1450 p->lastManPresent = true;
1452 p->lastManPresent = false;
1455 cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
1456 switchThrottle[ 1 ] = 0; // passby the throttle
1457 startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
1462 SetFlagMsg( false );
1464 nextState = INACTIVE; // used to abort a game. cancel out any upcoming state change
1465 // set all players not ready and spectating
1466 for( i = 0; i < gameLocal.numClients; i++ ) {
1467 idEntity *ent = gameLocal.entities[ i ];
1468 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1471 static_cast< idPlayer *>( ent )->forcedReady = false;
1472 static_cast<idPlayer *>(ent)->ServerSpectate( true );
1474 UpdateWinsLosses( player );
1481 PrintMessageEvent( -1, MSG_SUDDENDEATH );
1482 PlayGlobalSound( -1, SND_SUDDENDEATH );
1489 warmupEndTime = gameLocal.time + 1000*cvarSystem->GetCVarInteger( "g_countDown" );
1491 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1492 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME );
1493 outMsg.WriteLong( warmupEndTime );
1494 networkSystem->ServerSendReliableMessage( -1, outMsg );
1503 if ( IsGametypeFlagBased() ) {
1504 // reset player scores to zero, only required for CTF
1505 for( i = 0; i < gameLocal.numClients; i++ ) {
1506 idEntity *ent = gameLocal.entities[ i ];
1507 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1510 playerState[ i ].fragCount = 0;
1524 idMultiplayerGame::FillTourneySlots
1525 NOTE: called each frame during warmup to keep the tourney slots filled
1528 void idMultiplayerGame::FillTourneySlots( ) {
1529 int i, j, rankmax, rankmaxindex;
1533 // fill up the slots based on tourney ranks
1534 for ( i = 0; i < 2; i++ ) {
1535 if ( currentTourneyPlayer[ i ] != -1 ) {
1540 for ( j = 0; j < gameLocal.numClients; j++ ) {
1541 ent = gameLocal.entities[ j ];
1542 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1545 if ( currentTourneyPlayer[ 0 ] == j || currentTourneyPlayer[ 1 ] == j ) {
1548 p = static_cast< idPlayer * >( ent );
1549 if ( p->wantSpectate ) {
1552 if ( p->tourneyRank >= rankmax ) {
1553 // when ranks are equal, use time in game
1554 if ( p->tourneyRank == rankmax ) {
1555 assert( rankmaxindex >= 0 );
1556 if ( p->spawnedTime > static_cast< idPlayer * >( gameLocal.entities[ rankmaxindex ] )->spawnedTime ) {
1560 rankmax = static_cast< idPlayer * >( ent )->tourneyRank;
1564 currentTourneyPlayer[ i ] = rankmaxindex; // may be -1 if we found nothing
1570 idMultiplayerGame::UpdateTourneyLine
1571 we manipulate tourneyRank on player entities for internal ranking. it's easier to deal with.
1572 but we need a real wait list to be synced down to clients for GUI
1573 ignore current players, ignore wantSpectate
1576 void idMultiplayerGame::UpdateTourneyLine( void ) {
1577 int i, j, imax, max, globalmax = -1;
1580 assert( !gameLocal.isClient );
1581 if ( gameLocal.gameType != GAME_TOURNEY ) {
1585 for ( j = 1; j <= gameLocal.numClients; j++ ) {
1586 max = -1; imax = -1;
1587 for ( i = 0; i < gameLocal.numClients; i++ ) {
1588 if ( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i ) {
1591 p = static_cast< idPlayer * >( gameLocal.entities[ i ] );
1592 if ( !p || p->wantSpectate ) {
1595 if ( p->tourneyRank > max && ( globalmax == -1 || p->tourneyRank < globalmax ) ) {
1597 max = p->tourneyRank;
1606 outMsg.Init( msgBuf, sizeof( msgBuf ) );
1607 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_TOURNEYLINE );
1608 outMsg.WriteByte( j );
1609 networkSystem->ServerSendReliableMessage( imax, outMsg );
1617 idMultiplayerGame::CycleTourneyPlayers
1620 void idMultiplayerGame::CycleTourneyPlayers( ) {
1625 currentTourneyPlayer[ 0 ] = -1;
1626 currentTourneyPlayer[ 1 ] = -1;
1627 // if any, winner from last round will play again
1628 if ( lastWinner != -1 ) {
1629 idEntity *ent = gameLocal.entities[ lastWinner ];
1630 if ( ent && ent->IsType( idPlayer::Type ) ) {
1631 currentTourneyPlayer[ 0 ] = lastWinner;
1634 FillTourneySlots( );
1635 // force selected players in/out of the game and update the ranks
1636 for ( i = 0 ; i < gameLocal.numClients ; i++ ) {
1637 if ( currentTourneyPlayer[ 0 ] == i || currentTourneyPlayer[ 1 ] == i ) {
1638 player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
1639 player->ServerSpectate( false );
1641 ent = gameLocal.entities[ i ];
1642 if ( ent && ent->IsType( idPlayer::Type ) ) {
1643 player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
1644 player->ServerSpectate( true );
1648 UpdateTourneyLine();
1653 idMultiplayerGame::ExecuteVote
1654 the votes are checked for validity/relevance before they are started
1655 we assume that they are still legit when reaching here
1658 void idMultiplayerGame::ExecuteVote( void ) {
1662 gameLocal.MapRestart();
1664 case VOTE_TIMELIMIT:
1665 si_timeLimit.SetInteger( atoi( voteValue ) );
1667 needRestart = gameLocal.NeedRestart();
1668 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
1669 if ( needRestart ) {
1670 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
1674 case VOTE_FRAGLIMIT:
1675 si_fragLimit.SetInteger( atoi( voteValue ) );
1677 needRestart = gameLocal.NeedRestart();
1678 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
1679 if ( needRestart ) {
1680 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
1685 si_gameType.SetString( voteValue );
1686 gameLocal.MapRestart();
1689 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "kick %s", voteValue.c_str() ) );
1692 si_map.SetString( voteValue );
1693 gameLocal.MapRestart();
1695 case VOTE_SPECTATORS:
1696 si_spectators.SetBool( !si_spectators.GetBool() );
1698 needRestart = gameLocal.NeedRestart();
1699 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "rescanSI" );
1700 if ( needRestart ) {
1701 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "nextMap" );
1706 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverNextMap\n" );
1713 idMultiplayerGame::CheckVote
1716 void idMultiplayerGame::CheckVote( void ) {
1719 if ( vote == VOTE_NONE ) {
1723 if ( voteExecTime ) {
1724 if ( gameLocal.time > voteExecTime ) {
1726 ClientUpdateVote( VOTE_RESET, 0, 0 );
1733 // count voting players
1735 for ( i = 0; i < gameLocal.numClients; i++ ) {
1736 idEntity *ent = gameLocal.entities[ i ];
1737 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
1740 if ( playerState[ i ].vote != PLAYER_VOTE_NONE ) {
1747 ClientUpdateVote( VOTE_ABORTED, yesVotes, noVotes );
1750 if ( yesVotes / numVoters > 0.5f ) {
1751 ClientUpdateVote( VOTE_PASSED, yesVotes, noVotes );
1752 voteExecTime = gameLocal.time + 2000;
1755 if ( gameLocal.time > voteTimeOut || noVotes / numVoters >= 0.5f ) {
1756 ClientUpdateVote( VOTE_FAILED, yesVotes, noVotes );
1764 idMultiplayerGame::Warmup
1767 bool idMultiplayerGame::Warmup() {
1768 return ( gameState == WARMUP );
1773 idMultiplayerGame::Run
1776 void idMultiplayerGame::Run() {
1779 int gameReviewPause;
1781 assert( gameLocal.isMultiplayer );
1782 assert( !gameLocal.isClient );
1786 if ( gameState == INACTIVE ) {
1787 lastGameType = gameLocal.gameType;
1795 if ( nextState != INACTIVE && gameLocal.time > nextStateSwitch ) {
1796 NewState( nextState );
1797 nextState = INACTIVE;
1800 // don't update the ping every frame to save bandwidth
1801 if ( gameLocal.time > pingUpdateTime ) {
1802 for ( i = 0; i < gameLocal.numClients; i++ ) {
1803 playerState[i].ping = networkSystem->ServerGetClientPing( i );
1805 pingUpdateTime = gameLocal.time + 1000;
1810 switch( gameState ) {
1812 if ( nextState == INACTIVE ) {
1813 gameReviewPause = cvarSystem->GetCVarInteger( "g_gameReviewPause" );
1814 nextState = NEXTGAME;
1815 nextStateSwitch = gameLocal.time + 1000 * gameReviewPause;
1820 if ( nextState == INACTIVE ) {
1821 // game rotation, new map, gametype etc.
1822 if ( gameLocal.NextMap() ) {
1823 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "serverMapRestart\n" );
1827 // make sure flags are returned
1828 if ( IsGametypeFlagBased() ) {
1830 flag = GetTeamFlag( 0 );
1834 flag = GetTeamFlag( 1 );
1841 if ( gameLocal.gameType == GAME_TOURNEY ) {
1842 CycleTourneyPlayers();
1844 // put everyone back in from endgame spectate
1845 for ( i = 0; i < gameLocal.numClients; i++ ) {
1846 idEntity *ent = gameLocal.entities[ i ];
1847 if ( ent && ent->IsType( idPlayer::Type ) ) {
1848 if ( !static_cast< idPlayer * >( ent )->wantSpectate ) {
1849 CheckRespawns( static_cast<idPlayer *>( ent ) );
1857 if ( AllPlayersReady() ) {
1858 NewState( COUNTDOWN );
1860 nextStateSwitch = gameLocal.time + 1000 * cvarSystem->GetCVarInteger( "g_countDown" );
1862 warmupText = "Warming up.. waiting for players to get ready";
1863 one = two = three = false;
1867 timeLeft = ( nextStateSwitch - gameLocal.time ) / 1000 + 1;
1868 if ( timeLeft == 3 && !three ) {
1869 PlayGlobalSound( -1, SND_THREE );
1871 } else if ( timeLeft == 2 && !two ) {
1872 PlayGlobalSound( -1, SND_TWO );
1874 } else if ( timeLeft == 1 && !one ) {
1875 PlayGlobalSound( -1, SND_ONE );
1878 warmupText = va( "Match starts in %i", timeLeft );
1883 if ( IsGametypeFlagBased() ) { /* CTF */
1884 // totally different logic branch for CTF
1885 if ( PointLimitHit() ) {
1886 int team = WinningTeam();
1887 assert( team != -1 );
1889 NewState( GAMEREVIEW, NULL );
1890 PrintMessageEvent( -1, MSG_POINTLIMIT, team );
1891 } else if ( TimeLimitHit() ) {
1892 int team = WinningTeam();
1893 if ( EnoughClientsToPlay() && team == -1 ) {
1894 NewState( SUDDENDEATH );
1896 NewState( GAMEREVIEW, NULL );
1897 PrintMessageEvent( -1, MSG_TIMELIMIT );
1904 player = FragLimitHit();
1906 // delay between detecting frag limit and ending game. let the death anims play
1907 if ( !fragLimitTimeout ) {
1908 common->DPrintf( "enter FragLimit timeout, player %d is leader\n", player->entityNumber );
1909 fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
1911 if ( gameLocal.time > fragLimitTimeout ) {
1912 NewState( GAMEREVIEW, player );
1913 PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber );
1916 if ( fragLimitTimeout ) {
1917 // frag limit was hit and cancelled. means the two teams got even during FRAGLIMIT_DELAY
1918 // enter sudden death, the next frag leader will win
1920 PrintMessageEvent( -1, MSG_HOLYSHIT );
1921 fragLimitTimeout = 0;
1922 NewState( SUDDENDEATH );
1923 } else if ( TimeLimitHit() ) {
1924 player = FragLeader();
1926 NewState( SUDDENDEATH );
1928 NewState( GAMEREVIEW, player );
1929 PrintMessageEvent( -1, MSG_TIMELIMIT );
1937 if ( IsGametypeFlagBased() ) { /* CTF */
1938 int team = WinningTeam();
1940 // TODO : implement pointLimitTimeout
1941 NewState( GAMEREVIEW, NULL );
1942 PrintMessageEvent( -1, MSG_POINTLIMIT, team );
1948 player = FragLeader();
1950 if ( !fragLimitTimeout ) {
1951 common->DPrintf( "enter sudden death FragLeader timeout, player %d is leader\n", player->entityNumber );
1952 fragLimitTimeout = gameLocal.time + FRAGLIMIT_DELAY;
1954 if ( gameLocal.time > fragLimitTimeout ) {
1955 NewState( GAMEREVIEW, player );
1956 PrintMessageEvent( -1, MSG_FRAGLIMIT, player->entityNumber );
1958 } else if ( fragLimitTimeout ) {
1960 PrintMessageEvent( -1, MSG_HOLYSHIT );
1961 fragLimitTimeout = 0;
1970 idMultiplayerGame::UpdateMainGui
1973 void idMultiplayerGame::UpdateMainGui( void ) {
1975 mainGui->SetStateInt( "readyon", gameState == WARMUP ? 1 : 0 );
1976 mainGui->SetStateInt( "readyoff", gameState != WARMUP ? 1 : 0 );
1977 idStr strReady = cvarSystem->GetCVarString( "ui_ready" );
1978 if ( strReady.Icmp( "ready") == 0 ){
1979 strReady = common->GetLanguageDict()->GetString( "#str_04248" );
1981 strReady = common->GetLanguageDict()->GetString( "#str_04247" );
1983 mainGui->SetStateString( "ui_ready", strReady );
1984 mainGui->SetStateInt( "teamon", IsGametypeTeamBased() ? 1 : 0 ); /* CTF */
1985 mainGui->SetStateInt( "teamoff", (!IsGametypeTeamBased()) ? 1 : 0 ); /* CTF */
1986 if ( IsGametypeTeamBased() ) {
1987 idPlayer *p = gameLocal.GetClientByNum( gameLocal.localClientNum );
1989 mainGui->SetStateInt( "team", p->team );
1992 mainGui->SetStateInt( "team", 0 );
1996 mainGui->SetStateInt( "voteon", ( vote != VOTE_NONE && !voted ) ? 1 : 0 );
1997 mainGui->SetStateInt( "voteoff", ( vote != VOTE_NONE && !voted ) ? 0 : 1 );
1999 mainGui->SetStateInt( "isLastMan", gameLocal.gameType == GAME_LASTMAN ? 1 : 0 );
2000 // send the current serverinfo values
2001 for ( i = 0; i < gameLocal.serverInfo.GetNumKeyVals(); i++ ) {
2002 const idKeyValue *keyval = gameLocal.serverInfo.GetKeyVal( i );
2003 mainGui->SetStateString( keyval->GetKey(), keyval->GetValue() );
2005 mainGui->StateChanged( gameLocal.time );
2006 #if defined( __linux__ )
2007 // replacing the oh-so-useful s_reverse with sound backend prompt
2008 mainGui->SetStateString( "driver_prompt", "1" );
2010 mainGui->SetStateString( "driver_prompt", "0" );
2016 idMultiplayerGame::StartMenu
2019 idUserInterface* idMultiplayerGame::StartMenu( void ) {
2021 if ( mainGui == NULL ) {
2026 if ( currentMenu ) {
2028 cvarSystem->SetCVarBool( "ui_chat", false );
2030 if ( nextMenu >= 2 ) {
2031 currentMenu = nextMenu;
2033 // for default and explicit
2036 cvarSystem->SetCVarBool( "ui_chat", true );
2039 gameLocal.sessionCommand = ""; // in case we used "game_startMenu" to trigger the menu
2040 if ( currentMenu == 1 ) {
2043 // UpdateMainGui sets most things, but it doesn't set these because
2044 // it'd be pointless and/or harmful to set them every frame (for various reasons)
2045 // Currenty the gui doesn't update properly if they change anyway, so we'll leave it like this.
2048 if ( vote == VOTE_NONE ) {
2049 bool callvote_ok = false;
2050 for ( i = 0; i < VOTE_COUNT; i++ ) {
2051 // flag on means vote is denied, so default value 0 means all votes and -1 disables
2052 mainGui->SetStateInt( va( "vote%d", i ), g_voteFlags.GetInteger() & ( 1 << i ) ? 0 : 1 );
2053 if ( !( g_voteFlags.GetInteger() & ( 1 << i ) ) ) {
2057 mainGui->SetStateInt( "callvote", callvote_ok );
2059 mainGui->SetStateInt( "callvote", 2 );
2065 for ( i = 0; i < gameLocal.numClients; i++ ) {
2066 if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
2067 if ( kickList.Length() ) {
2070 kickList += va( "\"%d - %s\"", i, gameLocal.userInfo[ i ].GetString( "ui_name" ) );
2071 kickVoteMap[ j ] = i;
2075 mainGui->SetStateString( "kickChoices", kickList );
2078 const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" );
2079 const char *map = gameLocal.serverInfo.GetString( "si_map" ); // what if server changes this strings while user in UI?
2080 int num = declManager->GetNumDecls( DECL_MAPDEF );
2082 for ( i = 0; i < num; i++ ) {
2083 const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_MAPDEF, i ) );
2085 if ( mapDef && idStr::Icmp( mapDef->GetName(), map ) == 0 && mapDef->dict.GetBool( gametype ) ) {
2090 for ( j = 0; si_gameTypeArgs[ j ]; j++ ) {
2091 if ( mapDef->dict.GetBool( si_gameTypeArgs[ j ] ) ) {
2092 if ( gametypeList.Length() ) {
2093 gametypeList += ";";
2095 gametypeList += va( "%s", si_gameTypeArgs[ j ] );
2096 gameTypeVoteMap[ k ] = si_gameTypeArgs[ j ];
2101 mainGui->SetStateString( "gametypeChoices", gametypeList );
2108 mainGui->SetStateString( "chattext", "" );
2109 mainGui->Activate( true, gameLocal.time );
2111 } else if ( currentMenu == 2 ) {
2112 // the setup is done in MessageMode
2113 msgmodeGui->Activate( true, gameLocal.time );
2114 cvarSystem->SetCVarBool( "ui_chat", true );
2122 idMultiplayerGame::DisableMenu
2125 void idMultiplayerGame::DisableMenu( void ) {
2126 gameLocal.sessionCommand = ""; // in case we used "game_startMenu" to trigger the menu
2127 if ( currentMenu == 1 ) {
2128 mainGui->Activate( false, gameLocal.time );
2129 } else if ( currentMenu == 2 ) {
2130 msgmodeGui->Activate( false, gameLocal.time );
2134 cvarSystem->SetCVarBool( "ui_chat", false );
2139 idMultiplayerGame::SetMapShot
2142 void idMultiplayerGame::SetMapShot( void ) {
2143 char screenshot[ MAX_STRING_CHARS ];
2144 int mapNum = mapList->GetSelection( NULL, 0 );
2145 const idDict *dict = NULL;
2146 if ( mapNum >= 0 ) {
2147 dict = fileSystem->GetMapDecl( mapNum );
2149 fileSystem->FindMapScreenshot( dict ? dict->GetString( "path" ) : "", screenshot, MAX_STRING_CHARS );
2150 mainGui->SetStateString( "current_levelshot", screenshot );
2155 idMultiplayerGame::HandleGuiCommands
2158 const char* idMultiplayerGame::HandleGuiCommands( const char *_menuCommand ) {
2159 idUserInterface *currentGui;
2160 const char *voteValue;
2165 if ( !_menuCommand[ 0 ] ) {
2166 common->Printf( "idMultiplayerGame::HandleGuiCommands: empty command\n" );
2169 assert( currentMenu );
2170 if ( currentMenu == 1 ) {
2171 currentGui = mainGui;
2173 currentGui = msgmodeGui;
2176 args.TokenizeString( _menuCommand, false );
2178 for( icmd = 0; icmd < args.Argc(); ) {
2179 const char *cmd = args.Argv( icmd++ );
2181 if ( !idStr::Icmp( cmd, ";" ) ) {
2183 } else if ( !idStr::Icmp( cmd, "video" ) ) {
2185 if ( args.Argc() - icmd >= 1 ) {
2186 vcmd = args.Argv( icmd++ );
2189 int oldSpec = cvarSystem->GetCVarInteger( "com_machineSpec" );
2191 if ( idStr::Icmp( vcmd, "low" ) == 0 ) {
2192 cvarSystem->SetCVarInteger( "com_machineSpec", 0 );
2193 } else if ( idStr::Icmp( vcmd, "medium" ) == 0 ) {
2194 cvarSystem->SetCVarInteger( "com_machineSpec", 1 );
2195 } else if ( idStr::Icmp( vcmd, "high" ) == 0 ) {
2196 cvarSystem->SetCVarInteger( "com_machineSpec", 2 );
2197 } else if ( idStr::Icmp( vcmd, "ultra" ) == 0 ) {
2198 cvarSystem->SetCVarInteger( "com_machineSpec", 3 );
2199 } else if ( idStr::Icmp( vcmd, "recommended" ) == 0 ) {
2200 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "setMachineSpec\n" );
2203 if ( oldSpec != cvarSystem->GetCVarInteger( "com_machineSpec" ) ) {
2204 currentGui->SetStateInt( "com_machineSpec", cvarSystem->GetCVarInteger( "com_machineSpec" ) );
2205 currentGui->StateChanged( gameLocal.realClientTime );
2206 cmdSystem->BufferCommandText( CMD_EXEC_NOW, "execMachineSpec\n" );
2209 if ( idStr::Icmp( vcmd, "restart" ) == 0) {
2210 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" );
2214 } else if ( !idStr::Icmp( cmd, "play" ) ) {
2215 if ( args.Argc() - icmd >= 1 ) {
2216 idStr snd = args.Argv( icmd++ );
2218 if ( snd.Length() == 1 ) {
2219 channel = atoi( snd );
2220 snd = args.Argv( icmd++ );
2222 gameSoundWorld->PlayShaderDirectly( snd, channel );
2225 } else if ( !idStr::Icmp( cmd, "mpSkin" ) ) {
2227 if ( args.Argc() - icmd >= 1 ) {
2228 skin = args.Argv( icmd++ );
2229 cvarSystem->SetCVarString( "ui_skin", skin );
2233 } else if ( !idStr::Icmp( cmd, "quit" ) ) {
2234 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
2236 } else if ( !idStr::Icmp( cmd, "disconnect" ) ) {
2237 cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "disconnect\n" );
2239 } else if ( !idStr::Icmp( cmd, "close" ) ) {
2242 } else if ( !idStr::Icmp( cmd, "spectate" ) ) {
2246 } else if ( !idStr::Icmp( cmd, "chatmessage" ) ) {
2247 int mode = currentGui->State().GetInt( "messagemode" );
2249 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "sayTeam \"%s\"", currentGui->State().GetString( "chattext" ) ) );
2251 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say \"%s\"", currentGui->State().GetString( "chattext" ) ) );
2253 currentGui->SetStateString( "chattext", "" );
2254 if ( currentMenu == 1 ) {
2260 } else if ( !idStr::Icmp( cmd, "readytoggle" ) ) {
2264 } else if ( !idStr::Icmp( cmd, "teamtoggle" ) ) {
2268 } else if ( !idStr::Icmp( cmd, "callVote" ) ) {
2269 vote_flags_t voteIndex = (vote_flags_t)mainGui->State().GetInt( "voteIndex" );
2270 if ( voteIndex == VOTE_MAP ) {
2271 int mapNum = mapList->GetSelection( NULL, 0 );
2272 if ( mapNum >= 0 ) {
2273 const idDict *dict = fileSystem->GetMapDecl( mapNum );
2275 ClientCallVote( VOTE_MAP, dict->GetString( "path" ) );
2279 voteValue = mainGui->State().GetString( "str_voteValue" );
2280 if ( voteIndex == VOTE_KICK ) {
2281 vote_clientNum = kickVoteMap[ atoi( voteValue ) ];
2282 ClientCallVote( voteIndex, va( "%d", vote_clientNum ) );
2284 } else if ( voteIndex == VOTE_GAMETYPE ) {
2285 // send the actual gametype index, not an index in the choice list
2287 for ( i = 0; si_gameTypeArgs[i]; i++ ) {
2288 if ( !idStr::Icmp( gameTypeVoteMap[ atoi( voteValue ) ], si_gameTypeArgs[i] ) ) {
2289 ClientCallVote( voteIndex, va( "%d", i ) );
2295 ClientCallVote( voteIndex, voteValue );
2300 } else if ( !idStr::Icmp( cmd, "voteyes" ) ) {
2301 CastVote( gameLocal.localClientNum, true );
2304 } else if ( !idStr::Icmp( cmd, "voteno" ) ) {
2305 CastVote( gameLocal.localClientNum, false );
2308 } else if ( !idStr::Icmp( cmd, "bind" ) ) {
2309 if ( args.Argc() - icmd >= 2 ) {
2310 idStr key = args.Argv( icmd++ );
2311 idStr bind = args.Argv( icmd++ );
2312 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "bindunbindtwo \"%s\" \"%s\"", key.c_str(), bind.c_str() ) );
2313 mainGui->SetKeyBindingNames();
2316 } else if ( !idStr::Icmp( cmd, "clearbind" ) ) {
2317 if ( args.Argc() - icmd >= 1 ) {
2318 idStr bind = args.Argv( icmd++ );
2319 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "unbind \"%s\"", bind.c_str() ) );
2320 mainGui->SetKeyBindingNames();
2323 } else if ( !idStr::Icmp( cmd, "MAPScan" ) ) {
2324 const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" );
2325 if ( gametype == NULL || *gametype == 0 || idStr::Icmp( gametype, "singleplayer" ) == 0 ) {
2326 gametype = "Deathmatch";
2330 idStr si_map = gameLocal.serverInfo.GetString("si_map");
2334 mapList->SetSelection( -1 );
2335 num = fileSystem->GetNumMaps();
2336 for ( i = 0; i < num; i++ ) {
2337 dict = fileSystem->GetMapDecl( i );
2339 // any MP gametype supported
2341 int igt = GAME_SP + 1;
2342 while ( si_gameTypeArgs[ igt ] ) {
2343 if ( dict->GetBool( si_gameTypeArgs[ igt ] ) ) {
2350 const char *mapName = dict->GetString( "name" );
2351 if ( mapName[0] == '\0' ) {
2352 mapName = dict->GetString( "path" );
2354 mapName = common->GetLanguageDict()->GetString( mapName );
2355 mapList->Add( i, mapName );
2356 if ( !si_map.Icmp( dict->GetString( "path" ) ) ) {
2357 mapList->SetSelection( mapList->Num() - 1 );
2362 // set the current level shot
2365 } else if ( !idStr::Icmp( cmd, "click_maplist" ) ) {
2368 } else if ( strstr( cmd, "sound" ) == cmd ) {
2369 // pass that back to the core, will know what to do with it
2370 return _menuCommand;
2372 common->Printf( "idMultiplayerGame::HandleGuiCommands: '%s' unknown\n", cmd );
2380 idMultiplayerGame::Draw
2383 bool idMultiplayerGame::Draw( int clientNum ) {
2384 idPlayer *player, *viewPlayer;
2386 // clear the render entities for any players that don't need
2387 // icons and which might not be thinking because they weren't in
2388 // the last snapshot.
2389 for ( int i = 0; i < gameLocal.numClients; i++ ) {
2390 player = static_cast<idPlayer *>( gameLocal.entities[ i ] );
2391 if ( player && !player->NeedsIcon() ) {
2392 player->HidePlayerIcons();
2396 player = viewPlayer = static_cast<idPlayer *>( gameLocal.entities[ clientNum ] );
2398 if ( player == NULL ) {
2402 if ( player->spectating ) {
2403 viewPlayer = static_cast<idPlayer *>( gameLocal.entities[ player->spectator ] );
2404 if ( viewPlayer == NULL ) {
2409 UpdatePlayerRanks();
2410 UpdateHud( viewPlayer, player->hud );
2411 // use the hud of the local player
2412 viewPlayer->playerView.RenderPlayerView( player->hud );
2414 if ( currentMenu ) {
2416 // uncomment this if you want to track when players are in a menu
2417 if ( !bCurrentMenuMsg ) {
2421 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2422 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU );
2423 outMsg.WriteBits( 1, 1 );
2424 networkSystem->ClientSendReliableMessage( outMsg );
2426 bCurrentMenuMsg = true;
2429 if ( player->wantSpectate ) {
2430 mainGui->SetStateString( "spectext", common->GetLanguageDict()->GetString( "#str_04249" ) );
2432 mainGui->SetStateString( "spectext", common->GetLanguageDict()->GetString( "#str_04250" ) );
2435 if ( currentMenu == 1 ) {
2437 mainGui->Redraw( gameLocal.time );
2439 msgmodeGui->Redraw( gameLocal.time );
2443 // uncomment this if you want to track when players are in a menu
2444 if ( bCurrentMenuMsg ) {
2448 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2449 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_MENU );
2450 outMsg.WriteBits( 0, 1 );
2451 networkSystem->ClientSendReliableMessage( outMsg );
2453 bCurrentMenuMsg = false;
2456 if ( player->spectating ) {
2457 idStr spectatetext[ 2 ];
2459 if ( gameLocal.gameType == GAME_TOURNEY ) {
2460 if ( !player->wantSpectate ) {
2461 spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_04246" );
2462 switch ( player->tourneyLine ) {
2464 spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07003" );
2467 spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07004" );
2470 spectatetext[ 0 ] += common->GetLanguageDict()->GetString( "#str_07005" );
2473 spectatetext[ 0 ] += va( common->GetLanguageDict()->GetString( "#str_07006" ), player->tourneyLine );
2478 } else if ( gameLocal.gameType == GAME_LASTMAN ) {
2479 if ( !player->wantSpectate ) {
2480 spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_07007" );
2484 if ( player->spectator != player->entityNumber ) {
2485 spectatetext[ ispecline ] = va( common->GetLanguageDict()->GetString( "#str_07008" ), viewPlayer->GetUserInfo()->GetString( "ui_name" ) );
2486 } else if ( !ispecline ) {
2487 spectatetext[ 0 ] = common->GetLanguageDict()->GetString( "#str_04246" );
2489 spectateGui->SetStateString( "spectatetext0", spectatetext[0].c_str() );
2490 spectateGui->SetStateString( "spectatetext1", spectatetext[1].c_str() );
2491 if ( vote != VOTE_NONE ) {
2492 spectateGui->SetStateString( "vote", va( "%s (y: %d n: %d)", voteString.c_str(), (int)yesVotes, (int)noVotes ) );
2494 spectateGui->SetStateString( "vote", "" );
2496 spectateGui->Redraw( gameLocal.time );
2499 DrawScoreBoard( player );
2507 idMultiplayerGame::UpdateHud
2510 void idMultiplayerGame::UpdateHud( idPlayer *player, idUserInterface *hud ) {
2517 hud->SetStateBool( "warmup", Warmup() );
2519 if ( gameState == WARMUP ) {
2520 if ( player->IsReady() ) {
2521 hud->SetStateString( "warmuptext", common->GetLanguageDict()->GetString( "#str_04251" ) );
2523 hud->SetStateString( "warmuptext", common->GetLanguageDict()->GetString( "#str_07002" ) );
2527 hud->SetStateString( "timer", ( Warmup() ) ? common->GetLanguageDict()->GetString( "#str_04251" ) : ( gameState == SUDDENDEATH ) ? common->GetLanguageDict()->GetString( "#str_04252" ) : GameTime() );
2528 if ( vote != VOTE_NONE ) {
2529 hud->SetStateString( "vote", va( "%s (y: %d n: %d)", voteString.c_str(), (int)yesVotes, (int)noVotes ) );
2531 hud->SetStateString( "vote", "" );
2534 hud->SetStateInt( "rank_self", 0 );
2535 if ( gameState == GAMEON ) {
2536 for ( i = 0; i < numRankedPlayers; i++ ) {
2537 if ( IsGametypeTeamBased() ) { /* CTF */
2538 hud->SetStateInt( va( "player%i_score", i+1 ), playerState[ rankedPlayers[ i ]->entityNumber ].teamFragCount );
2540 hud->SetStateInt( va( "player%i_score", i+1 ), playerState[ rankedPlayers[ i ]->entityNumber ].fragCount );
2542 hud->SetStateInt( va( "rank%i", i+1 ), 1 );
2543 UpdateRankColor( hud, "rank%i_color%i", i+1, rankedPlayers[ i ]->colorBar );
2544 if ( rankedPlayers[ i ] == player ) {
2545 hud->SetStateInt( "rank_self", i+1 );
2550 for ( i = ( gameState == GAMEON ? numRankedPlayers : 0 ) ; i < MAX_CLIENTS; i++ ) {
2552 for ( i = ( gameState == GAMEON ? numRankedPlayers : 0 ) ; i < 5; i++ ) {
2554 hud->SetStateString( va( "player%i", i+1 ), "" );
2555 hud->SetStateString( va( "player%i_score", i+1 ), "" );
2556 hud->SetStateInt( va( "rank%i", i+1 ), 0 );
2560 if ( IsGametypeFlagBased() )
2561 hud->SetStateInt( "self_team", player->team );
2563 hud->SetStateInt( "self_team", -1 ); /* Disable */
2570 idMultiplayerGame::DrawScoreBoard
2573 void idMultiplayerGame::DrawScoreBoard( idPlayer *player ) {
2574 if ( player->scoreBoardOpen || gameState == GAMEREVIEW ) {
2575 if ( !playerState[ player->entityNumber ].scoreBoardUp ) {
2576 scoreBoard->Activate( true, gameLocal.time );
2577 playerState[ player->entityNumber ].scoreBoardUp = true;
2581 if ( IsGametypeFlagBased() )
2582 UpdateCTFScoreboard( scoreBoard, player );
2585 UpdateScoreboard( scoreBoard, player );
2588 if ( playerState[ player->entityNumber ].scoreBoardUp ) {
2589 scoreBoard->Activate( false, gameLocal.time );
2590 playerState[ player->entityNumber ].scoreBoardUp = false;
2597 idMultiplayerGame::ClearChatData
2600 void idMultiplayerGame::ClearChatData() {
2601 chatHistoryIndex = 0;
2602 chatHistorySize = 0;
2603 chatDataUpdated = true;
2608 idMultiplayerGame::AddChatLine
2611 void idMultiplayerGame::AddChatLine( const char *fmt, ... ) {
2615 va_start( argptr, fmt );
2616 vsprintf( temp, fmt, argptr );
2619 gameLocal.Printf( "%s\n", temp.c_str() );
2621 chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].line = temp;
2622 chatHistory[ chatHistoryIndex % NUM_CHAT_NOTIFY ].fade = 6;
2625 if ( chatHistorySize < NUM_CHAT_NOTIFY ) {
2628 chatDataUpdated = true;
2629 lastChatLineTime = gameLocal.time;
2634 idMultiplayerGame::DrawChat
2637 void idMultiplayerGame::DrawChat() {
2640 if ( gameLocal.time - lastChatLineTime > CHAT_FADE_TIME ) {
2641 if ( chatHistorySize > 0 ) {
2642 for ( i = chatHistoryIndex - chatHistorySize; i < chatHistoryIndex; i++ ) {
2643 chatHistory[ i % NUM_CHAT_NOTIFY ].fade--;
2644 if ( chatHistory[ i % NUM_CHAT_NOTIFY ].fade < 0 ) {
2645 chatHistorySize--; // this assumes the removals are always at the beginning
2648 chatDataUpdated = true;
2650 lastChatLineTime = gameLocal.time;
2652 if ( chatDataUpdated ) {
2654 i = chatHistoryIndex - chatHistorySize;
2655 while ( i < chatHistoryIndex ) {
2656 guiChat->SetStateString( va( "chat%i", j ), chatHistory[ i % NUM_CHAT_NOTIFY ].line );
2657 // don't set alpha above 4, the gui only knows that
2658 guiChat->SetStateInt( va( "alpha%i", j ), Min( 4, (int)chatHistory[ i % NUM_CHAT_NOTIFY ].fade ) );
2661 while ( j < NUM_CHAT_NOTIFY ) {
2662 guiChat->SetStateString( va( "chat%i", j ), "" );
2665 guiChat->Activate( true, gameLocal.time );
2666 chatDataUpdated = false;
2668 guiChat->Redraw( gameLocal.time );
2673 //D3XP: Adding one to frag count to allow for the negative flag in numbers greater than 255
2674 const int ASYNC_PLAYER_FRAG_BITS = -(idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS )+1); // player can have negative frags
2676 const int ASYNC_PLAYER_FRAG_BITS = -idMath::BitsForInteger( MP_PLAYER_MAXFRAGS - MP_PLAYER_MINFRAGS ); // player can have negative frags
2678 const int ASYNC_PLAYER_WINS_BITS = idMath::BitsForInteger( MP_PLAYER_MAXWINS );
2679 const int ASYNC_PLAYER_PING_BITS = idMath::BitsForInteger( MP_PLAYER_MAXPING );
2683 idMultiplayerGame::WriteToSnapshot
2686 void idMultiplayerGame::WriteToSnapshot( idBitMsgDelta &msg ) const {
2690 msg.WriteByte( gameState );
2691 msg.WriteShort( currentTourneyPlayer[ 0 ] );
2692 msg.WriteShort( currentTourneyPlayer[ 1 ] );
2693 for ( i = 0; i < MAX_CLIENTS; i++ ) {
2694 // clamp all values to min/max possible value that we can send over
2695 value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].fragCount );
2696 msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS );
2697 value = idMath::ClampInt( MP_PLAYER_MINFRAGS, MP_PLAYER_MAXFRAGS, playerState[i].teamFragCount );
2698 msg.WriteBits( value, ASYNC_PLAYER_FRAG_BITS );
2699 value = idMath::ClampInt( 0, MP_PLAYER_MAXWINS, playerState[i].wins );
2700 msg.WriteBits( value, ASYNC_PLAYER_WINS_BITS );
2701 value = idMath::ClampInt( 0, MP_PLAYER_MAXPING, playerState[i].ping );
2702 msg.WriteBits( value, ASYNC_PLAYER_PING_BITS );
2703 msg.WriteBits( playerState[i].ingame, 1 );
2707 msg.WriteShort( teamPoints[0] );
2708 msg.WriteShort( teamPoints[1] );
2709 msg.WriteShort( player_red_flag );
2710 msg.WriteShort( player_blue_flag );
2716 idMultiplayerGame::ReadFromSnapshot
2719 void idMultiplayerGame::ReadFromSnapshot( const idBitMsgDelta &msg ) {
2721 gameState_t newState;
2723 newState = (idMultiplayerGame::gameState_t)msg.ReadByte();
2724 if ( newState != gameState ) {
2725 gameLocal.DPrintf( "%s -> %s\n", GameStateStrings[ gameState ], GameStateStrings[ newState ] );
2726 gameState = newState;
2727 // these could be gathered in a BGNewState() kind of thing, as we have to do them in NewState as well
2728 if ( gameState == GAMEON ) {
2729 matchStartedTime = gameLocal.time;
2730 cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
2731 switchThrottle[ 1 ] = 0; // passby the throttle
2732 startFragLimit = gameLocal.serverInfo.GetInt( "si_fragLimit" );
2735 currentTourneyPlayer[ 0 ] = msg.ReadShort();
2736 currentTourneyPlayer[ 1 ] = msg.ReadShort();
2737 for ( i = 0; i < MAX_CLIENTS; i++ ) {
2738 playerState[i].fragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS );
2739 playerState[i].teamFragCount = msg.ReadBits( ASYNC_PLAYER_FRAG_BITS );
2740 playerState[i].wins = msg.ReadBits( ASYNC_PLAYER_WINS_BITS );
2741 playerState[i].ping = msg.ReadBits( ASYNC_PLAYER_PING_BITS );
2742 playerState[i].ingame = msg.ReadBits( 1 ) != 0;
2746 teamPoints[0] = msg.ReadShort();
2747 teamPoints[1] = msg.ReadShort();
2749 player_red_flag = msg.ReadShort();
2750 player_blue_flag = msg.ReadShort();
2758 idMultiplayerGame::PlayGlobalSound
2761 void idMultiplayerGame::PlayGlobalSound( int to, snd_evt_t evt, const char *shader ) {
2762 const idSoundShader *shaderDecl;
2764 if ( to == -1 || to == gameLocal.localClientNum ) {
2766 if ( gameSoundWorld ) {
2767 gameSoundWorld->PlayShaderDirectly( shader );
2770 if ( gameSoundWorld ) {
2771 gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ evt ] );
2776 if ( !gameLocal.isClient ) {
2779 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2782 shaderDecl = declManager->FindSound( shader );
2783 if ( !shaderDecl ) {
2786 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SOUND_INDEX );
2787 outMsg.WriteLong( gameLocal.ServerRemapDecl( to, DECL_SOUND, shaderDecl->Index() ) );
2789 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SOUND_EVENT );
2790 outMsg.WriteByte( evt );
2793 networkSystem->ServerSendReliableMessage( to, outMsg );
2800 idMultiplayerGame::PlayTeamSound
2803 void idMultiplayerGame::PlayTeamSound( int toTeam, snd_evt_t evt, const char *shader ) {
2804 for( int i = 0; i < gameLocal.numClients; i++ ) {
2805 idEntity *ent = gameLocal.entities[ i ];
2806 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
2809 idPlayer * player = static_cast<idPlayer*>(ent);
2810 if ( player->team != toTeam )
2812 PlayGlobalSound( i, evt, shader );
2819 idMultiplayerGame::PrintMessageEvent
2822 void idMultiplayerGame::PrintMessageEvent( int to, msg_evt_t evt, int parm1, int parm2 ) {
2825 assert( parm1 >= 0 );
2826 AddChatLine( common->GetLanguageDict()->GetString( "#str_04293" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2829 assert( parm1 >= 0 && parm2 >= 0 );
2830 AddChatLine( common->GetLanguageDict()->GetString( "#str_04292" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
2832 case MSG_KILLEDTEAM:
2833 assert( parm1 >= 0 && parm2 >= 0 );
2834 AddChatLine( common->GetLanguageDict()->GetString( "#str_04291" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
2836 case MSG_TELEFRAGGED:
2837 assert( parm1 >= 0 && parm2 >= 0 );
2838 AddChatLine( common->GetLanguageDict()->GetString( "#str_04290" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) );
2841 assert( parm1 >= 0 );
2842 AddChatLine( common->GetLanguageDict()->GetString( "#str_04289" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2845 AddChatLine( common->GetLanguageDict()->GetString( "#str_04288" ) );
2847 case MSG_SUDDENDEATH:
2848 AddChatLine( common->GetLanguageDict()->GetString( "#str_04287" ) );
2850 case MSG_FORCEREADY:
2851 AddChatLine( common->GetLanguageDict()->GetString( "#str_04286" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2852 if ( gameLocal.entities[ parm1 ] && gameLocal.entities[ parm1 ]->IsType( idPlayer::Type ) ) {
2853 static_cast< idPlayer * >( gameLocal.entities[ parm1 ] )->forcedReady = true;
2856 case MSG_JOINEDSPEC:
2857 AddChatLine( common->GetLanguageDict()->GetString( "#str_04285" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2860 AddChatLine( common->GetLanguageDict()->GetString( "#str_04284" ) );
2863 if ( gameLocal.gameType == GAME_LASTMAN ) {
2864 AddChatLine( common->GetLanguageDict()->GetString( "#str_04283" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2865 } else if ( IsGametypeTeamBased() ) { /* CTF */
2866 AddChatLine( common->GetLanguageDict()->GetString( "#str_04282" ), gameLocal.userInfo[ parm1 ].GetString( "ui_team" ) );
2868 AddChatLine( common->GetLanguageDict()->GetString( "#str_04281" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ) );
2872 AddChatLine( common->GetLanguageDict()->GetString( "#str_04280" ), gameLocal.userInfo[ parm1 ].GetString( "ui_name" ), parm2 ? common->GetLanguageDict()->GetString( "#str_02500" ) : common->GetLanguageDict()->GetString( "#str_02499" ) );
2875 AddChatLine( common->GetLanguageDict()->GetString( "#str_06732" ) );
2878 case MSG_POINTLIMIT:
2879 AddChatLine( common->GetLanguageDict()->GetString( "#str_11100" ), parm1 ? common->GetLanguageDict()->GetString( "#str_11110" ) : common->GetLanguageDict()->GetString( "#str_11111" ) );
2882 case MSG_FLAGTAKEN :
2883 if ( gameLocal.GetLocalPlayer() == NULL )
2886 if ( parm2 < 0 || parm2 >= MAX_CLIENTS )
2889 if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2890 AddChatLine( common->GetLanguageDict()->GetString( "#str_11101" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); // your team
2892 AddChatLine( common->GetLanguageDict()->GetString( "#str_11102" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); // enemy
2897 if ( gameLocal.GetLocalPlayer() == NULL )
2900 if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2901 AddChatLine( common->GetLanguageDict()->GetString( "#str_11103" ) ); // your team
2903 AddChatLine( common->GetLanguageDict()->GetString( "#str_11104" ) ); // enemy
2907 case MSG_FLAGRETURN :
2908 if ( gameLocal.GetLocalPlayer() == NULL )
2911 if ( parm2 >= 0 && parm2 < MAX_CLIENTS ) {
2912 if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2913 AddChatLine( common->GetLanguageDict()->GetString( "#str_11120" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); // your team
2915 AddChatLine( common->GetLanguageDict()->GetString( "#str_11121" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); // enemy
2918 AddChatLine( common->GetLanguageDict()->GetString( "#str_11105" ), parm1 ? common->GetLanguageDict()->GetString( "#str_11110" ) : common->GetLanguageDict()->GetString( "#str_11111" ) );
2922 case MSG_FLAGCAPTURE :
2923 if ( gameLocal.GetLocalPlayer() == NULL )
2926 if ( parm2 < 0 || parm2 >= MAX_CLIENTS )
2929 if ( gameLocal.GetLocalPlayer()->team != parm1 ) {
2930 AddChatLine( common->GetLanguageDict()->GetString( "#str_11122" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); // your team
2932 AddChatLine( common->GetLanguageDict()->GetString( "#str_11123" ), gameLocal.userInfo[ parm2 ].GetString( "ui_name" ) ); // enemy
2935 // AddChatLine( common->GetLanguageDict()->GetString( "#str_11106" ), parm1 ? common->GetLanguageDict()->GetString( "#str_11110" ) : common->GetLanguageDict()->GetString( "#str_11111" ) );
2938 case MSG_SCOREUPDATE:
2939 AddChatLine( common->GetLanguageDict()->GetString( "#str_11107" ), parm1, parm2 );
2943 gameLocal.DPrintf( "PrintMessageEvent: unknown message type %d\n", evt );
2946 if ( !gameLocal.isClient ) {
2949 outMsg.Init( msgBuf, sizeof( msgBuf ) );
2950 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DB );
2951 outMsg.WriteByte( evt );
2952 outMsg.WriteByte( parm1 );
2953 outMsg.WriteByte( parm2 );
2954 networkSystem->ServerSendReliableMessage( to, outMsg );
2960 idMultiplayerGame::SuddenRespawns
2961 solely for LMN if an end game ( fragLimitTimeout ) was entered and aborted before expiration
2962 LMN players which still have lives left need to be respawned without being marked lastManOver
2965 void idMultiplayerGame::SuddenRespawn( void ) {
2968 if ( gameLocal.gameType != GAME_LASTMAN ) {
2972 for ( i = 0; i < gameLocal.numClients; i++ ) {
2973 if ( !gameLocal.entities[ i ] || !gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
2976 if ( !CanPlay( static_cast< idPlayer * >( gameLocal.entities[ i ] ) ) ) {
2979 if ( static_cast< idPlayer * >( gameLocal.entities[ i ] )->lastManOver ) {
2982 static_cast< idPlayer * >( gameLocal.entities[ i ] )->lastManPlayAgain = true;
2988 idMultiplayerGame::CheckSpawns
2991 void idMultiplayerGame::CheckRespawns( idPlayer *spectator ) {
2992 for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
2993 idEntity *ent = gameLocal.entities[ i ];
2994 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
2997 idPlayer *p = static_cast<idPlayer *>(ent);
2998 // once we hit sudden death, nobody respawns till game has ended
2999 if ( WantRespawn( p ) || p == spectator ) {
3000 if ( gameState == SUDDENDEATH && gameLocal.gameType != GAME_LASTMAN ) {
3001 // respawn rules while sudden death are different
3002 // sudden death may trigger while a player is dead, so there are still cases where we need to respawn
3003 // don't do any respawns while we are in end game delay though
3004 if ( !fragLimitTimeout ) {
3005 if ( IsGametypeTeamBased() || p->IsLeader() ) { /* CTF */
3007 if ( gameLocal.gameType == GAME_TOURNEY ) {
3008 assert( p->entityNumber == currentTourneyPlayer[ 0 ] || p->entityNumber == currentTourneyPlayer[ 1 ] );
3011 p->ServerSpectate( false );
3012 } else if ( !p->IsLeader() ) {
3013 // sudden death is rolling, this player is not a leader, have him spectate
3014 p->ServerSpectate( true );
3019 if ( gameLocal.gameType == GAME_DM || // CTF : 3wave sboily, was DM really included before?
3020 IsGametypeTeamBased() )
3022 if ( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON ) {
3023 p->ServerSpectate( false );
3025 } else if ( gameLocal.gameType == GAME_TOURNEY ) {
3026 if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
3027 if ( gameState == WARMUP || gameState == COUNTDOWN || gameState == GAMEON ) {
3028 p->ServerSpectate( false );
3030 } else if ( gameState == WARMUP ) {
3031 // make sure empty tourney slots get filled first
3032 FillTourneySlots( );
3033 if ( i == currentTourneyPlayer[ 0 ] || i == currentTourneyPlayer[ 1 ] ) {
3034 p->ServerSpectate( false );
3037 } else if ( gameLocal.gameType == GAME_LASTMAN ) {
3038 if ( gameState == WARMUP || gameState == COUNTDOWN ) {
3039 p->ServerSpectate( false );
3040 } else if ( gameState == GAMEON || gameState == SUDDENDEATH ) {
3041 if ( gameState == GAMEON && playerState[ i ].fragCount > 0 && p->lastManPresent ) {
3042 assert( !p->lastManOver );
3043 p->ServerSpectate( false );
3044 } else if ( p->lastManPlayAgain && p->lastManPresent ) {
3045 assert( gameState == SUDDENDEATH );
3046 p->ServerSpectate( false );
3048 // if a fragLimitTimeout was engaged, do NOT mark lastManOver as that could mean
3049 // everyone ends up spectator and game is stalled with no end
3050 // if the frag limit delay is engaged and cancels out before expiring, LMN players are
3051 // respawned to play the tie again ( through SuddenRespawn and lastManPlayAgain )
3052 if ( !fragLimitTimeout && !p->lastManOver ) {
3053 common->DPrintf( "client %d has lost all last man lives\n", i );
3054 // end of the game for this guy, send him to spectators
3055 p->lastManOver = true;
3056 // clients don't have access to lastManOver
3057 // so set the fragCount to something silly ( used in scoreboard and player ranking )
3058 playerState[ i ].fragCount = LASTMAN_NOLIVES;
3059 p->ServerSpectate( true );
3061 //Check for a situation where the last two player dies at the same time and don't
3062 //try to respawn manually...This was causing all players to go into spectate mode
3063 //and the server got stuck
3066 for ( j = 0; j < gameLocal.numClients; j++ ) {
3067 if ( !gameLocal.entities[ j ] ) {
3070 if ( !CanPlay( static_cast< idPlayer * >( gameLocal.entities[ j ] ) ) ) {
3073 if ( !static_cast< idPlayer * >( gameLocal.entities[ j ] )->lastManOver ) {
3077 if( j == gameLocal.numClients) {
3078 //Everyone is dead so don't allow this player to spectate
3079 //so the match will end
3080 p->ServerSpectate( false );
3088 } else if ( p->wantSpectate && !p->spectating ) {
3089 playerState[ i ].fragCount = 0; // whenever you willingly go spectate during game, your score resets
3090 p->ServerSpectate( true );
3091 UpdateTourneyLine();
3099 idMultiplayerGame::ForceReady
3102 void idMultiplayerGame::ForceReady( ) {
3104 for( int i = 0 ; i < gameLocal.numClients ; i++ ) {
3105 idEntity *ent = gameLocal.entities[ i ];
3106 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
3109 idPlayer *p = static_cast<idPlayer *>( ent );
3110 if ( !p->IsReady() ) {
3111 PrintMessageEvent( -1, MSG_FORCEREADY, i );
3112 p->forcedReady = true;
3119 idMultiplayerGame::ForceReady_f
3122 void idMultiplayerGame::ForceReady_f( const idCmdArgs &args ) {
3123 if ( !gameLocal.isMultiplayer || gameLocal.isClient ) {
3124 common->Printf( "forceReady: multiplayer server only\n" );
3127 gameLocal.mpGame.ForceReady();
3132 idMultiplayerGame::DropWeapon
3135 void idMultiplayerGame::DropWeapon( int clientNum ) {
3136 assert( !gameLocal.isClient );
3137 idEntity *ent = gameLocal.entities[ clientNum ];
3138 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
3141 static_cast< idPlayer* >( ent )->DropWeapon( false );
3146 idMultiplayerGame::DropWeapon_f
3149 void idMultiplayerGame::DropWeapon_f( const idCmdArgs &args ) {
3150 if ( !gameLocal.isMultiplayer ) {
3151 common->Printf( "clientDropWeapon: only valid in multiplayer\n" );
3156 outMsg.Init( msgBuf, sizeof( msgBuf ) );
3157 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_DROPWEAPON );
3158 networkSystem->ClientSendReliableMessage( outMsg );
3163 idMultiplayerGame::MessageMode_f
3166 void idMultiplayerGame::MessageMode_f( const idCmdArgs &args ) {
3167 gameLocal.mpGame.MessageMode( args );
3172 idMultiplayerGame::MessageMode
3175 void idMultiplayerGame::MessageMode( const idCmdArgs &args ) {
3179 if ( !gameLocal.isMultiplayer ) {
3180 common->Printf( "clientMessageMode: only valid in multiplayer\n" );
3184 common->Printf( "no local client\n" );
3187 mode = args.Argv( 1 );
3191 imode = atoi( mode );
3193 msgmodeGui->SetStateString( "messagemode", imode ? "1" : "0" );
3194 msgmodeGui->SetStateString( "chattext", "" );
3196 // let the session know that we want our ingame main menu opened
3197 gameLocal.sessionCommand = "game_startmenu";
3202 idMultiplayerGame::Vote_f
3203 FIXME: voting from console
3206 void idMultiplayerGame::Vote_f( const idCmdArgs &args ) { }
3210 idMultiplayerGame::CallVote_f
3211 FIXME: voting from console
3214 void idMultiplayerGame::CallVote_f( const idCmdArgs &args ) { }
3218 idMultiplayerGame::ServerStartVote
3221 void idMultiplayerGame::ServerStartVote( int clientNum, vote_flags_t voteIndex, const char *value ) {
3224 assert( vote == VOTE_NONE );
3231 voteTimeOut = gameLocal.time + 20000;
3232 // mark players allowed to vote - only current ingame players, players joining during vote will be ignored
3233 for ( i = 0; i < gameLocal.numClients; i++ ) {
3234 if ( gameLocal.entities[ i ] && gameLocal.entities[ i ]->IsType( idPlayer::Type ) ) {
3235 playerState[ i ].vote = ( i == clientNum ) ? PLAYER_VOTE_YES : PLAYER_VOTE_WAIT;
3237 playerState[i].vote = PLAYER_VOTE_NONE;
3244 idMultiplayerGame::ClientStartVote
3247 void idMultiplayerGame::ClientStartVote( int clientNum, const char *_voteString ) {
3249 byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
3251 if ( !gameLocal.isClient ) {
3252 outMsg.Init( msgBuf, sizeof( msgBuf ) );
3253 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTVOTE );
3254 outMsg.WriteByte( clientNum );
3255 outMsg.WriteString( _voteString );
3256 networkSystem->ServerSendReliableMessage( -1, outMsg );
3259 voteString = _voteString;
3260 AddChatLine( va( common->GetLanguageDict()->GetString( "#str_04279" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) );
3261 gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE ] );
3262 if ( clientNum == gameLocal.localClientNum ) {
3267 if ( gameLocal.isClient ) {
3268 // the the vote value to something so the vote line is displayed
3269 vote = VOTE_RESTART;
3277 idMultiplayerGame::ClientUpdateVote
3280 void idMultiplayerGame::ClientUpdateVote( vote_result_t status, int yesCount, int noCount ) {
3282 byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
3284 if ( !gameLocal.isClient ) {
3285 outMsg.Init( msgBuf, sizeof( msgBuf ) );
3286 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_UPDATEVOTE );
3287 outMsg.WriteByte( status );
3288 outMsg.WriteByte( yesCount );
3289 outMsg.WriteByte( noCount );
3290 networkSystem->ServerSendReliableMessage( -1, outMsg );
3293 if ( vote == VOTE_NONE ) {
3294 // clients coming in late don't get the vote start and are not allowed to vote
3300 AddChatLine( common->GetLanguageDict()->GetString( "#str_04278" ) );
3301 gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE_FAILED ] );
3302 if ( gameLocal.isClient ) {
3307 AddChatLine( common->GetLanguageDict()->GetString( "#str_04277" ) );
3308 gameSoundWorld->PlayShaderDirectly( GlobalSoundStrings[ SND_VOTE_PASSED ] );
3311 if ( gameLocal.isClient ) {
3316 AddChatLine( common->GetLanguageDict()->GetString( "#str_04276" ) );
3317 if ( gameLocal.isClient ) {
3324 if ( gameLocal.isClient ) {
3325 yesVotes = yesCount;
3332 idMultiplayerGame::ClientCallVote
3335 void idMultiplayerGame::ClientCallVote( vote_flags_t voteIndex, const char *voteValue ) {
3337 byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
3340 outMsg.Init( msgBuf, sizeof( msgBuf ) );
3341 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CALLVOTE );
3342 outMsg.WriteByte( voteIndex );
3343 outMsg.WriteString( voteValue );
3344 networkSystem->ClientSendReliableMessage( outMsg );
3349 idMultiplayerGame::CastVote
3352 void idMultiplayerGame::CastVote( int clientNum, bool castVote ) {
3356 if ( clientNum == gameLocal.localClientNum ) {
3360 if ( gameLocal.isClient ) {
3361 outMsg.Init( msgBuf, sizeof( msgBuf ) );
3362 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CASTVOTE );
3363 outMsg.WriteByte( castVote );
3364 networkSystem->ClientSendReliableMessage( outMsg );
3369 if ( vote == VOTE_NONE ) {
3370 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04275" ) );
3371 common->DPrintf( "client %d: cast vote while no vote in progress\n", clientNum );
3374 if ( playerState[ clientNum ].vote != PLAYER_VOTE_WAIT ) {
3375 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04274" ) );
3376 common->DPrintf( "client %d: cast vote - vote %d != PLAYER_VOTE_WAIT\n", clientNum, playerState[ clientNum ].vote );
3381 playerState[ clientNum ].vote = PLAYER_VOTE_YES;
3384 playerState[ clientNum ].vote = PLAYER_VOTE_NO;
3388 ClientUpdateVote( VOTE_UPDATE, yesVotes, noVotes );
3393 idMultiplayerGame::ServerCallVote
3396 void idMultiplayerGame::ServerCallVote( int clientNum, const idBitMsg &msg ) {
3397 vote_flags_t voteIndex;
3398 int vote_timeLimit, vote_fragLimit, vote_clientNum, vote_gameTypeIndex; //, vote_kickIndex;
3399 char value[ MAX_STRING_CHARS ];
3401 assert( clientNum != -1 );
3402 assert( !gameLocal.isClient );
3404 voteIndex = (vote_flags_t)msg.ReadByte( );
3405 msg.ReadString( value, sizeof( value ) );
3407 // sanity checks - setup the vote
3408 if ( vote != VOTE_NONE ) {
3409 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04273" ) );
3410 common->DPrintf( "client %d: called vote while voting already in progress - ignored\n", clientNum );
3413 switch ( voteIndex ) {
3415 ServerStartVote( clientNum, voteIndex, "" );
3416 ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04271" ) );
3419 ServerStartVote( clientNum, voteIndex, "" );
3420 ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04272" ) );
3422 case VOTE_TIMELIMIT:
3423 vote_timeLimit = strtol( value, NULL, 10 );
3424 if ( vote_timeLimit == gameLocal.serverInfo.GetInt( "si_timeLimit" ) ) {
3425 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04270" ) );
3426 common->DPrintf( "client %d: already at the voted Time Limit\n", clientNum );
3429 if ( vote_timeLimit < si_timeLimit.GetMinValue() || vote_timeLimit > si_timeLimit.GetMaxValue() ) {
3430 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04269" ) );
3431 common->DPrintf( "client %d: timelimit value out of range for vote: %s\n", clientNum, value );
3434 ServerStartVote( clientNum, voteIndex, value );
3435 ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04268" ), vote_timeLimit ) );
3437 case VOTE_FRAGLIMIT:
3438 vote_fragLimit = strtol( value, NULL, 10 );
3439 if ( vote_fragLimit == gameLocal.serverInfo.GetInt( "si_fragLimit" ) ) {
3440 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04267" ) );
3441 common->DPrintf( "client %d: already at the voted Frag Limit\n", clientNum );
3444 if ( vote_fragLimit < si_fragLimit.GetMinValue() || vote_fragLimit > si_fragLimit.GetMaxValue() ) {
3445 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04266" ) );
3446 common->DPrintf( "client %d: fraglimit value out of range for vote: %s\n", clientNum, value );
3449 ServerStartVote( clientNum, voteIndex, value );
3450 ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04303" ), gameLocal.gameType == GAME_LASTMAN ? common->GetLanguageDict()->GetString( "#str_04264" ) : common->GetLanguageDict()->GetString( "#str_04265" ), vote_fragLimit ) );
3453 vote_gameTypeIndex = strtol( value, NULL, 10 );
3455 assert( vote_gameTypeIndex > 0 && vote_gameTypeIndex < GAME_COUNT );
3456 strcpy( value, si_gameTypeArgs[ vote_gameTypeIndex ] );
3460 assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex <= 4 );
3462 assert( vote_gameTypeIndex >= 0 && vote_gameTypeIndex <= 3 );
3464 switch ( vote_gameTypeIndex ) {
3466 strcpy( value, "Deathmatch" );
3469 strcpy( value, "Tourney" );
3472 strcpy( value, "Team DM" );
3475 strcpy( value, "Last Man" );
3479 strcpy( value, "CTF" );
3483 if ( !idStr::Icmp( value, gameLocal.serverInfo.GetString( "si_gameType" ) ) ) {
3484 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04259" ) );
3485 common->DPrintf( "client %d: already at the voted Game Type\n", clientNum );
3488 ServerStartVote( clientNum, voteIndex, value );
3489 ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04258" ), value ) );
3492 vote_clientNum = strtol( value, NULL, 10 );
3493 if ( vote_clientNum == gameLocal.localClientNum ) {
3494 gameLocal.ServerSendChatMessage( clientNum, "server", common->GetLanguageDict()->GetString( "#str_04257" ) );
3495 common->DPrintf( "client %d: called kick for the server host\n", clientNum );
3498 ServerStartVote( clientNum, voteIndex, va( "%d", vote_clientNum ) );
3499 ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04302" ), vote_clientNum, gameLocal.userInfo[ vote_clientNum ].GetString( "ui_name" ) ) );
3502 if ( idStr::FindText( gameLocal.serverInfo.GetString( "si_map" ), value ) != -1 ) {
3503 gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04295" ), value ) );
3504 common->DPrintf( "client %d: already running the voted map: %s\n", clientNum, value );
3507 int num = fileSystem->GetNumMaps();
3510 bool haveMap = false;
3511 for ( i = 0; i < num; i++ ) {
3512 dict = fileSystem->GetMapDecl( i );
3513 if ( dict && !idStr::Icmp( dict->GetString( "path" ), value ) ) {
3519 gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04296" ), value ) );
3520 common->Printf( "client %d: map not found: %s\n", clientNum, value );
3523 ServerStartVote( clientNum, voteIndex, value );
3524 ClientStartVote( clientNum, va( common->GetLanguageDict()->GetString( "#str_04256" ), common->GetLanguageDict()->GetString( dict ? dict->GetString( "name" ) : value ) ) );
3527 case VOTE_SPECTATORS:
3528 if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
3529 ServerStartVote( clientNum, voteIndex, "" );
3530 ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04255" ) );
3532 ServerStartVote( clientNum, voteIndex, "" );
3533 ClientStartVote( clientNum, common->GetLanguageDict()->GetString( "#str_04254" ) );
3537 gameLocal.ServerSendChatMessage( clientNum, "server", va( common->GetLanguageDict()->GetString( "#str_04297" ), (int)voteIndex ) );
3538 common->DPrintf( "client %d: unknown vote index %d\n", clientNum, voteIndex );
3544 idMultiplayerGame::DisconnectClient
3547 void idMultiplayerGame::DisconnectClient( int clientNum ) {
3548 if ( lastWinner == clientNum ) {
3551 UpdatePlayerRanks();
3557 idMultiplayerGame::CheckAbortGame
3560 void idMultiplayerGame::CheckAbortGame( void ) {
3562 if ( gameLocal.gameType == GAME_TOURNEY && gameState == WARMUP ) {
3563 // if a tourney player joined spectators, let someone else have his spot
3564 for ( i = 0; i < 2; i++ ) {
3565 if ( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || static_cast< idPlayer * >( gameLocal.entities[ currentTourneyPlayer[ i ] ] )->spectating ) {
3566 currentTourneyPlayer[ i ] = -1;
3570 // only checks for aborts -> game review below
3571 if ( gameState != COUNTDOWN && gameState != GAMEON && gameState != SUDDENDEATH ) {
3574 switch ( gameLocal.gameType ) {
3576 for ( i = 0; i < 2; i++ ) {
3577 if ( !gameLocal.entities[ currentTourneyPlayer[ i ] ] || static_cast< idPlayer * >( gameLocal.entities[ currentTourneyPlayer[ i ] ] )->spectating ) {
3578 NewState( GAMEREVIEW );
3584 if ( !EnoughClientsToPlay() ) {
3585 NewState( GAMEREVIEW );
3593 idMultiplayerGame::WantKilled
3596 void idMultiplayerGame::WantKilled( int clientNum ) {
3597 idEntity *ent = gameLocal.entities[ clientNum ];
3598 if ( ent && ent->IsType( idPlayer::Type ) ) {
3599 static_cast<idPlayer *>( ent )->Kill( false, false );
3605 idMultiplayerGame::MapRestart
3608 void idMultiplayerGame::MapRestart( void ) {
3611 assert( !gameLocal.isClient );
3612 if ( gameState != WARMUP ) {
3614 nextState = INACTIVE;
3615 nextStateSwitch = 0;
3626 // still balance teams in CTF
3627 if ( g_balanceTDM.GetBool() && lastGameType != GAME_TDM && lastGameType != GAME_CTF && gameLocal.mpGame.IsGametypeTeamBased() ) {
3629 if ( g_balanceTDM.GetBool() && lastGameType != GAME_TDM && gameLocal.gameType == GAME_TDM ) {
3631 for ( clientNum = 0; clientNum < gameLocal.numClients; clientNum++ ) {
3632 if ( gameLocal.entities[ clientNum ] && gameLocal.entities[ clientNum ]->IsType( idPlayer::Type ) ) {
3633 if ( static_cast< idPlayer* >( gameLocal.entities[ clientNum ] )->BalanceTDM() ) {
3634 // core is in charge of syncing down userinfo changes
3635 // it will also call back game through SetUserInfo with the current info for update
3636 cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "updateUI %d\n", clientNum ) );
3641 lastGameType = gameLocal.gameType;
3646 idMultiplayerGame::SwitchToTeam
3649 void idMultiplayerGame::SwitchToTeam( int clientNum, int oldteam, int newteam ) {
3653 assert( IsGametypeTeamBased() ); /* CTF */
3654 assert( oldteam != newteam );
3655 assert( !gameLocal.isClient );
3657 if ( !gameLocal.isClient && newteam >= 0 && IsInGame( clientNum ) ) {
3658 PrintMessageEvent( -1, MSG_JOINTEAM, clientNum, newteam );
3660 // assign the right teamFragCount
3661 for( i = 0; i < gameLocal.numClients; i++ ) {
3662 if ( i == clientNum ) {
3665 ent = gameLocal.entities[ i ];
3666 if ( ent && ent->IsType( idPlayer::Type ) && static_cast< idPlayer * >(ent)->team == newteam ) {
3667 playerState[ clientNum ].teamFragCount = playerState[ i ].teamFragCount;
3671 if ( i == gameLocal.numClients ) {
3672 // alone on this team
3673 playerState[ clientNum ].teamFragCount = 0;
3677 if ( ( gameState == GAMEON || ( IsGametypeFlagBased() && gameState == SUDDENDEATH ) ) && oldteam != -1 ) {
3679 if ( gameState == GAMEON && oldteam != -1 ) {
3681 // when changing teams during game, kill and respawn
3682 idPlayer *p = static_cast<idPlayer *>( gameLocal.entities[ clientNum ] );
3683 if ( p->IsInTeleport() ) {
3684 p->ServerSendEvent( idPlayer::EVENT_ABORT_TELEPORTER, NULL, false, -1 );
3685 p->SetPrivateCameraView( NULL );
3687 p->Kill( true, true );
3689 if ( IsGametypeFlagBased() )
3695 else if ( IsGametypeFlagBased() && oldteam != -1 ) {
3696 idPlayer *p = static_cast<idPlayer *>( gameLocal.entities[ clientNum ] );
3704 idMultiplayerGame::ProcessChatMessage
3707 void idMultiplayerGame::ProcessChatMessage( int clientNum, bool team, const char *name, const char *text, const char *sound ) {
3710 const char *prefix = NULL;
3711 int send_to; // 0 - all, 1 - specs, 2 - team
3715 idStr prefixed_name;
3717 assert( !gameLocal.isClient );
3719 if ( clientNum >= 0 ) {
3720 p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
3721 if ( !( p && p->IsType( idPlayer::Type ) ) ) {
3725 if ( p->spectating ) {
3726 prefix = "spectating";
3727 if ( team || ( !g_spectatorChat.GetBool() && ( gameState == GAMEON || gameState == SUDDENDEATH ) ) ) {
3734 } else if ( team ) {
3746 // put the message together
3747 outMsg.Init( msgBuf, sizeof( msgBuf ) );
3748 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_CHAT );
3750 prefixed_name = va( "(%s) %s", prefix, name );
3752 prefixed_name = name;
3754 outMsg.WriteString( prefixed_name );
3755 outMsg.WriteString( text, -1, false );
3757 AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
3758 networkSystem->ServerSendReliableMessage( -1, outMsg );
3760 PlayGlobalSound( -1, SND_COUNT, sound );
3763 for ( i = 0; i < gameLocal.numClients; i++ ) {
3764 ent = gameLocal.entities[ i ];
3765 if ( !ent || !ent->IsType( idPlayer::Type ) ) {
3768 if ( send_to == 1 && static_cast< idPlayer * >( ent )->spectating ) {
3770 PlayGlobalSound( i, SND_COUNT, sound );
3772 if ( i == gameLocal.localClientNum ) {
3773 AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
3775 networkSystem->ServerSendReliableMessage( i, outMsg );
3777 } else if ( send_to == 2 && static_cast< idPlayer * >( ent )->team == p->team ) {
3779 PlayGlobalSound( i, SND_COUNT, sound );
3781 if ( i == gameLocal.localClientNum ) {
3782 AddChatLine( "%s^0: %s\n", prefixed_name.c_str(), text );
3784 networkSystem->ServerSendReliableMessage( i, outMsg );
3793 idMultiplayerGame::Precache
3796 void idMultiplayerGame::Precache( void ) {
3800 if ( !gameLocal.isMultiplayer ) {
3803 gameLocal.FindEntityDefDict( "player_doommarine", false );;
3806 idStr str = cvarSystem->GetCVarString( "mod_validSkins" );
3808 while ( str.Length() ) {
3809 int n = str.Find( ";" );
3811 skin = str.Left( n );
3812 str = str.Right( str.Length() - n - 1 );
3817 declManager->FindSkin( skin, false );
3820 for ( i = 0; ui_skinArgs[ i ]; i++ ) {
3821 declManager->FindSkin( ui_skinArgs[ i ], false );
3824 for ( i = 0; i < SND_COUNT; i++ ) {
3825 f = fileSystem->OpenFileRead( GlobalSoundStrings[ i ] );
3826 fileSystem->CloseFile( f );
3828 // MP guis. just make sure we hit all of them
3830 while ( MPGuis[ i ] ) {
3831 uiManager->FindGui( MPGuis[ i ], true );
3838 idMultiplayerGame::ToggleSpectate
3841 void idMultiplayerGame::ToggleSpectate( void ) {
3843 assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
3845 spectating = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_spectate" ), "Spectate" ) == 0 );
3847 // always allow toggling to play
3848 cvarSystem->SetCVarString( "ui_spectate", "Play" );
3850 // only allow toggling to spectate if spectators are enabled.
3851 if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
3852 cvarSystem->SetCVarString( "ui_spectate", "Spectate" );
3854 gameLocal.mpGame.AddChatLine( common->GetLanguageDict()->GetString( "#str_06747" ) );
3861 idMultiplayerGame::ToggleReady
3864 void idMultiplayerGame::ToggleReady( void ) {
3866 assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
3868 ready = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_ready" ), "Ready" ) == 0 );
3870 cvarSystem->SetCVarString( "ui_ready", "Not Ready" );
3872 cvarSystem->SetCVarString( "ui_ready", "Ready" );
3878 idMultiplayerGame::ToggleTeam
3881 void idMultiplayerGame::ToggleTeam( void ) {
3883 assert( gameLocal.isClient || gameLocal.localClientNum == 0 );
3885 team = ( idStr::Icmp( cvarSystem->GetCVarString( "ui_team" ), "Red" ) == 0 );
3887 cvarSystem->SetCVarString( "ui_team", "Blue" );
3889 cvarSystem->SetCVarString( "ui_team", "Red" );
3895 idMultiplayerGame::ToggleUserInfo
3898 void idMultiplayerGame::ThrottleUserInfo( void ) {
3901 assert( gameLocal.localClientNum >= 0 );
3904 while ( ThrottleVars[ i ] ) {
3905 if ( idStr::Icmp( gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ),
3906 cvarSystem->GetCVarString( ThrottleVars[ i ] ) ) ) {
3907 if ( gameLocal.realClientTime < switchThrottle[ i ] ) {
3908 AddChatLine( common->GetLanguageDict()->GetString( "#str_04299" ), common->GetLanguageDict()->GetString( ThrottleVarsInEnglish[ i ] ), ( switchThrottle[ i ] - gameLocal.time ) / 1000 + 1 );
3909 cvarSystem->SetCVarString( ThrottleVars[ i ], gameLocal.userInfo[ gameLocal.localClientNum ].GetString( ThrottleVars[ i ] ) );
3911 switchThrottle[ i ] = gameLocal.time + ThrottleDelay[ i ] * 1000;
3920 idMultiplayerGame::CanPlay
3923 bool idMultiplayerGame::CanPlay( idPlayer *p ) {
3924 return !p->wantSpectate && playerState[ p->entityNumber ].ingame;
3929 idMultiplayerGame::EnterGame
3932 void idMultiplayerGame::EnterGame( int clientNum ) {
3933 assert( !gameLocal.isClient );
3935 if ( !playerState[ clientNum ].ingame ) {
3936 playerState[ clientNum ].ingame = true;
3937 if ( gameLocal.isMultiplayer ) {
3938 // can't use PrintMessageEvent as clients don't know the nickname yet
3939 gameLocal.ServerSendChatMessage( -1, common->GetLanguageDict()->GetString( "#str_02047" ), va( common->GetLanguageDict()->GetString( "#str_07177" ), gameLocal.userInfo[ clientNum ].GetString( "ui_name" ) ) );
3946 idMultiplayerGame::WantRespawn
3949 bool idMultiplayerGame::WantRespawn( idPlayer *p ) {
3950 return p->forceRespawn && !p->wantSpectate && playerState[ p->entityNumber ].ingame;
3955 idMultiplayerGame::VoiceChat
3958 void idMultiplayerGame::VoiceChat_f( const idCmdArgs &args ) {
3959 gameLocal.mpGame.VoiceChat( args, false );
3964 idMultiplayerGame::VoiceChatTeam
3967 void idMultiplayerGame::VoiceChatTeam_f( const idCmdArgs &args ) {
3968 gameLocal.mpGame.VoiceChat( args, true );
3973 idMultiplayerGame::VoiceChat
3976 void idMultiplayerGame::VoiceChat( const idCmdArgs &args, bool team ) {
3980 const idDict *spawnArgs;
3981 const idKeyValue *keyval;
3984 if ( !gameLocal.isMultiplayer ) {
3985 common->Printf( "clientVoiceChat: only valid in multiplayer\n" );
3988 if ( args.Argc() != 2 ) {
3989 common->Printf( "clientVoiceChat: bad args\n" );
3993 if ( gameLocal.realClientTime < voiceChatThrottle ) {
3997 voc = args.Argv( 1 );
3998 spawnArgs = gameLocal.FindEntityDefDict( "player_doommarine", false );
3999 keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL );
4002 if ( !keyval->GetValue().Icmp( voc ) ) {
4005 keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval );
4009 common->Printf( "Voice command not found: %s\n", voc );
4012 voiceChatThrottle = gameLocal.realClientTime + 1000;
4014 outMsg.Init( msgBuf, sizeof( msgBuf ) );
4015 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_VCHAT );
4016 outMsg.WriteLong( index );
4017 outMsg.WriteBits( team ? 1 : 0, 1 );
4018 networkSystem->ClientSendReliableMessage( outMsg );
4023 idMultiplayerGame::ProcessVoiceChat
4026 void idMultiplayerGame::ProcessVoiceChat( int clientNum, bool team, int index ) {
4027 const idDict *spawnArgs;
4028 const idKeyValue *keyval;
4034 p = static_cast< idPlayer * >( gameLocal.entities[ clientNum ] );
4035 if ( !( p && p->IsType( idPlayer::Type ) ) ) {
4039 if ( p->spectating ) {
4043 // lookup the sound def
4044 spawnArgs = gameLocal.FindEntityDefDict( "player_doommarine", false );
4045 keyval = spawnArgs->MatchPrefix( "snd_voc_", NULL );
4046 while ( index > 0 && keyval ) {
4047 keyval = spawnArgs->MatchPrefix( "snd_voc_", keyval );
4051 common->DPrintf( "ProcessVoiceChat: unknown chat index %d\n", index );
4054 snd_key = keyval->GetKey();
4055 name = gameLocal.userInfo[ clientNum ].GetString( "ui_name" );
4056 sprintf( text_key, "txt_%s", snd_key.Right( snd_key.Length() - 4 ).c_str() );
4057 if ( team || gameState == COUNTDOWN || gameState == GAMEREVIEW ) {
4058 ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), spawnArgs->GetString( snd_key ) );
4060 p->StartSound( snd_key, SND_CHANNEL_ANY, 0, true, NULL );
4061 ProcessChatMessage( clientNum, team, name, spawnArgs->GetString( text_key ), NULL );
4067 idMultiplayerGame::ServerWriteInitialReliableMessages
4070 void idMultiplayerGame::ServerWriteInitialReliableMessages( int clientNum ) {
4072 byte msgBuf[ MAX_GAME_MESSAGE_SIZE ];
4076 outMsg.Init( msgBuf, sizeof( msgBuf ) );
4077 outMsg.BeginWriting();
4078 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_STARTSTATE );
4079 // send the game state and start time
4080 outMsg.WriteByte( gameState );
4081 outMsg.WriteLong( matchStartedTime );
4082 outMsg.WriteShort( startFragLimit );
4083 // send the powerup states and the spectate states
4084 for( i = 0; i < gameLocal.numClients; i++ ) {
4085 ent = gameLocal.entities[ i ];
4086 if ( i != clientNum && ent && ent->IsType( idPlayer::Type ) ) {
4087 outMsg.WriteShort( i );
4088 outMsg.WriteShort( static_cast< idPlayer * >( ent )->inventory.powerups );
4089 outMsg.WriteBits( static_cast< idPlayer * >( ent )->spectating, 1 );
4092 outMsg.WriteShort( MAX_CLIENTS );
4093 networkSystem->ServerSendReliableMessage( clientNum, outMsg );
4095 // we send SI in connectResponse messages, but it may have been modified already
4096 outMsg.BeginWriting( );
4097 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_SERVERINFO );
4098 outMsg.WriteDeltaDict( gameLocal.serverInfo, NULL );
4099 networkSystem->ServerSendReliableMessage( clientNum, outMsg );
4102 if ( gameState == COUNTDOWN ) {
4103 outMsg.BeginWriting();
4104 outMsg.WriteByte( GAME_RELIABLE_MESSAGE_WARMUPTIME );
4105 outMsg.WriteLong( warmupEndTime );
4106 networkSystem->ServerSendReliableMessage( clientNum, outMsg );
4112 idMultiplayerGame::ClientReadStartState
4115 void idMultiplayerGame::ClientReadStartState( const idBitMsg &msg ) {
4116 int i, client, powerup;
4118 // read the state in preparation for reading snapshot updates
4119 gameState = (idMultiplayerGame::gameState_t)msg.ReadByte();
4120 matchStartedTime = msg.ReadLong( );
4121 startFragLimit = msg.ReadShort( );
4122 while ( ( client = msg.ReadShort() ) != MAX_CLIENTS ) {
4123 assert( gameLocal.entities[ client ] && gameLocal.entities[ client ]->IsType( idPlayer::Type ) );
4124 powerup = msg.ReadShort();
4125 for ( i = 0; i < MAX_POWERUPS; i++ ) {
4126 if ( powerup & ( 1 << i ) ) {
4127 static_cast< idPlayer * >( gameLocal.entities[ client ] )->GivePowerUp( i, 0 );
4130 bool spectate = ( msg.ReadBits( 1 ) != 0 );
4131 static_cast< idPlayer * >( gameLocal.entities[ client ] )->Spectate( spectate );
4137 idMultiplayerGame::ClientReadWarmupTime
4140 void idMultiplayerGame::ClientReadWarmupTime( const idBitMsg &msg ) {
4141 warmupEndTime = msg.ReadLong();
4148 The below IsGametype...() functions were implemented for CTF,
4149 but we did not #ifdef CTF them, because doing so would clutter
4150 the codebase substantially. Please consider them part of the merged
4156 idMultiplayerGame::IsGametypeTeamBased
4159 bool idMultiplayerGame::IsGametypeTeamBased( void ) /* CTF */
4161 switch ( gameLocal.gameType )
4175 assert( !"Add support for your new gametype here." );
4183 idMultiplayerGame::IsGametypeFlagBased
4186 bool idMultiplayerGame::IsGametypeFlagBased( void ) {
4187 switch ( gameLocal.gameType )
4202 assert( !"Add support for your new gametype here." );
4212 idMultiplayerGame::GetTeamFlag
4215 idItemTeam * idMultiplayerGame::GetTeamFlag( int team ) {
4216 assert( team == 0 || team == 1 );
4218 if ( !IsGametypeFlagBased() || ( team != 0 && team != 1 ) ) /* CTF */
4221 // TODO : just call on map start
4224 return teamFlags[team];
4229 idMultiplayerGame::GetTeamFlag
4232 void idMultiplayerGame::FindTeamFlags( void ) {
4233 char * flagDefs[2] =
4239 for ( int i = 0; i < 2; i++)
4241 idEntity * entity = gameLocal.FindEntityUsingDef( NULL, flagDefs[i] );
4244 if ( entity == NULL )
4247 idItemTeam * flag = static_cast<idItemTeam *>(entity);
4249 if ( flag->team == i )
4251 teamFlags[i] = flag;
4255 entity = gameLocal.FindEntityUsingDef( entity, flagDefs[i] );
4262 idMultiplayerGame::GetFlagStatus
4265 flagStatus_t idMultiplayerGame::GetFlagStatus( int team ) {
4266 //assert( IsGametypeFlagBased() );
4268 idItemTeam *teamFlag = GetTeamFlag( team );
4269 //assert( teamFlag != NULL );
4271 if ( teamFlag != NULL ) {
4272 if ( teamFlag->carried == false && teamFlag->dropped == false )
4273 return FLAGSTATUS_INBASE;
4275 if ( teamFlag->carried == true )
4276 return FLAGSTATUS_TAKEN;
4278 if ( teamFlag->carried == false && teamFlag->dropped == true )
4279 return FLAGSTATUS_STRAY;
4282 //assert( !"Invalid flag state." );
4283 return FLAGSTATUS_NONE;
4288 idMultiplayerGame::SetFlagMsgs
4291 void idMultiplayerGame::SetFlagMsg( bool b ) {
4297 idMultiplayerGame::IsFlagMsgOn
4300 bool idMultiplayerGame::IsFlagMsgOn( void ) {
4301 return ( GetGameState() == WARMUP || GetGameState() == GAMEON || GetGameState() == SUDDENDEATH ) && flagMsgOn;
4307 idMultiplayerGame::SetBestGametype
4310 void idMultiplayerGame::SetBestGametype( const char * map ) {
4311 const char *gametype = gameLocal.serverInfo.GetString( "si_gameType" );
4312 // const char *map = gameLocal.serverInfo.GetString( "si_map" );
4313 int num = declManager->GetNumDecls( DECL_MAPDEF );
4316 for ( i = 0; i < num; i++ ) {
4317 const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_MAPDEF, i ) );
4319 if ( mapDef && idStr::Icmp( mapDef->GetName(), map ) == 0 ) {
4320 if ( mapDef->dict.GetBool( gametype ) ) {
4321 // dont change gametype
4325 for ( j = 1; si_gameTypeArgs[ j ]; j++ ) {
4326 if ( mapDef->dict.GetBool( si_gameTypeArgs[ j ] ) ) {
4327 si_gameType.SetString( si_gameTypeArgs[ j ] );
4332 // error out, no valid gametype
4340 idMultiplayerGame::ReloadScoreboard
4343 void idMultiplayerGame::ReloadScoreboard() {
4344 // CTF uses its own scoreboard
4345 if ( IsGametypeFlagBased() )
4346 scoreBoard = uiManager->FindGui( "guis/ctfscoreboard.gui", true, false, true );
4348 scoreBoard = uiManager->FindGui( "guis/scoreboard.gui", true, false, true );
4357 idStr idMultiplayerGame::GetBestGametype( const char* map, const char* gametype ) {
4359 int num = declManager->GetNumDecls( DECL_MAPDEF );
4362 for ( i = 0; i < num; i++ ) {
4363 const idDeclEntityDef *mapDef = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_MAPDEF, i ) );
4365 if ( mapDef && idStr::Icmp( mapDef->GetName(), map ) == 0 ) {
4366 if ( mapDef->dict.GetBool( gametype ) ) {
4367 // dont change gametype
4371 for ( j = 1; si_gameTypeArgs[ j ]; j++ ) {
4372 if ( mapDef->dict.GetBool( si_gameTypeArgs[ j ] ) ) {
4373 return si_gameTypeArgs[ j ];
4377 // error out, no valid gametype
4378 return "deathmatch";
4382 //For testing a new map let it play any gametpye